@hominis/fireforge 0.44.4 → 0.46.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 +120 -0
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/cli-usage-error.d.ts +46 -0
- package/dist/src/cli-usage-error.js +81 -0
- package/dist/src/cli.d.ts +5 -1
- package/dist/src/cli.js +20 -21
- package/dist/src/commands/bootstrap.js +1 -2
- package/dist/src/commands/build.js +15 -6
- package/dist/src/commands/destructive-decision.d.ts +19 -0
- package/dist/src/commands/destructive-decision.js +31 -0
- package/dist/src/commands/doctor-check-core.d.ts +0 -6
- package/dist/src/commands/doctor-check-core.js +0 -8
- package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
- package/dist/src/commands/doctor-orphaned-harness.js +4 -12
- package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
- package/dist/src/commands/doctor-source-pin.d.ts +29 -0
- package/dist/src/commands/doctor-source-pin.js +81 -0
- package/dist/src/commands/doctor.d.ts +2 -0
- package/dist/src/commands/doctor.js +9 -8
- package/dist/src/commands/download.js +35 -24
- package/dist/src/commands/export-all.js +21 -4
- package/dist/src/commands/export-flow.js +15 -60
- package/dist/src/commands/export-placement-conflicts.js +3 -3
- package/dist/src/commands/export-placement-gate.js +2 -8
- package/dist/src/commands/export-placement-policy.js +3 -5
- package/dist/src/commands/export-shared.d.ts +42 -23
- package/dist/src/commands/export-shared.js +4 -19
- package/dist/src/commands/export.js +10 -1
- package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
- package/dist/src/commands/furnace/authoring-config.js +19 -0
- package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
- package/dist/src/commands/furnace/create-browser-test.js +3 -8
- package/dist/src/commands/furnace/create-dry-run.js +2 -5
- package/dist/src/commands/furnace/create-templates.d.ts +17 -1
- package/dist/src/commands/furnace/create-templates.js +2 -1
- package/dist/src/commands/furnace/create.js +25 -19
- package/dist/src/commands/furnace/deploy.js +11 -2
- package/dist/src/commands/furnace/init.js +2 -1
- package/dist/src/commands/furnace/override.js +28 -19
- package/dist/src/commands/furnace/refresh.js +9 -1
- package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
- package/dist/src/commands/furnace/remove-mutations.js +0 -1
- package/dist/src/commands/furnace/remove.js +2 -6
- package/dist/src/commands/furnace/rename-browser-test.js +1 -7
- package/dist/src/commands/furnace/rename.js +29 -29
- package/dist/src/commands/furnace/status.js +12 -9
- package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
- package/dist/src/commands/furnace/test-file-name.js +39 -0
- package/dist/src/commands/furnace/validation-output.d.ts +21 -9
- package/dist/src/commands/furnace/validation-output.js +3 -8
- package/dist/src/commands/import.js +2 -9
- package/dist/src/commands/lint-per-patch.d.ts +2 -2
- package/dist/src/commands/lint-per-patch.js +19 -4
- package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
- package/dist/src/commands/lint.d.ts +2 -1
- package/dist/src/commands/lint.js +1 -1
- package/dist/src/commands/manifest.js +1 -1
- package/dist/src/commands/package.js +2 -4
- package/dist/src/commands/patch/compact.js +14 -15
- package/dist/src/commands/patch/delete.js +15 -25
- package/dist/src/commands/patch/entry-projection.d.ts +13 -0
- package/dist/src/commands/patch/entry-projection.js +24 -0
- package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
- package/dist/src/commands/patch/lint-ignore.js +19 -29
- package/dist/src/commands/patch/move-files-create.js +24 -10
- package/dist/src/commands/patch/move-files-into.js +38 -37
- package/dist/src/commands/patch/move-files.js +4 -13
- package/dist/src/commands/patch/rename.js +27 -41
- package/dist/src/commands/patch/reorder.js +25 -38
- package/dist/src/commands/patch/split-plan.d.ts +4 -5
- package/dist/src/commands/patch/split-plan.js +4 -11
- package/dist/src/commands/patch/split.js +45 -43
- package/dist/src/commands/patch/staged-dependency.js +16 -22
- package/dist/src/commands/patch/tier.js +15 -25
- package/dist/src/commands/patch-rename-map.d.ts +19 -0
- package/dist/src/commands/patch-rename-map.js +36 -0
- package/dist/src/commands/pending-resolution.d.ts +15 -0
- package/dist/src/commands/pending-resolution.js +25 -0
- package/dist/src/commands/re-export-adjacent.d.ts +32 -2
- package/dist/src/commands/re-export-adjacent.js +51 -10
- package/dist/src/commands/re-export-bulk-scan.js +6 -10
- package/dist/src/commands/re-export-drift.d.ts +8 -0
- package/dist/src/commands/re-export-drift.js +65 -4
- package/dist/src/commands/re-export-files.d.ts +2 -3
- package/dist/src/commands/re-export-files.js +38 -32
- package/dist/src/commands/re-export-lint.d.ts +3 -4
- package/dist/src/commands/re-export-lint.js +9 -2
- package/dist/src/commands/re-export-options.js +19 -5
- package/dist/src/commands/re-export-register.js +5 -1
- package/dist/src/commands/re-export-scan.js +8 -8
- package/dist/src/commands/re-export-single.d.ts +22 -3
- package/dist/src/commands/re-export-single.js +19 -11
- package/dist/src/commands/re-export.js +44 -27
- package/dist/src/commands/rebase/abort.js +3 -8
- package/dist/src/commands/rebase/continue.js +12 -12
- package/dist/src/commands/rebase/index.js +2 -2
- package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
- package/dist/src/commands/rebase/patch-loop.js +2 -7
- package/dist/src/commands/resolve.js +18 -10
- package/dist/src/commands/run.d.ts +0 -13
- package/dist/src/commands/run.js +12 -25
- package/dist/src/commands/setup-support.d.ts +16 -4
- package/dist/src/commands/setup-support.js +16 -16
- package/dist/src/commands/setup.js +3 -9
- package/dist/src/commands/source.js +8 -1
- package/dist/src/commands/status-check.js +2 -2
- package/dist/src/commands/status-output.js +2 -2
- package/dist/src/commands/status.js +3 -4
- package/dist/src/commands/test-build-phase.d.ts +2 -2
- package/dist/src/commands/test-build-phase.js +33 -8
- package/dist/src/commands/test-diagnose.js +14 -5
- package/dist/src/commands/test-modes.d.ts +2 -2
- package/dist/src/commands/test-modes.js +0 -1
- package/dist/src/commands/test-register.js +6 -3
- package/dist/src/commands/test-run.js +29 -21
- package/dist/src/commands/test-stale-gate.js +8 -5
- package/dist/src/commands/test-verdict.d.ts +16 -2
- package/dist/src/commands/test-verdict.js +18 -3
- package/dist/src/commands/test.js +51 -7
- package/dist/src/commands/token-list.d.ts +26 -0
- package/dist/src/commands/token-list.js +153 -0
- package/dist/src/commands/token.js +17 -1
- package/dist/src/commands/tree.js +7 -2
- package/dist/src/commands/typecheck.js +9 -9
- package/dist/src/commands/wire.js +4 -3
- package/dist/src/core/branding.d.ts +0 -7
- package/dist/src/core/branding.js +3 -2
- package/dist/src/core/browser-wire.js +6 -3
- package/dist/src/core/build-audit-platform.d.ts +29 -0
- package/dist/src/core/build-audit-platform.js +140 -0
- package/dist/src/core/build-audit-registration.js +3 -2
- package/dist/src/core/build-audit-skip.d.ts +57 -0
- package/dist/src/core/build-audit-skip.js +112 -0
- package/dist/src/core/build-audit.d.ts +15 -2
- package/dist/src/core/build-audit.js +154 -12
- package/dist/src/core/build-baseline.d.ts +45 -28
- package/dist/src/core/build-baseline.js +6 -29
- package/dist/src/core/build-jar-escalation.d.ts +64 -0
- package/dist/src/core/build-jar-escalation.js +128 -0
- package/dist/src/core/build-overwrite-guard.js +1 -1
- package/dist/src/core/build-prepare.d.ts +5 -2
- package/dist/src/core/build-prepare.js +34 -6
- package/dist/src/core/config-paths.d.ts +2 -6
- package/dist/src/core/config-paths.js +4 -2
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -19
- package/dist/src/core/config-validate-build-audit.d.ts +8 -0
- package/dist/src/core/config-validate-build-audit.js +80 -0
- package/dist/src/core/config-validate-file-size.d.ts +12 -0
- package/dist/src/core/config-validate-file-size.js +65 -0
- package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
- package/dist/src/core/config-validate-patch-policy.js +3 -3
- package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
- package/dist/src/core/config-validate-test-toolchains.js +4 -5
- package/dist/src/core/config-validate.js +20 -3
- package/dist/src/core/config.d.ts +2 -2
- package/dist/src/core/config.js +11 -15
- package/dist/src/core/coverage-extend.js +4 -4
- package/dist/src/core/discard-baseline.js +7 -7
- package/dist/src/core/engine-precondition.d.ts +1 -9
- package/dist/src/core/engine-precondition.js +3 -8
- package/dist/src/core/engine-session-lock.js +13 -17
- package/dist/src/core/export-moved-code-shape.js +2 -2
- package/dist/src/core/file-lock-owner.d.ts +103 -0
- package/dist/src/core/file-lock-owner.js +259 -0
- package/dist/src/core/file-lock.d.ts +2 -7
- package/dist/src/core/file-lock.js +40 -91
- package/dist/src/core/firefox-cache.d.ts +13 -1
- package/dist/src/core/firefox-cache.js +59 -34
- package/dist/src/core/firefox-download.d.ts +15 -0
- package/dist/src/core/firefox-download.js +37 -21
- package/dist/src/core/firefox-extract.d.ts +6 -5
- package/dist/src/core/firefox-extract.js +36 -18
- package/dist/src/core/firefox.d.ts +30 -32
- package/dist/src/core/firefox.js +5 -36
- package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
- package/dist/src/core/furnace-apply-dry-run.js +1 -7
- package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
- package/dist/src/core/furnace-apply-helpers.js +8 -19
- package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +30 -10
- package/dist/src/core/furnace-config.d.ts +1 -1
- package/dist/src/core/furnace-config.js +23 -36
- package/dist/src/core/furnace-constants.js +3 -2
- package/dist/src/core/furnace-dir-entry.d.ts +20 -0
- package/dist/src/core/furnace-dir-entry.js +21 -0
- package/dist/src/core/furnace-operation.d.ts +2 -0
- package/dist/src/core/furnace-operation.js +3 -1
- package/dist/src/core/furnace-refresh.d.ts +18 -6
- package/dist/src/core/furnace-refresh.js +3 -6
- package/dist/src/core/furnace-registration-ast.d.ts +0 -1
- package/dist/src/core/furnace-registration-ast.js +0 -3
- package/dist/src/core/furnace-registration.d.ts +2 -1
- package/dist/src/core/furnace-registration.js +22 -29
- package/dist/src/core/furnace-scanner.js +3 -6
- package/dist/src/core/furnace-state-persist.d.ts +2 -2
- package/dist/src/core/furnace-validate-registration.js +5 -7
- package/dist/src/core/git-base.d.ts +3 -1
- package/dist/src/core/git-base.js +25 -3
- package/dist/src/core/git-diff-new-file.d.ts +40 -0
- package/dist/src/core/git-diff-new-file.js +119 -0
- package/dist/src/core/git-diff.d.ts +9 -9
- package/dist/src/core/git-diff.js +88 -157
- package/dist/src/core/git-file-ops.d.ts +0 -7
- package/dist/src/core/git-file-ops.js +4 -28
- package/dist/src/core/git-readonly-index.d.ts +0 -2
- package/dist/src/core/git-readonly-index.js +0 -4
- package/dist/src/core/git-status.d.ts +7 -0
- package/dist/src/core/git-status.js +21 -9
- package/dist/src/core/git.d.ts +0 -6
- package/dist/src/core/git.js +14 -43
- package/dist/src/core/harness-orphans.d.ts +73 -0
- package/dist/src/core/harness-orphans.js +187 -0
- package/dist/src/core/history-log.d.ts +19 -0
- package/dist/src/core/history-log.js +29 -0
- package/dist/src/core/mach-error-hints.d.ts +0 -5
- package/dist/src/core/mach-error-hints.js +1 -1
- package/dist/src/core/mach-known-noise-filter.d.ts +25 -3
- package/dist/src/core/mach-known-noise-filter.js +31 -10
- package/dist/src/core/mach-mozconfig.js +2 -1
- package/dist/src/core/mach-python.d.ts +2 -0
- package/dist/src/core/mach-python.js +12 -7
- package/dist/src/core/mach-resource-shim.d.ts +13 -1
- package/dist/src/core/mach-resource-shim.js +112 -9
- package/dist/src/core/mach.d.ts +46 -53
- package/dist/src/core/mach.js +31 -83
- package/dist/src/core/marionette-port.d.ts +0 -17
- package/dist/src/core/marionette-port.js +12 -58
- package/dist/src/core/marionette-preflight.js +9 -27
- package/dist/src/core/mochitest-server-port.js +3 -3
- package/dist/src/core/moz-manifest-rules.js +37 -33
- package/dist/src/core/patch-apply-fuzz.js +4 -4
- package/dist/src/core/patch-apply.d.ts +4 -5
- package/dist/src/core/patch-apply.js +55 -32
- package/dist/src/core/patch-export-coverage.d.ts +0 -9
- package/dist/src/core/patch-export-coverage.js +1 -30
- package/dist/src/core/patch-export-update.d.ts +18 -1
- package/dist/src/core/patch-export-update.js +4 -3
- package/dist/src/core/patch-export.d.ts +11 -21
- package/dist/src/core/patch-export.js +44 -66
- package/dist/src/core/patch-files.d.ts +0 -4
- package/dist/src/core/patch-files.js +0 -11
- package/dist/src/core/patch-lint-binary.d.ts +6 -12
- package/dist/src/core/patch-lint-cache.d.ts +17 -1
- package/dist/src/core/patch-lint-cache.js +3 -6
- package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
- package/dist/src/core/patch-lint-checkjs.js +43 -25
- package/dist/src/core/patch-lint-creators.d.ts +2 -6
- package/dist/src/core/patch-lint-cross.d.ts +16 -8
- package/dist/src/core/patch-lint-cross.js +49 -92
- package/dist/src/core/patch-lint-css.js +10 -6
- package/dist/src/core/patch-lint-file-size.d.ts +52 -0
- package/dist/src/core/patch-lint-file-size.js +53 -0
- package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
- package/dist/src/core/patch-lint-forward-registration.js +210 -0
- package/dist/src/core/patch-lint-jsdoc.js +36 -36
- package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
- package/dist/src/core/patch-lint-prettier.d.ts +36 -0
- package/dist/src/core/patch-lint-prettier.js +126 -0
- package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
- package/dist/src/core/patch-lint-queue-types.js +12 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
- package/dist/src/core/patch-lint-staged-registration.js +90 -0
- package/dist/src/core/patch-lint-unmanaged-companion.d.ts +106 -0
- package/dist/src/core/patch-lint-unmanaged-companion.js +214 -0
- package/dist/src/core/patch-lint.d.ts +14 -9
- package/dist/src/core/patch-lint.js +27 -27
- package/dist/src/core/patch-lock.js +3 -3
- package/dist/src/core/patch-manifest-io.js +8 -7
- package/dist/src/core/patch-parse.d.ts +19 -8
- package/dist/src/core/patch-parse.js +25 -0
- package/dist/src/core/patch-policy.js +15 -27
- package/dist/src/core/patch-source-metadata.d.ts +1 -3
- package/dist/src/core/patch-source-metadata.js +0 -4
- package/dist/src/core/patch-transform.d.ts +21 -8
- package/dist/src/core/patch-transform.js +38 -10
- package/dist/src/core/process-cpu-time.js +1 -1
- package/dist/src/core/rebase-session.d.ts +2 -8
- package/dist/src/core/rebase-session.js +5 -17
- package/dist/src/core/register-browser-content.js +3 -2
- package/dist/src/core/register-browser-test.js +2 -1
- package/dist/src/core/register-module.js +2 -1
- package/dist/src/core/register-scaffold.js +5 -4
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/run-log-redact.d.ts +38 -0
- package/dist/src/core/run-log-redact.js +69 -0
- package/dist/src/core/run-log.d.ts +13 -2
- package/dist/src/core/run-log.js +67 -18
- package/dist/src/core/signal-critical.js +2 -4
- package/dist/src/core/smoke-patterns.d.ts +0 -1
- package/dist/src/core/smoke-patterns.js +18 -11
- package/dist/src/core/state-file.d.ts +12 -1
- package/dist/src/core/state-file.js +14 -1
- package/dist/src/core/status-classify.js +2 -1
- package/dist/src/core/test-changed-prefs.js +26 -11
- package/dist/src/core/test-harness-crash.js +61 -48
- package/dist/src/core/test-harness-verdict-notes.d.ts +55 -0
- package/dist/src/core/test-harness-verdict-notes.js +48 -0
- package/dist/src/core/test-stale-check.d.ts +23 -8
- package/dist/src/core/test-stale-check.js +77 -4
- package/dist/src/core/test-stall-triage.js +37 -19
- package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
- package/dist/src/core/test-xpcshell-retry.js +14 -11
- package/dist/src/core/token-category.d.ts +33 -0
- package/dist/src/core/token-category.js +56 -48
- package/dist/src/core/token-dark-mode.js +2 -14
- package/dist/src/core/token-inventory.d.ts +56 -0
- package/dist/src/core/token-inventory.js +109 -0
- package/dist/src/core/token-manager.d.ts +1 -1
- package/dist/src/core/token-manager.js +1 -1
- package/dist/src/core/token-variant.js +21 -22
- package/dist/src/core/toolchain-preflight.js +1 -1
- package/dist/src/core/tree-cow.d.ts +0 -2
- package/dist/src/core/tree-cow.js +2 -2
- package/dist/src/core/tree-store.js +4 -5
- package/dist/src/core/typecheck.js +2 -2
- package/dist/src/core/wire-dom-fragment.js +6 -17
- package/dist/src/core/wire-utils.js +5 -17
- package/dist/src/core/xpcshell-appdir.js +2 -4
- package/dist/src/errors/base.d.ts +53 -1
- package/dist/src/errors/base.js +79 -11
- package/dist/src/errors/build.d.ts +28 -0
- package/dist/src/errors/build.js +51 -10
- package/dist/src/errors/config.d.ts +0 -14
- package/dist/src/errors/config.js +13 -34
- package/dist/src/errors/download.d.ts +13 -0
- package/dist/src/errors/download.js +53 -24
- package/dist/src/errors/furnace.js +8 -9
- package/dist/src/errors/git.d.ts +8 -8
- package/dist/src/errors/git.js +20 -30
- package/dist/src/errors/patch.js +8 -10
- package/dist/src/errors/rebase.js +7 -7
- package/dist/src/types/commands/options.d.ts +96 -123
- package/dist/src/types/commands/patches.d.ts +17 -6
- package/dist/src/types/config.d.ts +75 -2
- package/dist/src/types/index.d.ts +0 -1
- package/dist/src/utils/assert.d.ts +3 -15
- package/dist/src/utils/assert.js +1 -18
- package/dist/src/utils/build-info.d.ts +1 -1
- package/dist/src/utils/child-env.d.ts +2 -1
- package/dist/src/utils/elapsed.js +10 -1
- package/dist/src/utils/fs.d.ts +4 -2
- package/dist/src/utils/fs.js +11 -14
- package/dist/src/utils/hash.d.ts +12 -0
- package/dist/src/utils/hash.js +16 -0
- package/dist/src/utils/line-dispatch.d.ts +15 -0
- package/dist/src/utils/line-dispatch.js +48 -0
- package/dist/src/utils/options.d.ts +1 -7
- package/dist/src/utils/options.js +6 -7
- package/dist/src/utils/package-root.js +3 -12
- package/dist/src/utils/platform.d.ts +0 -4
- package/dist/src/utils/process-group.d.ts +2 -1
- package/dist/src/utils/process-group.js +20 -19
- package/dist/src/utils/process.d.ts +40 -26
- package/dist/src/utils/process.js +115 -166
- package/dist/src/utils/ps-duration.js +3 -3
- package/dist/src/utils/sleep.d.ts +26 -0
- package/dist/src/utils/sleep.js +31 -0
- package/dist/src/utils/validation.d.ts +2 -38
- package/dist/src/utils/validation.js +7 -16
- package/docs/configuration.md +136 -1
- package/docs/exit-codes.md +34 -20
- package/docs/lifecycle-invariants.md +18 -12
- package/docs/machine-output.md +31 -8
- package/docs/patch-workflow.md +2 -1
- package/docs/run-logs.md +44 -4
- package/docs/testing.md +70 -11
- package/package.json +8 -7
- package/dist/src/commands/rebase.d.ts +0 -4
- package/dist/src/commands/rebase.js +0 -5
- package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
- package/dist/src/core/patch-artifact-normalize.js +0 -24
- package/dist/src/core/patch-lint-reexports.d.ts +0 -7
- package/dist/src/core/patch-lint-reexports.js +0 -8
- package/dist/src/core/wire-targets.d.ts +0 -7
- package/dist/src/core/wire-targets.js +0 -8
|
@@ -32,29 +32,29 @@ const ATTRIBUTE_FRAGMENT_PATTERN = /^\[[a-zA-Z][a-zA-Z0-9_-]*(?:=(?:"[a-zA-Z0-9_
|
|
|
32
32
|
const QUALIFIER_PATTERN = /^:[a-zA-Z][a-zA-Z0-9-]*(?:\(([^()]*)\))?$/;
|
|
33
33
|
/**
|
|
34
34
|
* Splits a run of consecutive attribute fragments into its groups, or
|
|
35
|
-
* returns `
|
|
35
|
+
* returns `undefined` when the run is not exactly that — a group that does not
|
|
36
36
|
* open with `[`, an unclosed group, an empty run, or a group whose name or
|
|
37
37
|
* value is not identifier-safe.
|
|
38
38
|
*
|
|
39
39
|
* @param run - The `[…][…]` text with no `:root` prefix
|
|
40
|
-
* @returns The individual fragments, or
|
|
40
|
+
* @returns The individual fragments, or undefined when the run is not valid
|
|
41
41
|
*/
|
|
42
42
|
function splitAttributeGroups(run) {
|
|
43
43
|
const groups = [];
|
|
44
44
|
let cursor = 0;
|
|
45
45
|
while (cursor < run.length) {
|
|
46
46
|
if (run[cursor] !== '[')
|
|
47
|
-
return
|
|
47
|
+
return undefined;
|
|
48
48
|
const close = run.indexOf(']', cursor);
|
|
49
49
|
if (close === -1)
|
|
50
|
-
return
|
|
50
|
+
return undefined;
|
|
51
51
|
const group = run.slice(cursor, close + 1);
|
|
52
52
|
if (!ATTRIBUTE_FRAGMENT_PATTERN.test(group))
|
|
53
|
-
return
|
|
53
|
+
return undefined;
|
|
54
54
|
groups.push(group);
|
|
55
55
|
cursor = close + 1;
|
|
56
56
|
}
|
|
57
|
-
return groups.length > 0 ? groups :
|
|
57
|
+
return groups.length > 0 ? groups : undefined;
|
|
58
58
|
}
|
|
59
59
|
/** Rewrites `=value` / `='value'` to the double-quoted Mozilla convention. */
|
|
60
60
|
function normalizeAttributeFragment(group) {
|
|
@@ -96,10 +96,10 @@ export function validateVariantSelector(raw) {
|
|
|
96
96
|
// The parse must have consumed the WHOLE fragment: anything it declined to
|
|
97
97
|
// read (a stray space, a trailing combinator) is text the matcher would
|
|
98
98
|
// not see either, so accepting it would author a block nothing can find.
|
|
99
|
-
if (parts ===
|
|
99
|
+
if (parts === undefined || `${parts.attributes}${parts.qualifier}` !== `:root${value}`)
|
|
100
100
|
return reject;
|
|
101
101
|
const groups = splitAttributeGroups(parts.attributes.slice(':root'.length));
|
|
102
|
-
if (groups ===
|
|
102
|
+
if (groups === undefined)
|
|
103
103
|
return reject;
|
|
104
104
|
let qualifier = '';
|
|
105
105
|
if (parts.qualifier !== '') {
|
|
@@ -112,7 +112,7 @@ export function validateVariantSelector(raw) {
|
|
|
112
112
|
}
|
|
113
113
|
else {
|
|
114
114
|
const innerGroups = splitAttributeGroups(inner);
|
|
115
|
-
if (innerGroups ===
|
|
115
|
+
if (innerGroups === undefined)
|
|
116
116
|
return reject;
|
|
117
117
|
const name = parts.qualifier.slice(0, parts.qualifier.indexOf('('));
|
|
118
118
|
qualifier = `${name}(${innerGroups.map(normalizeAttributeFragment).join('')})`;
|
|
@@ -131,7 +131,7 @@ function canonicalSelector(selector) {
|
|
|
131
131
|
}
|
|
132
132
|
/**
|
|
133
133
|
* Splits the `:root[…]` selector on `line` into its attribute run and any
|
|
134
|
-
* trailing qualifier, or `
|
|
134
|
+
* trailing qualifier, or `undefined` when the line carries none.
|
|
135
135
|
*
|
|
136
136
|
* Index arithmetic rather than `/:root\[[^{]*\]/`: `[^{]` also matches `]`,
|
|
137
137
|
* so that pattern backtracks quadratically on a line repeating `:root[`
|
|
@@ -155,7 +155,7 @@ function canonicalSelector(selector) {
|
|
|
155
155
|
function rootAttributeSelector(line) {
|
|
156
156
|
const start = line.indexOf(':root[');
|
|
157
157
|
if (start === -1)
|
|
158
|
-
return
|
|
158
|
+
return undefined;
|
|
159
159
|
const brace = line.indexOf('{', start);
|
|
160
160
|
const limit = brace === -1 ? line.length : brace;
|
|
161
161
|
let cursor = start + ':root'.length;
|
|
@@ -168,13 +168,13 @@ function rootAttributeSelector(line) {
|
|
|
168
168
|
end = cursor;
|
|
169
169
|
}
|
|
170
170
|
if (end === start + ':root'.length)
|
|
171
|
-
return
|
|
171
|
+
return undefined;
|
|
172
172
|
return { attributes: line.slice(start, end), qualifier: line.slice(end, limit).trim() };
|
|
173
173
|
}
|
|
174
174
|
/**
|
|
175
175
|
* Finds the top-level `:root<variant>` block whose attribute selector
|
|
176
176
|
* matches `variant` (compared canonically). Returns the opening-brace and
|
|
177
|
-
* closing-brace line indices, or `
|
|
177
|
+
* closing-brace line indices, or `undefined` when no such block exists.
|
|
178
178
|
*
|
|
179
179
|
* Scans a comment-stripped mirror of `lines` so braces inside comments do
|
|
180
180
|
* not offset the depth counter; the returned indices line up with the
|
|
@@ -189,10 +189,9 @@ function findVariantBlock(lines, variant) {
|
|
|
189
189
|
const requested = rootAttributeSelector(`:root${variant} {`);
|
|
190
190
|
const want = canonicalSelector(requested?.attributes ?? `:root${variant}`);
|
|
191
191
|
const wantQualifier = canonicalSelector(requested?.qualifier ?? '');
|
|
192
|
-
for (
|
|
193
|
-
const line = stripped[i] ?? '';
|
|
192
|
+
for (const [i, line] of stripped.entries()) {
|
|
194
193
|
const selector = rootAttributeSelector(line);
|
|
195
|
-
if (selector ===
|
|
194
|
+
if (selector === undefined || canonicalSelector(selector.attributes) !== want)
|
|
196
195
|
continue;
|
|
197
196
|
// An UNqualified request still matches a qualified block: that is the
|
|
198
197
|
// 0.44.2 behaviour `variantBlockQualifier` exists to report, since
|
|
@@ -219,7 +218,7 @@ function findVariantBlock(lines, variant) {
|
|
|
219
218
|
continue;
|
|
220
219
|
return { open: openLine, close, qualifier: selector.qualifier };
|
|
221
220
|
}
|
|
222
|
-
return
|
|
221
|
+
return undefined;
|
|
223
222
|
}
|
|
224
223
|
/**
|
|
225
224
|
* Returns the line index at which a brand-new `:root<variant>` block should
|
|
@@ -229,8 +228,8 @@ function findVariantBlock(lines, variant) {
|
|
|
229
228
|
*/
|
|
230
229
|
function findVariantBlockInsertionPoint(lines) {
|
|
231
230
|
const stripped = stripBlockCommentsInLines(lines);
|
|
232
|
-
for (
|
|
233
|
-
if (/^\s*:root\s*\{/.test(
|
|
231
|
+
for (const [i, line] of stripped.entries()) {
|
|
232
|
+
if (/^\s*:root\s*\{/.test(line)) {
|
|
234
233
|
const close = findBlockCloseIndex(stripped, i);
|
|
235
234
|
if (close !== -1)
|
|
236
235
|
return close + 1;
|
|
@@ -245,7 +244,7 @@ function findVariantBlockInsertionPoint(lines) {
|
|
|
245
244
|
* the block, which is only wanted for the explicit `--variant` flow.
|
|
246
245
|
*/
|
|
247
246
|
export function variantBlockExists(lines, variant) {
|
|
248
|
-
return findVariantBlock(lines, variant) !==
|
|
247
|
+
return findVariantBlock(lines, variant) !== undefined;
|
|
249
248
|
}
|
|
250
249
|
/**
|
|
251
250
|
* The pseudo-class qualifier on the matched `:root<variant>` block, or
|
|
@@ -300,8 +299,8 @@ export function findVariantBlockDeclaration(lines, variant, tokenName) {
|
|
|
300
299
|
.join('\n')
|
|
301
300
|
.replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, ' '))
|
|
302
301
|
.split('\n');
|
|
303
|
-
for (
|
|
304
|
-
if (
|
|
302
|
+
for (const [i, line] of stripped.entries()) {
|
|
303
|
+
if (line.includes(`${tokenName}:`))
|
|
305
304
|
return { line: block.open + i + 1 };
|
|
306
305
|
}
|
|
307
306
|
return undefined;
|
|
@@ -121,7 +121,7 @@ export function formatMajorVersionHopNotice(previousVersion, newVersion) {
|
|
|
121
121
|
return undefined;
|
|
122
122
|
if (previousMajor === newMajor)
|
|
123
123
|
return undefined;
|
|
124
|
-
return (`Firefox major version changed (${
|
|
124
|
+
return (`Firefox major version changed (${previousMajor} → ${newMajor}): ` +
|
|
125
125
|
'upstream toolchain minimums (cbindgen, Rust, …) may have moved with it. ' +
|
|
126
126
|
'Consider running "fireforge bootstrap" before the next build.');
|
|
127
127
|
}
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
export type CloneExecutor = (command: string, args: string[]) => Promise<void>;
|
|
3
3
|
/** How this host can materialise a tree. */
|
|
4
4
|
export type CowCapability = 'clonefile' | 'reflink' | 'none';
|
|
5
|
-
/** Per-platform `cp` argv prefix for a CoW copy of one entry. */
|
|
6
|
-
export declare function cowCopyArgs(capability: Exclude<CowCapability, 'none'>): string[];
|
|
7
5
|
/**
|
|
8
6
|
* Probes CoW support in `dir` by cloning a tiny probe file there.
|
|
9
7
|
* Non-darwin/linux platforms report 'none' without probing.
|
|
@@ -20,11 +20,11 @@ import { exec } from '../utils/process.js';
|
|
|
20
20
|
const defaultExecutor = async (command, args) => {
|
|
21
21
|
const result = await exec(command, args);
|
|
22
22
|
if (result.exitCode !== 0) {
|
|
23
|
-
throw new Error(`${command} ${args.join(' ')} failed (exit ${
|
|
23
|
+
throw new Error(`${command} ${args.join(' ')} failed (exit ${result.exitCode}): ${result.stderr}`);
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
/** Per-platform `cp` argv prefix for a CoW copy of one entry. */
|
|
27
|
-
|
|
27
|
+
function cowCopyArgs(capability) {
|
|
28
28
|
return capability === 'clonefile' ? ['-c', '-R', '-p'] : ['--reflink=always', '-a'];
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
@@ -19,6 +19,7 @@ import { GeneralError } from '../errors/base.js';
|
|
|
19
19
|
import { mapWithConcurrency } from '../utils/concurrency.js';
|
|
20
20
|
import { getNodeErrorCode, isProcessAlive, toError } from '../utils/errors.js';
|
|
21
21
|
import { pathExists, readJson, writeJson } from '../utils/fs.js';
|
|
22
|
+
import { sha256Hex } from '../utils/hash.js';
|
|
22
23
|
import { verbose, warn } from '../utils/logger.js';
|
|
23
24
|
import { isObject } from '../utils/validation.js';
|
|
24
25
|
import { getBuildBaselinePath } from './build-baseline.js';
|
|
@@ -128,7 +129,7 @@ export async function readTreeMarker(root) {
|
|
|
128
129
|
const code = getNodeErrorCode(error);
|
|
129
130
|
if (code === 'ENOENT' || code === 'ENOTDIR')
|
|
130
131
|
return { kind: 'absent' };
|
|
131
|
-
verbose(`Unreadable tree marker at ${markerPath}: ${
|
|
132
|
+
verbose(`Unreadable tree marker at ${markerPath}: ${toError(error).message}`);
|
|
132
133
|
return { kind: 'corrupt', reason: `the marker could not be read (${toError(error).message})` };
|
|
133
134
|
}
|
|
134
135
|
}
|
|
@@ -178,9 +179,7 @@ export async function computePrimaryFingerprint(primaryRoot) {
|
|
|
178
179
|
}
|
|
179
180
|
let engineFingerprint;
|
|
180
181
|
try {
|
|
181
|
-
engineFingerprint =
|
|
182
|
-
.update(await getAllDiff(engineDir))
|
|
183
|
-
.digest('hex');
|
|
182
|
+
engineFingerprint = sha256Hex(await getAllDiff(engineDir));
|
|
184
183
|
}
|
|
185
184
|
catch {
|
|
186
185
|
engineFingerprint = null;
|
|
@@ -461,7 +460,7 @@ export async function removeTree(primaryRoot, name, options = {}) {
|
|
|
461
460
|
]) {
|
|
462
461
|
const state = await inspectTreeLock(lockPath);
|
|
463
462
|
if (state.kind === 'held') {
|
|
464
|
-
throw new GeneralError(`Refusing to remove tree "${name}": a live process (pid ${
|
|
463
|
+
throw new GeneralError(`Refusing to remove tree "${name}": a live process (pid ${state.pid}) holds ${lockPath}. ` +
|
|
465
464
|
'Wait for it to finish, then retry.');
|
|
466
465
|
}
|
|
467
466
|
if (state.kind === 'unknown' && options.force !== true) {
|
|
@@ -144,7 +144,7 @@ export async function runTypecheck(projectRoot, cfg) {
|
|
|
144
144
|
*/
|
|
145
145
|
function resolveProjectExtraShim(cfg, projectPath) {
|
|
146
146
|
const overrides = cfg.projectOverrides;
|
|
147
|
-
if (overrides && Object.
|
|
147
|
+
if (overrides && Object.hasOwn(overrides, projectPath)) {
|
|
148
148
|
const value = overrides[projectPath];
|
|
149
149
|
return value === null ? undefined : value;
|
|
150
150
|
}
|
|
@@ -298,7 +298,7 @@ async function runTypecheckForProject(ts, projectRoot, projectPath, shimSource,
|
|
|
298
298
|
}
|
|
299
299
|
issues.push(diagnosticToIssue(ts, diag, absConfig, projectPath));
|
|
300
300
|
}
|
|
301
|
-
verbose(`typecheck: ${projectPath} — analyzed ${
|
|
301
|
+
verbose(`typecheck: ${projectPath} — analyzed ${parsed.fileNames.length} file(s), found ${issues.length} issue(s)`);
|
|
302
302
|
return {
|
|
303
303
|
project: projectPath,
|
|
304
304
|
issues,
|
|
@@ -12,6 +12,7 @@ import { dirname, join, relative } from 'node:path';
|
|
|
12
12
|
import { GeneralError } from '../errors/base.js';
|
|
13
13
|
import { pathExists, readText, writeText } from '../utils/fs.js';
|
|
14
14
|
import { toRootRelativePath } from '../utils/paths.js';
|
|
15
|
+
import { normalizePathSlashes } from '../utils/paths.js';
|
|
15
16
|
import { escapeRegex } from '../utils/regex.js';
|
|
16
17
|
import { tokenizeXhtml } from './wire-utils.js';
|
|
17
18
|
export const DEFAULT_DOM_TARGET = 'browser/base/content/browser.xhtml';
|
|
@@ -22,23 +23,11 @@ export function addDomFragmentTokenized(content, includeDirective) {
|
|
|
22
23
|
const lines = content.split('\n');
|
|
23
24
|
const tokens = tokenizeXhtml(lines);
|
|
24
25
|
// Find the #include browser-sets.inc token
|
|
25
|
-
let insertIndex = -
|
|
26
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
27
|
-
const token = tokens[i];
|
|
28
|
-
if (token && token.type === 'macro' && token.raw.includes('browser-sets.inc')) {
|
|
29
|
-
insertIndex = i;
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
26
|
+
let insertIndex = tokens.findIndex((token) => token.type === 'macro' && token.raw.includes('browser-sets.inc'));
|
|
33
27
|
if (insertIndex === -1) {
|
|
34
28
|
// Fallback: after <html:body>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (token && token.type === 'xml' && /<html:body/.test(token.raw)) {
|
|
38
|
-
insertIndex = i + 1;
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
29
|
+
const bodyIndex = tokens.findIndex((token) => token.type === 'xml' && /<html:body/.test(token.raw));
|
|
30
|
+
insertIndex = bodyIndex === -1 ? -1 : bodyIndex + 1;
|
|
42
31
|
}
|
|
43
32
|
if (insertIndex === -1) {
|
|
44
33
|
throw new GeneralError('Could not find insertion point in chrome document');
|
|
@@ -69,7 +58,7 @@ export async function probeDomFragmentInsertionPoint(engineDir, domFilePath, tar
|
|
|
69
58
|
}
|
|
70
59
|
const safeDomFilePath = toRootRelativePath(engineDir, domFilePath);
|
|
71
60
|
const targetDir = dirname(targetPath);
|
|
72
|
-
const includePath = relative(targetDir, safeDomFilePath)
|
|
61
|
+
const includePath = normalizePathSlashes(relative(targetDir, safeDomFilePath));
|
|
73
62
|
const includeDirective = `#include ${includePath}`;
|
|
74
63
|
const content = await readText(targetAbsPath);
|
|
75
64
|
if (new RegExp(`^${escapeRegex(includeDirective)}$`, 'm').test(content)) {
|
|
@@ -109,7 +98,7 @@ export async function addDomFragment(engineDir, domFilePath, targetPath = DEFAUL
|
|
|
109
98
|
// contains it, so this must track the chrome doc's location, not a
|
|
110
99
|
// hardcoded `browser/base/content/`.
|
|
111
100
|
const targetDir = dirname(targetPath);
|
|
112
|
-
const includePath = relative(targetDir, safeDomFilePath)
|
|
101
|
+
const includePath = normalizePathSlashes(relative(targetDir, safeDomFilePath));
|
|
113
102
|
const includeDirective = `#include ${includePath}`;
|
|
114
103
|
let content = await readText(targetAbsPath);
|
|
115
104
|
// Idempotency: check if the #include directive already exists (line-anchored to avoid substring matches)
|
|
@@ -160,7 +160,7 @@ export function tokenizeXhtml(lines) {
|
|
|
160
160
|
}
|
|
161
161
|
// ---------------------------------------------------------------------------
|
|
162
162
|
// Legacy (line-based) helpers — shared by fallback implementations in
|
|
163
|
-
// wire
|
|
163
|
+
// the wire target modules. Extracted to reduce duplication and make the brace-
|
|
164
164
|
// walking logic independently testable.
|
|
165
165
|
// ---------------------------------------------------------------------------
|
|
166
166
|
/**
|
|
@@ -179,24 +179,12 @@ export function tokenizeXhtml(lines) {
|
|
|
179
179
|
* found (or, under `requireBrace`, no brace follows the signature).
|
|
180
180
|
*/
|
|
181
181
|
export function findMethodBraceIndex(lines, pattern, options) {
|
|
182
|
-
|
|
183
|
-
for (let i = 0; i < lines.length; i++) {
|
|
184
|
-
if (pattern.test(lines[i] ?? '')) {
|
|
185
|
-
methodLine = i;
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
182
|
+
const methodLine = lines.findIndex((line) => pattern.test(line));
|
|
189
183
|
if (methodLine === -1)
|
|
190
184
|
return null;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (lines[i]?.includes('{')) {
|
|
195
|
-
braceIndex = i;
|
|
196
|
-
braceFound = true;
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
185
|
+
const braceOffset = lines.slice(methodLine).findIndex((line) => line.includes('{'));
|
|
186
|
+
const braceFound = braceOffset !== -1;
|
|
187
|
+
const braceIndex = braceFound ? methodLine + braceOffset : methodLine;
|
|
200
188
|
if (!braceFound && options?.requireBrace) {
|
|
201
189
|
return null;
|
|
202
190
|
}
|
|
@@ -64,8 +64,7 @@ export function parseAppdirFromToml(tomlText, key) {
|
|
|
64
64
|
// comment further along the file is not mistaken for the directive.
|
|
65
65
|
const escapedKey = escapeRegex(key);
|
|
66
66
|
const keyPattern = new RegExp('^\\s*' + escapedKey + '\\s*=\\s*(.+?)\\s*(?:[#;].*)?$');
|
|
67
|
-
for (
|
|
68
|
-
const line = lines[i] ?? '';
|
|
67
|
+
for (const [i, line] of lines.entries()) {
|
|
69
68
|
const sectionMatch = /^\s*\[([^\]]+)\]\s*$/.exec(line);
|
|
70
69
|
if (sectionMatch) {
|
|
71
70
|
sectionSeen = true;
|
|
@@ -290,8 +289,7 @@ export async function resolveXpcshellAppdirArg(engineDir, testPaths, objDirName)
|
|
|
290
289
|
* wins.
|
|
291
290
|
*/
|
|
292
291
|
export function operatorAlreadySetAppPath(extraArgs) {
|
|
293
|
-
for (
|
|
294
|
-
const arg = extraArgs[i] ?? '';
|
|
292
|
+
for (const [i, arg] of extraArgs.entries()) {
|
|
295
293
|
if (arg === '--app-path' && i + 1 < extraArgs.length)
|
|
296
294
|
return true;
|
|
297
295
|
if (arg.startsWith('--app-path='))
|
|
@@ -19,12 +19,45 @@ export declare abstract class FireForgeError extends Error {
|
|
|
19
19
|
*/
|
|
20
20
|
get userMessage(): string;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Renders the trailing "To fix this:" block shared by every FireForge error
|
|
24
|
+
* that offers remedies.
|
|
25
|
+
*
|
|
26
|
+
* Numbering is derived from the array rather than written into each string.
|
|
27
|
+
* Several classes vary their advice by context (`FurnaceError` drops the
|
|
28
|
+
* validate step, `ChecksumMismatchError` adds a Developer Edition note), and
|
|
29
|
+
* hand-numbered branches had to renumber every later step by hand — one
|
|
30
|
+
* conditional away from printing "2." twice.
|
|
31
|
+
*
|
|
32
|
+
* @param steps - The remedy lines, in order, without their numbers
|
|
33
|
+
* @returns The block, starting with the blank-line separator
|
|
34
|
+
*/
|
|
35
|
+
export declare function remedies(steps: readonly string[]): string;
|
|
22
36
|
/**
|
|
23
37
|
* General error for unexpected failures.
|
|
24
38
|
*/
|
|
25
39
|
export declare class GeneralError extends FireForgeError {
|
|
26
40
|
readonly code: 1;
|
|
27
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* A `fireforge test` preflight gate that refused before any harness ran.
|
|
44
|
+
*
|
|
45
|
+
* Exists to carry a `note`, not to change behaviour: it keeps
|
|
46
|
+
* {@link ExitCode.GENERAL_ERROR}, so `docs/exit-codes.md` and every CI
|
|
47
|
+
* consumer keying on the exit status are untouched. The note is a stable
|
|
48
|
+
* kebab-case refusal class that the `FIREFORGE-VERDICT:` line reports as an
|
|
49
|
+
* additive `note=` key. Without it every preflight refusal reached an
|
|
50
|
+
* unattended reader as the single token `reason=preflight`, which covers
|
|
51
|
+
* everything from a missing build to a peer's packaging record — so the one
|
|
52
|
+
* line that survives a truncating pipe could not say WHICH gate fired.
|
|
53
|
+
*/
|
|
54
|
+
export declare class PreflightRefusalError extends GeneralError {
|
|
55
|
+
/** Stable refusal class, e.g. `stale-browser`. */
|
|
56
|
+
readonly note: string;
|
|
57
|
+
constructor(message: string,
|
|
58
|
+
/** Stable refusal class, e.g. `stale-browser`. */
|
|
59
|
+
note: string);
|
|
60
|
+
}
|
|
28
61
|
/**
|
|
29
62
|
* Raised when one of FireForge's own internal invariants did not hold —
|
|
30
63
|
* the failure of a runtime assertion from `src/utils/assert.ts`.
|
|
@@ -92,14 +125,33 @@ export declare class ExecTimeoutError extends FireForgeError {
|
|
|
92
125
|
/** Timeout that elapsed, in milliseconds. */
|
|
93
126
|
readonly timeoutMs: number;
|
|
94
127
|
readonly code: 1;
|
|
128
|
+
/** Standard output captured before the deadline fired (empty when the exec path does not capture). */
|
|
129
|
+
readonly stdout: string;
|
|
130
|
+
/** Standard error captured before the deadline fired (empty when the exec path does not capture). */
|
|
131
|
+
readonly stderr: string;
|
|
132
|
+
/** True when either captured stream was cut off at the exec layer's collector cap. */
|
|
133
|
+
readonly outputTruncated: boolean;
|
|
95
134
|
constructor(
|
|
96
135
|
/** Executable that was spawned (argv[0]). */
|
|
97
136
|
command: string,
|
|
98
137
|
/** Arguments the executable was spawned with. */
|
|
99
138
|
args: readonly string[],
|
|
100
139
|
/** Timeout that elapsed, in milliseconds. */
|
|
101
|
-
timeoutMs: number, cause?: unknown);
|
|
140
|
+
timeoutMs: number, cause?: unknown, output?: ExecTimeoutOutput);
|
|
102
141
|
get userMessage(): string;
|
|
142
|
+
/**
|
|
143
|
+
* The last few lines the command printed before it was killed, so an
|
|
144
|
+
* operator can see what it was doing (a `git add -A` stuck on one path,
|
|
145
|
+
* a build step that hung) instead of a bare budget number. Prefers
|
|
146
|
+
* stderr; falls back to stdout when stderr is silent.
|
|
147
|
+
*/
|
|
148
|
+
private outputTailForUser;
|
|
149
|
+
}
|
|
150
|
+
/** Output captured before an exec timeout fired; see {@link ExecTimeoutError}. */
|
|
151
|
+
export interface ExecTimeoutOutput {
|
|
152
|
+
stdout: string;
|
|
153
|
+
stderr: string;
|
|
154
|
+
truncated: boolean;
|
|
103
155
|
}
|
|
104
156
|
/**
|
|
105
157
|
* Error thrown when a file-lock wait times out because another process holds
|
package/dist/src/errors/base.js
CHANGED
|
@@ -17,10 +17,11 @@ export class FireForgeError extends Error {
|
|
|
17
17
|
if (cause !== undefined) {
|
|
18
18
|
this.cause = cause;
|
|
19
19
|
}
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
// Trims the constructor frames off the stack. `engines` pins Node >= 22,
|
|
21
|
+
// so the V8-only `captureStackTrace` is always present — the former
|
|
22
|
+
// `typeof === 'function'` guard was an unreachable branch that coverage
|
|
23
|
+
// could never close.
|
|
24
|
+
Error.captureStackTrace(this, this.constructor);
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
27
|
* User-friendly error message with context and suggested fixes.
|
|
@@ -30,12 +31,49 @@ export class FireForgeError extends Error {
|
|
|
30
31
|
return this.message;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Renders the trailing "To fix this:" block shared by every FireForge error
|
|
36
|
+
* that offers remedies.
|
|
37
|
+
*
|
|
38
|
+
* Numbering is derived from the array rather than written into each string.
|
|
39
|
+
* Several classes vary their advice by context (`FurnaceError` drops the
|
|
40
|
+
* validate step, `ChecksumMismatchError` adds a Developer Edition note), and
|
|
41
|
+
* hand-numbered branches had to renumber every later step by hand — one
|
|
42
|
+
* conditional away from printing "2." twice.
|
|
43
|
+
*
|
|
44
|
+
* @param steps - The remedy lines, in order, without their numbers
|
|
45
|
+
* @returns The block, starting with the blank-line separator
|
|
46
|
+
*/
|
|
47
|
+
export function remedies(steps) {
|
|
48
|
+
return `\n\nTo fix this:\n${steps.map((step, index) => ` ${index + 1}. ${step}`).join('\n')}`;
|
|
49
|
+
}
|
|
33
50
|
/**
|
|
34
51
|
* General error for unexpected failures.
|
|
35
52
|
*/
|
|
36
53
|
export class GeneralError extends FireForgeError {
|
|
37
54
|
code = ExitCode.GENERAL_ERROR;
|
|
38
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* A `fireforge test` preflight gate that refused before any harness ran.
|
|
58
|
+
*
|
|
59
|
+
* Exists to carry a `note`, not to change behaviour: it keeps
|
|
60
|
+
* {@link ExitCode.GENERAL_ERROR}, so `docs/exit-codes.md` and every CI
|
|
61
|
+
* consumer keying on the exit status are untouched. The note is a stable
|
|
62
|
+
* kebab-case refusal class that the `FIREFORGE-VERDICT:` line reports as an
|
|
63
|
+
* additive `note=` key. Without it every preflight refusal reached an
|
|
64
|
+
* unattended reader as the single token `reason=preflight`, which covers
|
|
65
|
+
* everything from a missing build to a peer's packaging record — so the one
|
|
66
|
+
* line that survives a truncating pipe could not say WHICH gate fired.
|
|
67
|
+
*/
|
|
68
|
+
export class PreflightRefusalError extends GeneralError {
|
|
69
|
+
note;
|
|
70
|
+
constructor(message,
|
|
71
|
+
/** Stable refusal class, e.g. `stale-browser`. */
|
|
72
|
+
note) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.note = note;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
39
77
|
/**
|
|
40
78
|
* Raised when one of FireForge's own internal invariants did not hold —
|
|
41
79
|
* the failure of a runtime assertion from `src/utils/assert.ts`.
|
|
@@ -102,11 +140,9 @@ export class InvalidArgumentError extends FireForgeError {
|
|
|
102
140
|
this.argument = argument;
|
|
103
141
|
}
|
|
104
142
|
get userMessage() {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
return msg;
|
|
143
|
+
return this.argument
|
|
144
|
+
? `Invalid Argument: ${this.message}\n\nArgument: ${this.argument}`
|
|
145
|
+
: `Invalid Argument: ${this.message}`;
|
|
110
146
|
}
|
|
111
147
|
}
|
|
112
148
|
/**
|
|
@@ -123,24 +159,56 @@ export class ExecTimeoutError extends FireForgeError {
|
|
|
123
159
|
args;
|
|
124
160
|
timeoutMs;
|
|
125
161
|
code = ExitCode.GENERAL_ERROR;
|
|
162
|
+
/** Standard output captured before the deadline fired (empty when the exec path does not capture). */
|
|
163
|
+
stdout;
|
|
164
|
+
/** Standard error captured before the deadline fired (empty when the exec path does not capture). */
|
|
165
|
+
stderr;
|
|
166
|
+
/** True when either captured stream was cut off at the exec layer's collector cap. */
|
|
167
|
+
outputTruncated;
|
|
126
168
|
constructor(
|
|
127
169
|
/** Executable that was spawned (argv[0]). */
|
|
128
170
|
command,
|
|
129
171
|
/** Arguments the executable was spawned with. */
|
|
130
172
|
args,
|
|
131
173
|
/** Timeout that elapsed, in milliseconds. */
|
|
132
|
-
timeoutMs, cause) {
|
|
174
|
+
timeoutMs, cause, output) {
|
|
133
175
|
super(`Command timed out after ${Math.round(timeoutMs / 1000)}s: ${[command, ...args].join(' ')}`, cause);
|
|
134
176
|
this.command = command;
|
|
135
177
|
this.args = args;
|
|
136
178
|
this.timeoutMs = timeoutMs;
|
|
179
|
+
this.stdout = output?.stdout ?? '';
|
|
180
|
+
this.stderr = output?.stderr ?? '';
|
|
181
|
+
this.outputTruncated = output?.truncated ?? false;
|
|
137
182
|
}
|
|
138
183
|
get userMessage() {
|
|
139
184
|
return (`${this.message}\n\n` +
|
|
140
185
|
'The command was killed because it exceeded its time budget, not because it failed on its own. ' +
|
|
141
|
-
'If the host is just slow or loaded, re-running may succeed.'
|
|
186
|
+
'If the host is just slow or loaded, re-running may succeed.' +
|
|
187
|
+
this.outputTailForUser());
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The last few lines the command printed before it was killed, so an
|
|
191
|
+
* operator can see what it was doing (a `git add -A` stuck on one path,
|
|
192
|
+
* a build step that hung) instead of a bare budget number. Prefers
|
|
193
|
+
* stderr; falls back to stdout when stderr is silent.
|
|
194
|
+
*/
|
|
195
|
+
outputTailForUser() {
|
|
196
|
+
const source = this.stderr.trim().length > 0 ? this.stderr : this.stdout;
|
|
197
|
+
const lines = source
|
|
198
|
+
.trimEnd()
|
|
199
|
+
.split(/\r?\n/)
|
|
200
|
+
.filter((line) => line.length > 0);
|
|
201
|
+
if (lines.length === 0)
|
|
202
|
+
return '';
|
|
203
|
+
const tail = lines.slice(-EXEC_TIMEOUT_TAIL_LINES);
|
|
204
|
+
const label = this.stderr.trim().length > 0 ? 'stderr' : 'stdout';
|
|
205
|
+
const omitted = lines.length > tail.length ? ` (last ${tail.length} of ${lines.length} lines)` : '';
|
|
206
|
+
const truncatedNote = this.outputTruncated ? ' [captured output was truncated]' : '';
|
|
207
|
+
return `\n\nOutput before the timeout (${label}${omitted})${truncatedNote}:\n${tail.map((line) => ` ${line}`).join('\n')}`;
|
|
142
208
|
}
|
|
143
209
|
}
|
|
210
|
+
/** How many trailing output lines {@link ExecTimeoutError.userMessage} shows. */
|
|
211
|
+
const EXEC_TIMEOUT_TAIL_LINES = 20;
|
|
144
212
|
/**
|
|
145
213
|
* Error thrown when a file-lock wait times out because another process holds
|
|
146
214
|
* the lock.
|
|
@@ -9,6 +9,34 @@ export declare class BuildError extends FireForgeError {
|
|
|
9
9
|
constructor(message: string, command?: string | undefined, cause?: Error);
|
|
10
10
|
get userMessage(): string;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* A test SUITE went red — assertions failed, or sharded runs did not all
|
|
14
|
+
* pass. Distinct from its {@link BuildError} parent only in what it TELLS
|
|
15
|
+
* the operator, deliberately not in its exit code: `docs/exit-codes.md`
|
|
16
|
+
* documents 5 as "a suite that FAILED", and consumers key CI on it.
|
|
17
|
+
*
|
|
18
|
+
* The parent's remedies are all build remedies — "check the build output",
|
|
19
|
+
* "fireforge bootstrap", "delete obj-* directories" — and none of them apply
|
|
20
|
+
* to a failing assertion. Worse, the first points at the wrong half of the
|
|
21
|
+
* log, which is exactly the half a `tail` keeps. Ground truth for a test red
|
|
22
|
+
* is the harness's own output, so the remedies here name the failing tests,
|
|
23
|
+
* the `FIREFORGE-VERDICT:` line, and the run log that survives a pipe.
|
|
24
|
+
*
|
|
25
|
+
* Note that `machineErrorCode` derives the `--json` error code from the class
|
|
26
|
+
* name, so this reports as `test-failure` rather than `build`.
|
|
27
|
+
*/
|
|
28
|
+
export declare class TestFailureError extends BuildError {
|
|
29
|
+
/** Rendered TEST-UNEXPECTED blocks, when the verdict carried any. */
|
|
30
|
+
readonly failureBlocks?: string | undefined;
|
|
31
|
+
/** The run's complete raw log, when one was opened. */
|
|
32
|
+
readonly logPath?: string | undefined;
|
|
33
|
+
constructor(message: string, command?: string,
|
|
34
|
+
/** Rendered TEST-UNEXPECTED blocks, when the verdict carried any. */
|
|
35
|
+
failureBlocks?: string | undefined,
|
|
36
|
+
/** The run's complete raw log, when one was opened. */
|
|
37
|
+
logPath?: string | undefined);
|
|
38
|
+
get userMessage(): string;
|
|
39
|
+
}
|
|
12
40
|
/**
|
|
13
41
|
* Error thrown when mach is not available.
|
|
14
42
|
*/
|