@hominis/fireforge 0.43.1 → 0.44.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 +126 -0
- package/README.md +10 -1
- package/dist/bin/fireforge.d.ts +4 -9
- package/dist/bin/fireforge.js +42 -40
- package/dist/build-info.json +4 -4
- package/dist/src/cli.js +74 -8
- package/dist/src/commands/bootstrap.js +17 -20
- package/dist/src/commands/build.js +25 -32
- package/dist/src/commands/config.js +34 -41
- package/dist/src/commands/discard.js +8 -9
- package/dist/src/commands/doctor-check-core.d.ts +4 -18
- package/dist/src/commands/doctor-check-core.js +8 -35
- package/dist/src/commands/doctor-external-toolchains.js +2 -2
- package/dist/src/commands/doctor-furnace-config-sync.d.ts +18 -0
- package/dist/src/commands/{doctor-furnace-manifest-sync.js → doctor-furnace-config-sync.js} +27 -26
- package/dist/src/commands/doctor-furnace-jar.d.ts +7 -9
- package/dist/src/commands/doctor-furnace-jar.js +7 -9
- package/dist/src/commands/doctor-furnace.js +21 -24
- package/dist/src/commands/doctor-orphaned-harness.d.ts +16 -20
- package/dist/src/commands/doctor-orphaned-harness.js +18 -44
- package/dist/src/commands/doctor-post-rebase-audit.d.ts +2 -0
- package/dist/src/commands/{doctor/post-rebase-audit.js → doctor-post-rebase-audit.js} +3 -3
- package/dist/src/commands/doctor-working-tree.d.ts +10 -12
- package/dist/src/commands/doctor-working-tree.js +11 -18
- package/dist/src/commands/doctor.js +35 -53
- package/dist/src/commands/download.js +32 -48
- package/dist/src/commands/export-all.js +29 -33
- package/dist/src/commands/export-flow.js +6 -6
- package/dist/src/commands/export-placement-conflicts.js +10 -9
- package/dist/src/commands/export-placement-gate.js +5 -5
- package/dist/src/commands/export-shared.d.ts +30 -33
- package/dist/src/commands/export-shared.js +45 -43
- package/dist/src/commands/export.js +21 -27
- package/dist/src/commands/furnace/apply.js +14 -23
- package/dist/src/commands/furnace/chrome-doc-remove.js +14 -21
- package/dist/src/commands/furnace/chrome-doc-templates.d.ts +23 -23
- package/dist/src/commands/furnace/chrome-doc-templates.js +23 -23
- package/dist/src/commands/furnace/chrome-doc-tests.js +26 -29
- package/dist/src/commands/furnace/chrome-doc.d.ts +16 -17
- package/dist/src/commands/furnace/chrome-doc.js +31 -41
- package/dist/src/commands/furnace/create-browser-test.d.ts +4 -4
- package/dist/src/commands/furnace/create-browser-test.js +8 -8
- package/dist/src/commands/furnace/create-mochikit.d.ts +6 -7
- package/dist/src/commands/furnace/create-mochikit.js +8 -9
- package/dist/src/commands/furnace/create-readback.d.ts +12 -13
- package/dist/src/commands/furnace/create-readback.js +12 -13
- package/dist/src/commands/furnace/create-templates.d.ts +22 -28
- package/dist/src/commands/furnace/create-templates.js +35 -30
- package/dist/src/commands/furnace/create-xpcshell.js +2 -2
- package/dist/src/commands/furnace/create.js +33 -23
- package/dist/src/commands/furnace/deploy.js +16 -24
- package/dist/src/commands/furnace/diff.js +9 -9
- package/dist/src/commands/furnace/init.js +33 -36
- package/dist/src/commands/furnace/override.js +23 -25
- package/dist/src/commands/furnace/preview.js +47 -67
- package/dist/src/commands/furnace/refresh.js +12 -16
- package/dist/src/commands/furnace/remove-mutations.d.ts +3 -6
- package/dist/src/commands/furnace/remove-mutations.js +9 -16
- package/dist/src/commands/furnace/remove-state.js +1 -1
- package/dist/src/commands/furnace/remove.js +33 -38
- package/dist/src/commands/furnace/rename-xpcshell.d.ts +8 -11
- package/dist/src/commands/furnace/rename-xpcshell.js +8 -11
- package/dist/src/commands/furnace/rename.js +59 -69
- package/dist/src/commands/furnace/scan.js +19 -33
- package/dist/src/commands/furnace/status.js +12 -9
- package/dist/src/commands/furnace/sync.js +9 -16
- package/dist/src/commands/furnace/validate.js +20 -20
- package/dist/src/commands/import.js +42 -35
- package/dist/src/commands/lint-per-patch.js +15 -10
- package/dist/src/commands/lint-per-run-checkjs.d.ts +13 -13
- package/dist/src/commands/lint-per-run-checkjs.js +13 -13
- package/dist/src/commands/lint-report.js +6 -7
- package/dist/src/commands/lint.d.ts +15 -16
- package/dist/src/commands/lint.js +86 -102
- package/dist/src/commands/package.js +6 -11
- package/dist/src/commands/patch/compact.js +1 -1
- package/dist/src/commands/patch/delete.js +24 -33
- package/dist/src/commands/patch/index.d.ts +5 -6
- package/dist/src/commands/patch/index.js +10 -12
- package/dist/src/commands/patch/lint-ignore.js +14 -13
- package/dist/src/commands/patch/move-files-create.d.ts +6 -6
- package/dist/src/commands/patch/move-files-create.js +8 -8
- package/dist/src/commands/patch/move-files-into.d.ts +6 -6
- package/dist/src/commands/patch/move-files-into.js +8 -8
- package/dist/src/commands/patch/patch-context.d.ts +2 -3
- package/dist/src/commands/patch/patch-context.js +2 -3
- package/dist/src/commands/patch/rename.d.ts +10 -11
- package/dist/src/commands/patch/rename.js +11 -12
- package/dist/src/commands/patch/reorder.js +8 -9
- package/dist/src/commands/patch/split-plan.js +1 -1
- package/dist/src/commands/patch/split.d.ts +8 -10
- package/dist/src/commands/patch/split.js +9 -11
- package/dist/src/commands/patch/staged-dependency-validate.js +8 -8
- package/dist/src/commands/patch/staged-dependency.js +7 -3
- package/dist/src/commands/patch/tier.d.ts +9 -9
- package/dist/src/commands/patch/tier.js +18 -15
- package/dist/src/commands/re-export-adjacent.js +9 -8
- package/dist/src/commands/re-export-bulk-scan.d.ts +9 -9
- package/dist/src/commands/re-export-bulk-scan.js +22 -21
- package/dist/src/commands/re-export-drift.js +10 -10
- package/dist/src/commands/re-export-files.d.ts +6 -7
- package/dist/src/commands/re-export-files.js +34 -44
- package/dist/src/commands/re-export-lint.d.ts +10 -13
- package/dist/src/commands/re-export-lint.js +16 -14
- package/dist/src/commands/re-export-options.d.ts +6 -6
- package/dist/src/commands/re-export-options.js +6 -6
- package/dist/src/commands/re-export-register.d.ts +4 -10
- package/dist/src/commands/re-export-register.js +5 -11
- package/dist/src/commands/re-export-scan.js +1 -1
- package/dist/src/commands/re-export-single.d.ts +3 -3
- package/dist/src/commands/re-export-single.js +33 -25
- package/dist/src/commands/re-export.js +13 -21
- package/dist/src/commands/rebase/abort.js +3 -3
- package/dist/src/commands/rebase/confirm.js +2 -1
- package/dist/src/commands/rebase/continue.d.ts +1 -1
- package/dist/src/commands/rebase/continue.js +14 -9
- package/dist/src/commands/rebase/index.js +11 -11
- package/dist/src/commands/rebase/patch-loop.d.ts +7 -1
- package/dist/src/commands/rebase/patch-loop.js +60 -39
- package/dist/src/commands/rebase/summary.js +1 -1
- package/dist/src/commands/register.js +5 -7
- package/dist/src/commands/reset.js +2 -1
- package/dist/src/commands/resolve.d.ts +16 -13
- package/dist/src/commands/resolve.js +30 -33
- package/dist/src/commands/run.js +33 -35
- package/dist/src/commands/setup-support.js +15 -19
- package/dist/src/commands/setup.js +13 -9
- package/dist/src/commands/status-check.d.ts +6 -6
- package/dist/src/commands/status-json.d.ts +6 -6
- package/dist/src/commands/status-json.js +5 -0
- package/dist/src/commands/status-output.d.ts +5 -5
- package/dist/src/commands/status-output.js +6 -6
- package/dist/src/commands/status-ownership.d.ts +16 -14
- package/dist/src/commands/status-ownership.js +16 -14
- package/dist/src/commands/status.js +42 -34
- package/dist/src/commands/test-build-phase.js +26 -13
- package/dist/src/commands/test-diagnose.js +44 -44
- package/dist/src/commands/test-register.js +1 -0
- package/dist/src/commands/test-run.d.ts +14 -16
- package/dist/src/commands/test-run.js +21 -13
- package/dist/src/commands/test-stale-gate.d.ts +15 -16
- package/dist/src/commands/test-stale-gate.js +15 -16
- package/dist/src/commands/test-verdict.d.ts +20 -3
- package/dist/src/commands/test-verdict.js +30 -0
- package/dist/src/commands/test.js +83 -76
- package/dist/src/commands/token-coverage.js +3 -4
- package/dist/src/commands/token.js +16 -18
- package/dist/src/commands/tree.js +47 -31
- package/dist/src/commands/typecheck.d.ts +10 -12
- package/dist/src/commands/typecheck.js +24 -27
- package/dist/src/commands/verify.js +13 -15
- package/dist/src/commands/watch.js +21 -23
- package/dist/src/commands/wire.js +52 -62
- package/dist/src/core/ast-utils.d.ts +8 -7
- package/dist/src/core/ast-utils.js +8 -7
- package/dist/src/core/branding.d.ts +9 -14
- package/dist/src/core/branding.js +7 -11
- package/dist/src/core/browser-wire.d.ts +1 -1
- package/dist/src/core/browser-wire.js +14 -19
- package/dist/src/core/build-audit-platform.js +25 -33
- package/dist/src/core/build-audit-registration.js +17 -25
- package/dist/src/core/build-audit-resolve.d.ts +15 -17
- package/dist/src/core/build-audit-resolve.js +27 -33
- package/dist/src/core/build-audit-transforms.js +18 -19
- package/dist/src/core/build-audit.js +28 -42
- package/dist/src/core/build-baseline-types.d.ts +29 -35
- package/dist/src/core/build-baseline.d.ts +5 -4
- package/dist/src/core/build-baseline.js +14 -14
- package/dist/src/core/build-overwrite-guard.d.ts +7 -14
- package/dist/src/core/build-overwrite-guard.js +17 -27
- package/dist/src/core/build-prepare.d.ts +2 -2
- package/dist/src/core/build-prepare.js +8 -9
- package/dist/src/core/config-state.js +12 -4
- package/dist/src/core/config-validate.js +8 -5
- package/dist/src/core/config.d.ts +5 -8
- package/dist/src/core/config.js +7 -16
- package/dist/src/core/coverage-extend.d.ts +15 -13
- package/dist/src/core/coverage-extend.js +15 -13
- package/dist/src/core/destructive.d.ts +31 -9
- package/dist/src/core/destructive.js +29 -26
- package/dist/src/core/diff-hunks.js +10 -12
- package/dist/src/core/discard-baseline.d.ts +8 -8
- package/dist/src/core/discard-baseline.js +16 -15
- package/dist/src/core/display-state.d.ts +12 -14
- package/dist/src/core/display-state.js +12 -14
- package/dist/src/core/engine-changes.d.ts +2 -4
- package/dist/src/core/engine-changes.js +2 -4
- package/dist/src/core/engine-precondition.d.ts +28 -13
- package/dist/src/core/engine-precondition.js +25 -10
- package/dist/src/core/engine-session-lock.d.ts +47 -21
- package/dist/src/core/engine-session-lock.js +168 -33
- package/dist/src/core/export-moved-code-shape.d.ts +10 -10
- package/dist/src/core/export-moved-code-shape.js +10 -10
- package/dist/src/core/file-lock.d.ts +62 -5
- package/dist/src/core/file-lock.js +196 -59
- package/dist/src/core/firefox-cache.js +1 -3
- package/dist/src/core/firefox-extract.js +10 -14
- package/dist/src/core/firefox.d.ts +7 -5
- package/dist/src/core/firefox.js +4 -5
- package/dist/src/core/furnace-apply-dry-run.d.ts +2 -2
- package/dist/src/core/furnace-apply-dry-run.js +3 -2
- package/dist/src/core/furnace-apply-ftl.d.ts +7 -9
- package/dist/src/core/furnace-apply-ftl.js +12 -12
- package/dist/src/core/furnace-apply-helpers.d.ts +7 -7
- package/dist/src/core/furnace-apply-helpers.js +19 -16
- package/dist/src/core/furnace-apply-overwrite-warn.d.ts +8 -9
- package/dist/src/core/furnace-apply-overwrite-warn.js +8 -9
- package/dist/src/core/furnace-apply-undeploy-actions.d.ts +3 -4
- package/dist/src/core/furnace-apply-undeploy-actions.js +3 -4
- package/dist/src/core/furnace-apply.d.ts +5 -8
- package/dist/src/core/furnace-apply.js +19 -24
- package/dist/src/core/furnace-checksum-utils.d.ts +14 -0
- package/dist/src/core/furnace-checksum-utils.js +16 -0
- package/dist/src/core/furnace-config.d.ts +21 -30
- package/dist/src/core/furnace-config.js +32 -54
- package/dist/src/core/furnace-constants.d.ts +8 -0
- package/dist/src/core/furnace-constants.js +10 -2
- package/dist/src/core/furnace-css-fragments.d.ts +5 -5
- package/dist/src/core/furnace-css-fragments.js +5 -5
- package/dist/src/core/furnace-jsconfig.d.ts +11 -12
- package/dist/src/core/furnace-jsconfig.js +11 -12
- package/dist/src/core/furnace-marker.d.ts +7 -8
- package/dist/src/core/furnace-marker.js +7 -8
- package/dist/src/core/furnace-operation.d.ts +31 -0
- package/dist/src/core/furnace-operation.js +54 -17
- package/dist/src/core/furnace-precondition.d.ts +61 -0
- package/dist/src/core/furnace-precondition.js +67 -0
- package/dist/src/core/furnace-registration-remove.d.ts +5 -6
- package/dist/src/core/furnace-registration-remove.js +6 -7
- package/dist/src/core/furnace-registration-validate.d.ts +13 -0
- package/dist/src/core/furnace-registration-validate.js +20 -1
- package/dist/src/core/furnace-registration.d.ts +5 -6
- package/dist/src/core/furnace-registration.js +10 -12
- package/dist/src/core/furnace-rollback.js +7 -7
- package/dist/src/core/furnace-scanner.d.ts +1 -0
- package/dist/src/core/furnace-scanner.js +1 -2
- package/dist/src/core/furnace-stale-export.d.ts +10 -10
- package/dist/src/core/furnace-stale-export.js +10 -10
- package/dist/src/core/furnace-state-persist.d.ts +6 -8
- package/dist/src/core/furnace-state-persist.js +6 -8
- package/dist/src/core/furnace-validate-accessibility.d.ts +22 -0
- package/dist/src/core/furnace-validate-accessibility.js +310 -1
- package/dist/src/core/furnace-validate-compatibility.js +141 -1
- package/dist/src/core/furnace-validate-helpers.d.ts +5 -116
- package/dist/src/core/furnace-validate-helpers.js +0 -491
- package/dist/src/core/furnace-validate-registration.d.ts +7 -0
- package/dist/src/core/furnace-validate-registration.js +115 -41
- package/dist/src/core/furnace-validate.d.ts +16 -0
- package/dist/src/core/furnace-validate.js +188 -145
- package/dist/src/core/furnace-version-drift.d.ts +10 -11
- package/dist/src/core/furnace-version-drift.js +10 -11
- package/dist/src/core/git-base.d.ts +7 -9
- package/dist/src/core/git-base.js +7 -9
- package/dist/src/core/git-diff.d.ts +20 -9
- package/dist/src/core/git-diff.js +148 -63
- package/dist/src/core/git-file-ops.d.ts +19 -19
- package/dist/src/core/git-file-ops.js +19 -19
- package/dist/src/core/git-readonly-index.d.ts +53 -19
- package/dist/src/core/git-readonly-index.js +75 -31
- package/dist/src/core/git-status.d.ts +5 -6
- package/dist/src/core/git-status.js +5 -6
- package/dist/src/core/git.d.ts +9 -10
- package/dist/src/core/git.js +54 -70
- package/dist/src/core/license-headers.d.ts +17 -23
- package/dist/src/core/license-headers.js +17 -23
- package/dist/src/core/mach-build-artifacts.d.ts +9 -9
- package/dist/src/core/mach-build-artifacts.js +57 -19
- package/dist/src/core/mach-error-hints.js +42 -29
- package/dist/src/core/mach-known-noise-filter.d.ts +15 -16
- package/dist/src/core/mach-known-noise-filter.js +18 -20
- package/dist/src/core/mach-mozconfig.d.ts +14 -0
- package/dist/src/core/mach-mozconfig.js +30 -0
- package/dist/src/core/mach-resource-shim.d.ts +43 -58
- package/dist/src/core/mach-resource-shim.js +50 -67
- package/dist/src/core/mach.d.ts +37 -41
- package/dist/src/core/mach.js +50 -51
- package/dist/src/core/marionette-port.d.ts +36 -1
- package/dist/src/core/marionette-port.js +96 -31
- package/dist/src/core/marionette-preflight.d.ts +8 -12
- package/dist/src/core/marionette-preflight.js +86 -52
- package/dist/src/core/mochitest-server-port.d.ts +42 -0
- package/dist/src/core/mochitest-server-port.js +119 -0
- package/dist/src/core/module-resolution-preflight.d.ts +9 -11
- package/dist/src/core/module-resolution-preflight.js +9 -11
- package/dist/src/core/moz-manifest-helpers.d.ts +79 -0
- package/dist/src/core/moz-manifest-helpers.js +163 -0
- package/dist/src/core/{manifest-register.d.ts → moz-manifest-register.d.ts} +1 -1
- package/dist/src/core/{manifest-register.js → moz-manifest-register.js} +13 -28
- package/dist/src/core/{manifest-rules.d.ts → moz-manifest-rules.d.ts} +10 -1
- package/dist/src/core/{manifest-rules.js → moz-manifest-rules.js} +31 -24
- package/dist/src/core/{manifest-tokenizers.d.ts → moz-manifest-tokenizers.d.ts} +3 -5
- package/dist/src/core/{manifest-tokenizers.js → moz-manifest-tokenizers.js} +9 -12
- package/dist/src/core/ownership-table.d.ts +14 -16
- package/dist/src/core/ownership-table.js +14 -16
- package/dist/src/core/parser-fallback.d.ts +15 -1
- package/dist/src/core/parser-fallback.js +22 -3
- package/dist/src/core/patch-apply-fuzz.d.ts +10 -12
- package/dist/src/core/patch-apply-fuzz.js +15 -18
- package/dist/src/core/patch-apply.d.ts +13 -34
- package/dist/src/core/patch-apply.js +16 -76
- package/dist/src/core/patch-artifact-normalize.d.ts +13 -15
- package/dist/src/core/patch-artifact-normalize.js +13 -15
- package/dist/src/core/patch-export-metadata.d.ts +12 -2
- package/dist/src/core/patch-export-metadata.js +13 -4
- package/dist/src/core/patch-export-update.d.ts +13 -1
- package/dist/src/core/patch-export-update.js +25 -8
- package/dist/src/core/patch-export.d.ts +15 -8
- package/dist/src/core/patch-export.js +10 -11
- package/dist/src/core/patch-files.d.ts +5 -6
- package/dist/src/core/patch-files.js +5 -6
- package/dist/src/core/patch-identifier-suggest.d.ts +7 -12
- package/dist/src/core/patch-identifier-suggest.js +14 -19
- package/dist/src/core/patch-lint-binary.d.ts +51 -0
- package/dist/src/core/patch-lint-binary.js +39 -0
- package/dist/src/core/{lint-cache.d.ts → patch-lint-cache.d.ts} +5 -5
- package/dist/src/core/{lint-cache.js → patch-lint-cache.js} +21 -23
- package/dist/src/core/patch-lint-checkjs.d.ts +16 -16
- package/dist/src/core/patch-lint-checkjs.js +16 -16
- package/dist/src/core/patch-lint-chrome-jsdoc.d.ts +6 -9
- package/dist/src/core/patch-lint-chrome-jsdoc.js +10 -14
- package/dist/src/core/patch-lint-creators.d.ts +8 -8
- package/dist/src/core/patch-lint-creators.js +8 -8
- package/dist/src/core/patch-lint-cross.d.ts +17 -20
- package/dist/src/core/patch-lint-cross.js +16 -16
- package/dist/src/core/patch-lint-css.d.ts +3 -5
- package/dist/src/core/patch-lint-css.js +17 -22
- package/dist/src/core/patch-lint-diff-tag.d.ts +14 -16
- package/dist/src/core/patch-lint-diff-tag.js +14 -16
- package/dist/src/core/patch-lint-jsdoc.d.ts +7 -8
- package/dist/src/core/patch-lint-jsdoc.js +26 -19
- package/dist/src/core/patch-lint-mozbuild.js +20 -20
- package/dist/src/core/patch-lint-observer.d.ts +7 -7
- package/dist/src/core/patch-lint-observer.js +7 -7
- package/dist/src/core/patch-lint.d.ts +38 -29
- package/dist/src/core/patch-lint.js +89 -121
- package/dist/src/core/patch-lock.d.ts +1 -1
- package/dist/src/core/patch-lock.js +45 -9
- package/dist/src/core/patch-manifest-consistency.d.ts +2 -1
- package/dist/src/core/patch-manifest-consistency.js +25 -27
- package/dist/src/core/patch-manifest-io.js +14 -18
- package/dist/src/core/patch-manifest-resolve.d.ts +9 -12
- package/dist/src/core/patch-manifest-resolve.js +9 -12
- package/dist/src/core/patch-manifest-validate.d.ts +6 -1
- package/dist/src/core/patch-manifest-validate.js +25 -13
- package/dist/src/core/patch-parse.d.ts +24 -13
- package/dist/src/core/patch-parse.js +19 -14
- package/dist/src/core/patch-policy.d.ts +2 -2
- package/dist/src/core/patch-policy.js +2 -2
- package/dist/src/core/patch-registration-refs.d.ts +7 -8
- package/dist/src/core/patch-registration-refs.js +9 -9
- package/dist/src/core/process-cpu-time.d.ts +12 -0
- package/dist/src/core/process-cpu-time.js +52 -0
- package/dist/src/core/rebase-session.d.ts +38 -26
- package/dist/src/core/rebase-session.js +55 -28
- package/dist/src/core/register-browser-content.js +9 -74
- package/dist/src/core/{register-test-manifest.js → register-browser-test.js} +7 -42
- package/dist/src/core/register-module.js +10 -43
- package/dist/src/core/register-scaffold.d.ts +8 -8
- package/dist/src/core/register-scaffold.js +10 -10
- package/dist/src/core/register-shared-css.d.ts +9 -12
- package/dist/src/core/register-shared-css.js +17 -87
- package/dist/src/core/register-xpcshell-test.d.ts +6 -7
- package/dist/src/core/register-xpcshell-test.js +7 -8
- package/dist/src/core/shared-ftl.js +12 -4
- package/dist/src/core/state-file.d.ts +45 -0
- package/dist/src/core/state-file.js +34 -0
- package/dist/src/core/status-binary.d.ts +18 -11
- package/dist/src/core/status-binary.js +19 -12
- package/dist/src/core/status-classify.d.ts +30 -36
- package/dist/src/core/status-classify.js +21 -26
- package/dist/src/core/test-changed-prefs.d.ts +30 -0
- package/dist/src/core/test-changed-prefs.js +78 -0
- package/dist/src/core/test-harness-crash.d.ts +25 -63
- package/dist/src/core/test-harness-crash.js +121 -142
- package/dist/src/core/test-harness-signature.d.ts +18 -0
- package/dist/src/core/test-harness-signature.js +13 -0
- package/dist/src/core/test-path-scope.d.ts +12 -15
- package/dist/src/core/test-path-scope.js +15 -18
- package/dist/src/core/test-stale-check.d.ts +17 -17
- package/dist/src/core/test-stale-check.js +50 -43
- package/dist/src/core/test-stall-triage.d.ts +51 -0
- package/dist/src/core/test-stall-triage.js +126 -0
- package/dist/src/core/token-category.js +5 -7
- package/dist/src/core/token-coverage.js +9 -9
- package/dist/src/core/token-dark-mode.d.ts +9 -11
- package/dist/src/core/token-dark-mode.js +9 -11
- package/dist/src/core/token-docs.d.ts +2 -0
- package/dist/src/core/token-docs.js +2 -0
- package/dist/src/core/token-manager.d.ts +10 -2
- package/dist/src/core/token-manager.js +17 -8
- package/dist/src/core/token-scaffold.d.ts +7 -7
- package/dist/src/core/token-scaffold.js +7 -7
- package/dist/src/core/token-variant.d.ts +4 -4
- package/dist/src/core/token-variant.js +4 -4
- package/dist/src/core/toolchain-preflight.d.ts +20 -21
- package/dist/src/core/toolchain-preflight.js +28 -31
- package/dist/src/core/tree-guard.js +12 -2
- package/dist/src/core/tree-store.d.ts +41 -27
- package/dist/src/core/tree-store.js +63 -42
- package/dist/src/core/typecheck-shim.d.ts +23 -24
- package/dist/src/core/typecheck-shim.js +23 -24
- package/dist/src/core/typecheck.js +8 -8
- package/dist/src/core/wire-destroy.d.ts +3 -3
- package/dist/src/core/wire-destroy.js +7 -15
- package/dist/src/core/wire-dom-fragment.d.ts +9 -17
- package/dist/src/core/wire-dom-fragment.js +11 -44
- package/dist/src/core/wire-init.d.ts +2 -3
- package/dist/src/core/wire-init.js +8 -19
- package/dist/src/core/wire-subscript.d.ts +2 -3
- package/dist/src/core/wire-subscript.js +4 -13
- package/dist/src/core/wire-utils.d.ts +7 -8
- package/dist/src/core/wire-utils.js +7 -8
- package/dist/src/core/xpcshell-appdir.d.ts +38 -45
- package/dist/src/core/xpcshell-appdir.js +38 -45
- package/dist/src/errors/base.d.ts +37 -16
- package/dist/src/errors/base.js +37 -16
- package/dist/src/errors/build.d.ts +13 -1
- package/dist/src/errors/build.js +14 -2
- package/dist/src/errors/codes.d.ts +16 -0
- package/dist/src/errors/codes.js +16 -0
- package/dist/src/errors/furnace.d.ts +4 -4
- package/dist/src/errors/git.d.ts +6 -10
- package/dist/src/errors/git.js +6 -10
- package/dist/src/errors/patch.d.ts +7 -7
- package/dist/src/errors/patch.js +7 -7
- package/dist/src/errors/rebase.d.ts +4 -4
- package/dist/src/errors/rebase.js +4 -4
- package/dist/src/index.d.ts +7 -1
- package/dist/src/index.js +5 -0
- package/dist/src/types/commands/index.d.ts +2 -2
- package/dist/src/types/commands/options.d.ts +88 -70
- package/dist/src/types/commands/patches.d.ts +24 -34
- package/dist/src/types/commands/project.d.ts +5 -34
- package/dist/src/types/config.d.ts +25 -23
- package/dist/src/types/config.js +6 -1
- package/dist/src/types/furnace.d.ts +37 -13
- package/dist/src/types/furnace.js +19 -1
- package/dist/src/types/index.d.ts +3 -3
- package/dist/src/utils/build-info.d.ts +7 -8
- package/dist/src/utils/build-info.js +12 -11
- package/dist/src/utils/errors.d.ts +6 -8
- package/dist/src/utils/errors.js +6 -8
- package/dist/src/utils/logger.d.ts +27 -10
- package/dist/src/utils/logger.js +28 -16
- package/dist/src/utils/machine-output.d.ts +12 -0
- package/dist/src/utils/machine-output.js +33 -0
- package/dist/src/utils/options.d.ts +31 -14
- package/dist/src/utils/options.js +86 -30
- package/dist/src/utils/paths.d.ts +10 -8
- package/dist/src/utils/paths.js +10 -8
- package/dist/src/utils/platform.d.ts +0 -25
- package/dist/src/utils/platform.js +1 -42
- package/dist/src/utils/process-group.d.ts +9 -4
- package/dist/src/utils/process-group.js +19 -5
- package/dist/src/utils/process.d.ts +42 -7
- package/dist/src/utils/process.js +44 -17
- package/dist/src/utils/ps-duration.d.ts +31 -0
- package/dist/src/utils/ps-duration.js +70 -0
- package/dist/src/utils/regex.d.ts +9 -10
- package/dist/src/utils/regex.js +9 -10
- package/dist/src/utils/validation.d.ts +58 -28
- package/dist/src/utils/validation.js +60 -33
- package/package.json +2 -2
- package/dist/src/commands/doctor/post-rebase-audit.d.ts +0 -2
- package/dist/src/commands/doctor-furnace-manifest-sync.d.ts +0 -18
- package/dist/src/core/manifest-helpers.d.ts +0 -38
- package/dist/src/core/manifest-helpers.js +0 -120
- /package/dist/src/core/{lint-projection.d.ts → patch-lint-projection.d.ts} +0 -0
- /package/dist/src/core/{lint-projection.js → patch-lint-projection.js} +0 -0
- /package/dist/src/core/{register-test-manifest.d.ts → register-browser-test.d.ts} +0 -0
package/dist/src/core/mach.js
CHANGED
|
@@ -12,6 +12,11 @@ import { getPython } from './mach-python.js';
|
|
|
12
12
|
import { installMachResourceGuard } from './mach-resource-shim.js';
|
|
13
13
|
import { detectHarnessCrashSignature } from './test-harness-crash.js';
|
|
14
14
|
// Re-export sub-modules so existing `from './mach.js'` imports keep working.
|
|
15
|
+
// This block is a frozen compatibility list, not a barrel to grow: every name
|
|
16
|
+
// here already had a `from './mach.js'` import when the module was split.
|
|
17
|
+
// New helpers belong in their own sub-module and are imported from it
|
|
18
|
+
// directly — adding to this list pulls the whole sub-module into every suite
|
|
19
|
+
// that mocks `mach.js`.
|
|
15
20
|
export { attemptMozinfoRewrite, buildArtifactMismatchMessage, hasBuildArtifacts, hasRunnableBundle, } from './mach-build-artifacts.js';
|
|
16
21
|
export { generateMozconfig } from './mach-mozconfig.js';
|
|
17
22
|
export { ensurePython, resetResolvedPython } from './mach-python.js';
|
|
@@ -87,7 +92,7 @@ export async function runMachCapture(args, engineDir, options = {}) {
|
|
|
87
92
|
...(options.env ? { env: options.env } : {}),
|
|
88
93
|
// Every capture dispatch (test suites, protected builds, package,
|
|
89
94
|
// storybook) runs as a process-group leader and is group-reaped on
|
|
90
|
-
// exit/abort — see ExecOptions.processGroup
|
|
95
|
+
// exit/abort — see ExecOptions.processGroup.
|
|
91
96
|
processGroup: true,
|
|
92
97
|
onStdout: (data) => {
|
|
93
98
|
stdout += data;
|
|
@@ -148,18 +153,16 @@ export async function bootstrapWithOutput(engineDir) {
|
|
|
148
153
|
return runMachInheritCapture(['bootstrap', '--application-choice', 'browser'], engineDir);
|
|
149
154
|
}
|
|
150
155
|
/**
|
|
151
|
-
* Prints any matched {@link MachErrorHint} hints for the captured mach
|
|
152
|
-
* No-op when nothing matches. Always called before a non-zero exit
|
|
153
|
-
* so the hint sits immediately below the raw mach error in the
|
|
154
|
-
* terminal.
|
|
156
|
+
* Prints any matched {@link MachErrorHint} hints for the captured mach
|
|
157
|
+
* output. No-op when nothing matches. Always called before a non-zero exit
|
|
158
|
+
* propagates so the hint sits immediately below the raw mach error in the
|
|
159
|
+
* operator's terminal.
|
|
155
160
|
*
|
|
156
161
|
* The scanner is passed the concatenation of stderr AND stdout because mach
|
|
157
162
|
* streams its subcommand output through a timestamp-prefixing wrapper that
|
|
158
|
-
* writes both streams to whatever FD the subprocess chose —
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* text but our pre-0.16 code only fed `result.stderr` into the scanner, so
|
|
162
|
-
* the hint never fired.
|
|
163
|
+
* writes both streams to whatever FD the subprocess chose — `rustc` errors
|
|
164
|
+
* from `mach build` can land on stdout, so feeding only `result.stderr`
|
|
165
|
+
* silently loses the match.
|
|
163
166
|
*/
|
|
164
167
|
function surfaceMachErrorHints(result) {
|
|
165
168
|
const combined = `${result.stderr}\n${result.stdout}`;
|
|
@@ -179,21 +182,20 @@ function surfaceMachErrorHints(result) {
|
|
|
179
182
|
const DEFAULT_BUILD_CRASH_RETRIES = 2;
|
|
180
183
|
/**
|
|
181
184
|
* The single protected path every FireForge mach build dispatch routes
|
|
182
|
-
* through
|
|
183
|
-
*
|
|
184
|
-
* because only some entries were protected and retry budgets differed):
|
|
185
|
+
* through — `build`, `build --ui`, and the pre-test `--build` step all use
|
|
186
|
+
* it, so no entry point is left unprotected with a different retry budget:
|
|
185
187
|
*
|
|
186
188
|
* 1. installs the resource-monitor degrade guard IN the mach virtualenvs
|
|
187
|
-
* (plus the PYTHONPATH fallback) — re-installed before every attempt,
|
|
188
|
-
*
|
|
189
|
-
*
|
|
189
|
+
* (plus the PYTHONPATH fallback) — re-installed before every attempt, so
|
|
190
|
+
* a venv materialized by a crashed first attempt is guarded on the next
|
|
191
|
+
* one instead of every retry dying on the same wedged state;
|
|
190
192
|
* 2. spawns a fresh mach process per attempt;
|
|
191
193
|
* 3. retries ONLY the recognized harness-crash family (resource monitor /
|
|
192
194
|
* psutil startup tracebacks) up to the uniform budget — an ordinary
|
|
193
195
|
* compile error is never retried.
|
|
194
196
|
*
|
|
195
|
-
* The protected path never runs `mach configure`, never clobbers, and
|
|
196
|
-
*
|
|
197
|
+
* The protected path never runs `mach configure`, never clobbers, and never
|
|
198
|
+
* widens the requested build kind — a `faster` dispatch retries as
|
|
197
199
|
* `mach build faster`, so it cannot invalidate more of the objdir than the
|
|
198
200
|
* command the operator asked for.
|
|
199
201
|
*/
|
|
@@ -249,32 +251,27 @@ export async function buildUI(engineDir) {
|
|
|
249
251
|
return runProtectedMachBuild('faster', engineDir);
|
|
250
252
|
}
|
|
251
253
|
/**
|
|
252
|
-
* Runs an operation while holding a sidecar build lock keyed on the
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
254
|
+
* Runs an operation while holding a sidecar build lock keyed on the project
|
|
255
|
+
* root. Concurrent `fireforge build` / `fireforge build --ui` invocations
|
|
256
|
+
* against the same tree serialise instead of racing through the mach
|
|
257
|
+
* obj-dir.
|
|
256
258
|
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* build in flight). The lock intercepts the second invocation before
|
|
265
|
-
* it touches mach, and the refusal message names the PID currently
|
|
266
|
-
* holding the lock so the operator can decide whether to wait or
|
|
267
|
-
* investigate a hung process.
|
|
259
|
+
* Without it, a `build --ui` started while a full `build` is still in flight
|
|
260
|
+
* hands off to `mach build faster` and fails almost immediately with `No
|
|
261
|
+
* rule to make target 'XUL'` — the real problem being that the first build
|
|
262
|
+
* has not yet materialised the full backend, with nothing in the low-level
|
|
263
|
+
* make error pointing at the concurrent run. The lock intercepts the second
|
|
264
|
+
* invocation before it touches mach, and the refusal names the PID holding
|
|
265
|
+
* the lock so the operator can decide whether to wait or investigate.
|
|
268
266
|
*
|
|
269
267
|
* Stale-lock recovery: the lock stores the owner PID; a crashed build
|
|
270
|
-
* (SIGINT, SIGTERM, or a kernel kill) leaves the lock dir behind but
|
|
271
|
-
*
|
|
272
|
-
*
|
|
268
|
+
* (SIGINT, SIGTERM, or a kernel kill) leaves the lock dir behind but not the
|
|
269
|
+
* owning process, and `withFileLock` removes the lock on the next attempt
|
|
270
|
+
* when `process.kill(pid, 0)` shows the owner is gone.
|
|
273
271
|
*
|
|
274
|
-
* The project-root variant is the right granularity: a single machine
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* project, not across unrelated ones.
|
|
272
|
+
* The project-root variant is the right granularity: a single machine may
|
|
273
|
+
* have several FireForge projects side by side and nothing says they cannot
|
|
274
|
+
* build in parallel. The lock serialises *within* one project.
|
|
278
275
|
*
|
|
279
276
|
* Returns whatever the inner operation returns.
|
|
280
277
|
*/
|
|
@@ -356,18 +353,17 @@ export async function watch(engineDir) {
|
|
|
356
353
|
}
|
|
357
354
|
/**
|
|
358
355
|
* Runs mach watch while preserving stdin and capturing emitted output.
|
|
356
|
+
*
|
|
357
|
+
* `env` is threaded through so the caller can prepend the resolved watchman
|
|
358
|
+
* directory to PATH in a way mach inherits: `fireforge watch` can locate
|
|
359
|
+
* `watchman` via `which`, but the mach subprocess spawns with the parent's
|
|
360
|
+
* PATH only — on macOS that typically omits `/opt/homebrew/bin`, so
|
|
361
|
+
* `mach watch` fails at the `watch-project` subscription step.
|
|
362
|
+
*
|
|
359
363
|
* @param engineDir - Path to the engine directory
|
|
360
|
-
* @param options - Optional environment overrides merged into the mach
|
|
364
|
+
* @param options - Optional environment overrides merged into the mach
|
|
365
|
+
* subprocess env
|
|
361
366
|
* @returns Captured output and exit code
|
|
362
|
-
*
|
|
363
|
-
* 2026-04-24 eval Finding 12: the pre-0.18.1 shape accepted no options
|
|
364
|
-
* and so never forwarded the detected watchman path into the mach
|
|
365
|
-
* subprocess env. `fireforge watch` could locate `watchman` via PATH
|
|
366
|
-
* (the probe's `which` succeeded) but the mach subprocess spawned with
|
|
367
|
-
* the parent's PATH only — on macOS that typically omits
|
|
368
|
-
* `/opt/homebrew/bin`, so `mach watch` failed at the `watch-project`
|
|
369
|
-
* subscription step. Accepting `env` here lets the caller prepend the
|
|
370
|
-
* resolved watchman directory to PATH in a way mach inherits.
|
|
371
367
|
*/
|
|
372
368
|
export async function watchWithOutput(engineDir, options = {}) {
|
|
373
369
|
return runMachInheritCapture(['watch'], engineDir, options.env ? { env: options.env } : {});
|
|
@@ -385,6 +381,9 @@ export async function test(engineDir, testPaths = [], args = []) {
|
|
|
385
381
|
/**
|
|
386
382
|
* Runs mach test while capturing streamed output for better diagnostics.
|
|
387
383
|
*
|
|
384
|
+
* @param engineDir - Absolute path to the engine checkout
|
|
385
|
+
* @param testPaths - Test paths to pass to `mach test`; empty runs the default set
|
|
386
|
+
* @param args - Extra `mach test` arguments appended after the paths
|
|
388
387
|
* @param env - Optional extra environment variables for the mach process
|
|
389
388
|
* (merged over `process.env` by the exec layer). Used by
|
|
390
389
|
* `fireforge test --perf-samples` to publish the artifact-path contract.
|
|
@@ -401,7 +400,7 @@ export async function testWithOutput(engineDir, testPaths = [], args = [], env)
|
|
|
401
400
|
* capturing output. Unlike the generic `mach test`, the suite-specific
|
|
402
401
|
* commands degrade a broken mozlog resource monitor to a warning instead of
|
|
403
402
|
* crashing at startup, so `fireforge test` dispatches single-suite runs here
|
|
404
|
-
* to stay resilient to the host psutil failure
|
|
403
|
+
* to stay resilient to the host psutil failure.
|
|
405
404
|
*
|
|
406
405
|
* Signature mirrors {@link testWithOutput} so the two are interchangeable in
|
|
407
406
|
* the dispatch path.
|
|
@@ -31,8 +31,24 @@ export interface MarionettePortProbeResult {
|
|
|
31
31
|
export interface RunningBundleProcess {
|
|
32
32
|
pid: number;
|
|
33
33
|
commandLine: string;
|
|
34
|
+
/**
|
|
35
|
+
* Elapsed wall-clock seconds since the process started, from `ps
|
|
36
|
+
* etime=`. `NaN` when the field was absent or unparseable — callers must
|
|
37
|
+
* omit the clause rather than print a zero, because "started 0s ago" is
|
|
38
|
+
* exactly the wrong attribution for a peer session's long-lived browser.
|
|
39
|
+
*/
|
|
40
|
+
elapsedSeconds: number;
|
|
34
41
|
}
|
|
35
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Pure parser kept exported so platform output handling is
|
|
44
|
+
* regression-testable.
|
|
45
|
+
*
|
|
46
|
+
* Reads `pid etime args` where the middle field is a `ps` duration. The
|
|
47
|
+
* elapsed field is parsed OPTIMISTICALLY: a listing produced without
|
|
48
|
+
* `etime=` still parses, with the duration reported as `NaN` and the whole
|
|
49
|
+
* second field folded back into the command line, so a caller that lost the
|
|
50
|
+
* column degrades to the old behaviour instead of dropping the process.
|
|
51
|
+
*/
|
|
36
52
|
export declare function parseProcessList(stdout: string, launchableBinary: string): RunningBundleProcess[];
|
|
37
53
|
/**
|
|
38
54
|
* Refuses a browser-harness launch when this objdir's app is already alive,
|
|
@@ -43,6 +59,25 @@ export declare function parseProcessList(stdout: string, launchableBinary: strin
|
|
|
43
59
|
export declare function ensureLaunchableBrowserNotRunning(launchableBinary: string, options?: {
|
|
44
60
|
killStaleBrowser?: boolean;
|
|
45
61
|
}): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Builds the refusal for a running objdir browser.
|
|
64
|
+
*
|
|
65
|
+
* Reports the EVIDENCE rather than a bare PID: the elapsed time and the
|
|
66
|
+
* command line are what let an operator on a shared checkout decide whose
|
|
67
|
+
* process it is, without reconstructing PID ancestry by hand. A downstream
|
|
68
|
+
* fork hit exactly this — the message named a PID belonging to a peer
|
|
69
|
+
* session's live browser and offered a flag that would have killed a
|
|
70
|
+
* multi-hour evaluation run.
|
|
71
|
+
*
|
|
72
|
+
* So `--kill-stale-marionette` is offered ONLY for a marionette-driven
|
|
73
|
+
* browser. For a bare launch the message says what it found and stops:
|
|
74
|
+
* FireForge cannot tell a dead run's orphan from someone's live window, and
|
|
75
|
+
* the flag is the right MANUAL clear once ownership is verified, never an
|
|
76
|
+
* automatic one.
|
|
77
|
+
*
|
|
78
|
+
* Exported for direct unit testing.
|
|
79
|
+
*/
|
|
80
|
+
export declare function describeRunningBundleRefusal(holder: RunningBundleProcess): string;
|
|
46
81
|
/**
|
|
47
82
|
* Probes whether the Marionette port is currently bound by a
|
|
48
83
|
* listener. The probe is best-effort: missing tooling returns
|
|
@@ -2,38 +2,33 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Marionette port probe.
|
|
4
4
|
*
|
|
5
|
-
* Gecko's Marionette control channel binds `127.0.0.1:2828` when a
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* that flag, so any test run needs the port to be free at start.
|
|
5
|
+
* Gecko's Marionette control channel binds `127.0.0.1:2828` when a browser
|
|
6
|
+
* is launched with `-marionette`. The `fireforge test` harness spawns the
|
|
7
|
+
* browser with that flag, so any test run needs the port free at start.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* —
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* when the holder is a browser process (by command-line `-marionette`
|
|
19
|
-
* flag or by basename matching a known browser binary) — throws a
|
|
20
|
-
* targeted `GeneralError` naming the PID and the exact `kill` command
|
|
21
|
-
* to run.
|
|
9
|
+
* An interrupted `fireforge test` can leave an orphan browser process
|
|
10
|
+
* listening on 2828 with parent PID 1, and the next test run — possibly in a
|
|
11
|
+
* *different* FireForge project — then fails with a bare Marionette bind
|
|
12
|
+
* error. This probe runs before every test launch, detects when the port is
|
|
13
|
+
* held, and — when the holder is a browser process (by command-line
|
|
14
|
+
* `-marionette` flag or by basename matching a known browser binary) —
|
|
15
|
+
* throws a targeted `GeneralError` naming the PID and the exact `kill`
|
|
16
|
+
* command to run.
|
|
22
17
|
*
|
|
23
18
|
* Cross-platform implementation:
|
|
24
19
|
* - POSIX (macOS / Linux): `lsof -i tcp:<port> -P -n -sTCP:LISTEN`
|
|
25
|
-
* - Windows: `Get-NetTCPConnection` via PowerShell, then
|
|
26
|
-
*
|
|
20
|
+
* - Windows: `Get-NetTCPConnection` via PowerShell, then `Get-Process` to
|
|
21
|
+
* resolve the PID into a command line.
|
|
27
22
|
*
|
|
28
|
-
* Both paths tolerate missing tooling: if `lsof` / PowerShell
|
|
29
|
-
* installed
|
|
30
|
-
*
|
|
31
|
-
* check, not a prerequisite.
|
|
23
|
+
* Both paths tolerate missing tooling: if `lsof` / PowerShell is not
|
|
24
|
+
* installed the probe returns `{ inUse: false }` rather than failing the
|
|
25
|
+
* test run — it is a best-effort friendliness check, not a prerequisite.
|
|
32
26
|
*/
|
|
33
27
|
import { GeneralError } from '../errors/base.js';
|
|
34
28
|
import { toError } from '../utils/errors.js';
|
|
35
29
|
import { getPlatform } from '../utils/platform.js';
|
|
36
30
|
import { exec } from '../utils/process.js';
|
|
31
|
+
import { formatPsDuration, parsePsDuration } from '../utils/ps-duration.js';
|
|
37
32
|
/** Default Marionette control port set by `-marionette`. */
|
|
38
33
|
export const DEFAULT_MARIONETTE_PORT = 2828;
|
|
39
34
|
/** Basenames of browser binaries that ship a Marionette listener. */
|
|
@@ -57,7 +52,8 @@ async function findRunningBundleProcesses(launchableBinary) {
|
|
|
57
52
|
if (process.platform === 'win32') {
|
|
58
53
|
const escapedPath = launchableBinary.replaceAll("'", "''");
|
|
59
54
|
const script = `Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -eq '${escapedPath}' } | ` +
|
|
60
|
-
'ForEach-Object { Write-Output ("$($_.ProcessId)
|
|
55
|
+
'ForEach-Object { Write-Output ("$($_.ProcessId) ' +
|
|
56
|
+
'$([int]((Get-Date) - $_.CreationDate).TotalSeconds)s $($_.CommandLine)") }';
|
|
61
57
|
const result = await exec('powershell.exe', [
|
|
62
58
|
'-NoProfile',
|
|
63
59
|
'-NonInteractive',
|
|
@@ -66,24 +62,44 @@ async function findRunningBundleProcesses(launchableBinary) {
|
|
|
66
62
|
]);
|
|
67
63
|
return parseProcessList(result.stdout, launchableBinary);
|
|
68
64
|
}
|
|
69
|
-
|
|
65
|
+
// `etime=` is what makes the refusal attributable on a shared checkout:
|
|
66
|
+
// a browser that started as YOUR run was finishing is not yours.
|
|
67
|
+
const result = await exec('ps', ['-axo', 'pid=,etime=,args=']);
|
|
70
68
|
return parseProcessList(result.stdout, launchableBinary);
|
|
71
69
|
}
|
|
72
70
|
catch {
|
|
73
71
|
return [];
|
|
74
72
|
}
|
|
75
73
|
}
|
|
76
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Pure parser kept exported so platform output handling is
|
|
76
|
+
* regression-testable.
|
|
77
|
+
*
|
|
78
|
+
* Reads `pid etime args` where the middle field is a `ps` duration. The
|
|
79
|
+
* elapsed field is parsed OPTIMISTICALLY: a listing produced without
|
|
80
|
+
* `etime=` still parses, with the duration reported as `NaN` and the whole
|
|
81
|
+
* second field folded back into the command line, so a caller that lost the
|
|
82
|
+
* column degrades to the old behaviour instead of dropping the process.
|
|
83
|
+
*/
|
|
77
84
|
export function parseProcessList(stdout, launchableBinary) {
|
|
78
85
|
const matches = [];
|
|
79
86
|
for (const line of stdout.split(/\r?\n/)) {
|
|
80
|
-
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
81
|
-
if (!match?.[1]
|
|
87
|
+
const match = /^\s*(\d+)\s+(\S+)\s+(.+)$/.exec(line);
|
|
88
|
+
if (!match?.[1])
|
|
82
89
|
continue;
|
|
83
90
|
const pid = Number(match[1]);
|
|
84
91
|
if (!Number.isSafeInteger(pid) || pid <= 0 || pid === process.pid)
|
|
85
92
|
continue;
|
|
86
|
-
|
|
93
|
+
const maybeElapsed = match[2] ?? '';
|
|
94
|
+
const elapsedSeconds = parsePsDuration(maybeElapsed);
|
|
95
|
+
// Without an `etime` column the second field is the head of the command
|
|
96
|
+
// line, so put it back rather than swallowing it.
|
|
97
|
+
const commandLine = Number.isNaN(elapsedSeconds)
|
|
98
|
+
? `${maybeElapsed} ${match[3] ?? ''}`.trim()
|
|
99
|
+
: (match[3] ?? '');
|
|
100
|
+
if (!commandLine.includes(launchableBinary))
|
|
101
|
+
continue;
|
|
102
|
+
matches.push({ pid, commandLine, elapsedSeconds });
|
|
87
103
|
}
|
|
88
104
|
return matches;
|
|
89
105
|
}
|
|
@@ -120,12 +136,61 @@ export async function ensureLaunchableBrowserNotRunning(launchableBinary, option
|
|
|
120
136
|
throw new GeneralError(`A browser from this objdir is still running (PID ${holder.pid}), but FireForge could not terminate it: ${toError(error).message}`);
|
|
121
137
|
}
|
|
122
138
|
}
|
|
139
|
+
throw new GeneralError(describeRunningBundleRefusal(holder));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* True when the process was launched BY a test harness — its command line
|
|
143
|
+
* carries `-marionette` (the control channel every `fireforge test` browser
|
|
144
|
+
* is started with) or a harness `-profile`.
|
|
145
|
+
*
|
|
146
|
+
* This is the distinction the refusal turns on. The objdir-bundle probe
|
|
147
|
+
* matches any process running this project's binary, which on a shared
|
|
148
|
+
* checkout includes a peer session's LIVE browser and a developer's own
|
|
149
|
+
* interactive one. Only a marionette-driven browser is safely
|
|
150
|
+
* attributable to an interrupted run.
|
|
151
|
+
*/
|
|
152
|
+
function isHarnessDrivenBrowser(commandLine) {
|
|
153
|
+
return /\s-marionette(?:\s|$)/.test(commandLine) || /\s-profile(?:\s|=)/.test(commandLine);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Builds the refusal for a running objdir browser.
|
|
157
|
+
*
|
|
158
|
+
* Reports the EVIDENCE rather than a bare PID: the elapsed time and the
|
|
159
|
+
* command line are what let an operator on a shared checkout decide whose
|
|
160
|
+
* process it is, without reconstructing PID ancestry by hand. A downstream
|
|
161
|
+
* fork hit exactly this — the message named a PID belonging to a peer
|
|
162
|
+
* session's live browser and offered a flag that would have killed a
|
|
163
|
+
* multi-hour evaluation run.
|
|
164
|
+
*
|
|
165
|
+
* So `--kill-stale-marionette` is offered ONLY for a marionette-driven
|
|
166
|
+
* browser. For a bare launch the message says what it found and stops:
|
|
167
|
+
* FireForge cannot tell a dead run's orphan from someone's live window, and
|
|
168
|
+
* the flag is the right MANUAL clear once ownership is verified, never an
|
|
169
|
+
* automatic one.
|
|
170
|
+
*
|
|
171
|
+
* Exported for direct unit testing.
|
|
172
|
+
*/
|
|
173
|
+
export function describeRunningBundleRefusal(holder) {
|
|
123
174
|
const killHint = process.platform === 'win32'
|
|
124
175
|
? `Stop-Process -Id ${holder.pid} -Force`
|
|
125
176
|
: `kill ${holder.pid} # or "kill -9 ${holder.pid}" if it doesn't exit`;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
177
|
+
const elapsed = formatPsDuration(holder.elapsedSeconds);
|
|
178
|
+
const age = elapsed !== undefined ? ` running for ${elapsed}` : '';
|
|
179
|
+
const harnessDriven = isHarnessDrivenBrowser(holder.commandLine);
|
|
180
|
+
const evidence = `A browser from this project's objdir is already running (PID ${holder.pid}${age}).\n` +
|
|
181
|
+
` command: ${holder.commandLine}\n`;
|
|
182
|
+
if (harnessDriven) {
|
|
183
|
+
return (evidence +
|
|
184
|
+
' This one carries harness arguments (-marionette/-profile), so it is very likely an orphan ' +
|
|
185
|
+
'of a timed-out or interrupted "fireforge test" run, and it can wedge the next headed launch.\n' +
|
|
186
|
+
` Stop it with "${killHint}", or retry with "--kill-stale-marionette".`);
|
|
187
|
+
}
|
|
188
|
+
return (evidence +
|
|
189
|
+
' This one carries NO harness arguments (-marionette/-profile), so it is not a test browser — ' +
|
|
190
|
+
"it is more likely a developer's own window, or another session sharing this checkout.\n" +
|
|
191
|
+
' "--kill-stale-marionette" is deliberately NOT suggested here: FireForge cannot tell an orphan ' +
|
|
192
|
+
"from someone else's live browser. Confirm the owner (start time above, and the command line), " +
|
|
193
|
+
`then stop it yourself with "${killHint}" if it is in fact yours.`);
|
|
129
194
|
}
|
|
130
195
|
/**
|
|
131
196
|
* Returns `true` when the holder's command basename or command-line
|
|
@@ -46,17 +46,13 @@ export declare function runMarionettePreflight(engineDir: string, options?: Mari
|
|
|
46
46
|
export declare function reportMarionettePreflight(result: MarionettePreflightResult): void;
|
|
47
47
|
/**
|
|
48
48
|
* Formats the PASS/FAIL banner as a plain string for direct
|
|
49
|
-
* `process.stdout.write` use —
|
|
50
|
-
* operators running `fireforge test --doctor` under a non-TTY (pipe,
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* we used to ship still lost the summary under some non-TTY flush
|
|
58
|
-
* races. Returning the raw string here lets the caller compose a single
|
|
59
|
-
* authoritative write without any clack layer between the probe and
|
|
60
|
-
* the captured log.
|
|
49
|
+
* `process.stdout.write` use — bypassing the clack logger entirely so
|
|
50
|
+
* operators running `fireforge test --doctor` under a non-TTY (pipe, CI,
|
|
51
|
+
* `tee`-wrapped capture) always see the final line. Clack's renderer can
|
|
52
|
+
* swallow trailing log output just before process exit, and a
|
|
53
|
+
* `success()` + `outro()` + direct-write combination still loses the
|
|
54
|
+
* summary under some non-TTY flush races. Returning the raw string lets the
|
|
55
|
+
* caller compose a single authoritative write with no clack layer between
|
|
56
|
+
* the probe and the captured log.
|
|
61
57
|
*/
|
|
62
58
|
export declare function formatMarionettePreflightLine(result: MarionettePreflightResult): string;
|
|
@@ -19,7 +19,9 @@ import { tmpdir } from 'node:os';
|
|
|
19
19
|
import { join } from 'node:path';
|
|
20
20
|
import { toError } from '../utils/errors.js';
|
|
21
21
|
import { pathExists } from '../utils/fs.js';
|
|
22
|
-
import { info, warn } from '../utils/logger.js';
|
|
22
|
+
import { info, verbose, warn } from '../utils/logger.js';
|
|
23
|
+
import { installGracefulShutdownForwarder, trackChildClosure } from '../utils/process.js';
|
|
24
|
+
import { killProcessTree, sweepProcessGroup } from '../utils/process-group.js';
|
|
23
25
|
import { ensureMach } from './mach.js';
|
|
24
26
|
import { getPython } from './mach-python.js';
|
|
25
27
|
/** Marionette's default TCP port when the browser is launched with `--marionette`. */
|
|
@@ -69,7 +71,7 @@ export async function runMarionettePreflight(engineDir, options = {}) {
|
|
|
69
71
|
const elapsed = () => Date.now() - startedAt;
|
|
70
72
|
// Layer 1: engine directory exists.
|
|
71
73
|
if (!(await pathExists(engineDir))) {
|
|
72
|
-
return fail('engine-present', 'Engine directory not found
|
|
74
|
+
return fail('engine-present', 'Engine directory not found. Run "fireforge download" first.', elapsed());
|
|
73
75
|
}
|
|
74
76
|
// Layer 2: mach binary resolves in the engine.
|
|
75
77
|
try {
|
|
@@ -96,6 +98,19 @@ export async function runMarionettePreflight(engineDir, options = {}) {
|
|
|
96
98
|
}
|
|
97
99
|
let child;
|
|
98
100
|
let stderrTail = '';
|
|
101
|
+
// Shutdown contract for a child spawned outside the exec layer.
|
|
102
|
+
//
|
|
103
|
+
// This is the repo's one legitimate raw `spawn`: the preflight needs the
|
|
104
|
+
// live `ChildProcess` mid-run (`hasChildExited` gates the socket poll),
|
|
105
|
+
// returns while the browser is still running, and aborts on a TCP byte
|
|
106
|
+
// rather than on a deadline — none of which any wrapper in utils/process
|
|
107
|
+
// can express. What it does NOT get to skip is the contract every wrapper
|
|
108
|
+
// applies to a detached child: register the closure so the bin signal
|
|
109
|
+
// handler waits for it, and forward the parent's SIGINT/SIGTERM to the
|
|
110
|
+
// group. Without both, Ctrl+C during `fireforge test --doctor` left the
|
|
111
|
+
// whole mach → Firefox tree running.
|
|
112
|
+
let closure;
|
|
113
|
+
let forwarder;
|
|
99
114
|
try {
|
|
100
115
|
// Layer 5: browser spawns and does not crash within the settle window.
|
|
101
116
|
try {
|
|
@@ -113,13 +128,12 @@ export async function runMarionettePreflight(engineDir, options = {}) {
|
|
|
113
128
|
stdio: ['ignore', 'ignore', 'pipe'],
|
|
114
129
|
// `detached: true` puts the child in a new process group so we can
|
|
115
130
|
// signal it and every descendant (Firefox, its helpers) via
|
|
116
|
-
// `process.kill(-pid, …)` in the finally block. Without
|
|
131
|
+
// `process.kill(-pid, …)` in the finally block. Without it, the
|
|
117
132
|
// child is Python running mach; a SIGTERM kills Python but the
|
|
118
133
|
// Firefox grandchild inherits the stderr pipe FD and keeps Node's
|
|
119
|
-
// event loop alive even after the preflight PASS log
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
// eval finding.
|
|
134
|
+
// event loop alive even after the preflight PASS log, so
|
|
135
|
+
// `fireforge test --doctor` prints `Marionette preflight: PASS`
|
|
136
|
+
// and then hangs indefinitely in `uv__io_poll`.
|
|
123
137
|
detached: true,
|
|
124
138
|
});
|
|
125
139
|
}
|
|
@@ -127,6 +141,10 @@ export async function runMarionettePreflight(engineDir, options = {}) {
|
|
|
127
141
|
return fail('browser-spawns', `Could not spawn mach run: ${toError(error).message}`, elapsed());
|
|
128
142
|
}
|
|
129
143
|
const spawnedChild = child;
|
|
144
|
+
closure = trackChildClosure();
|
|
145
|
+
forwarder = installGracefulShutdownForwarder(spawnedChild, 500, (signal) => {
|
|
146
|
+
killProcessGroup(spawnedChild, signal);
|
|
147
|
+
});
|
|
130
148
|
child.stderr?.on('data', (data) => {
|
|
131
149
|
const chunk = data.toString();
|
|
132
150
|
stderrTail = (stderrTail + chunk).slice(-STDERR_TAIL_LIMIT);
|
|
@@ -164,29 +182,60 @@ export async function runMarionettePreflight(engineDir, options = {}) {
|
|
|
164
182
|
`stderr tail: ${stderrTail.trim().slice(-2_000) || '(empty)'}`, elapsed());
|
|
165
183
|
}
|
|
166
184
|
finally {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
185
|
+
await teardownPreflightChild(child, forwarder, closure, profileDir);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Deterministic teardown for the preflight's detached child.
|
|
190
|
+
*
|
|
191
|
+
* Extracted from {@link runMarionettePreflight} so the probe body stays
|
|
192
|
+
* inside the per-function complexity budget alongside the shutdown contract
|
|
193
|
+
* (forwarder + closure tracking + group sweep) and the kill escalation.
|
|
194
|
+
*
|
|
195
|
+
* @param child - The spawned child, if the spawn got that far
|
|
196
|
+
* @param forwarder - Parent-signal forwarder to dispose
|
|
197
|
+
* @param closure - Closure registration to settle
|
|
198
|
+
* @param profileDir - Temporary profile directory to remove
|
|
199
|
+
*/
|
|
200
|
+
async function teardownPreflightChild(child, forwarder, closure, profileDir) {
|
|
201
|
+
// Dispose before killing: the forwarder's own escalation timer must not
|
|
202
|
+
// race the deterministic teardown below.
|
|
203
|
+
forwarder?.dispose();
|
|
204
|
+
const groupPid = child?.pid;
|
|
205
|
+
if (child && !hasChildExited(child)) {
|
|
206
|
+
killProcessGroup(child, 'SIGTERM');
|
|
207
|
+
// Small escalation: if the child doesn't honour SIGTERM quickly, SIGKILL
|
|
208
|
+
// so we don't leave a ghost mach process around after a failed probe.
|
|
209
|
+
await delay(500);
|
|
210
|
+
if (!hasChildExited(child)) {
|
|
211
|
+
killProcessGroup(child, 'SIGKILL');
|
|
175
212
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
// --doctor` exits cleanly after a passing preflight.
|
|
182
|
-
child?.stderr?.destroy();
|
|
213
|
+
}
|
|
214
|
+
// Reap anything that outlived the group signal. The file's own comments
|
|
215
|
+
// worry about exactly this — Firefox grandchildren surviving the Python
|
|
216
|
+
// mach wrapper — and every exec-layer wrapper sweeps for it.
|
|
217
|
+
if (groupPid !== undefined && process.platform !== 'win32') {
|
|
183
218
|
try {
|
|
184
|
-
await
|
|
219
|
+
await sweepProcessGroup(groupPid);
|
|
185
220
|
}
|
|
186
221
|
catch (error) {
|
|
187
|
-
|
|
222
|
+
verbose(`Marionette preflight group sweep failed: ${toError(error).message}`);
|
|
188
223
|
}
|
|
189
224
|
}
|
|
225
|
+
closure?.settle();
|
|
226
|
+
// Destroy the stderr pipe explicitly. Firefox (a grandchild of the Python
|
|
227
|
+
// mach wrapper we spawned) can inherit and hold the stderr FD even after
|
|
228
|
+
// its direct parent exits — until the pipe closes, Node's readable
|
|
229
|
+
// stream stays attached and `uv__io_poll` keeps the event loop alive.
|
|
230
|
+
// `destroy()` closes the local end regardless, so `fireforge test
|
|
231
|
+
// --doctor` exits cleanly after a passing preflight.
|
|
232
|
+
child?.stderr?.destroy();
|
|
233
|
+
try {
|
|
234
|
+
await rm(profileDir, { recursive: true, force: true });
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
warn(`Could not clean up marionette preflight profile: ${toError(error).message}`);
|
|
238
|
+
}
|
|
190
239
|
}
|
|
191
240
|
/**
|
|
192
241
|
* Sends `signal` to the child's whole process group when possible, falling
|
|
@@ -195,22 +244,11 @@ export async function runMarionettePreflight(engineDir, options = {}) {
|
|
|
195
244
|
* `kill(-pid, …)` is not a supported kernel primitive).
|
|
196
245
|
*/
|
|
197
246
|
function killProcessGroup(child, signal) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
catch {
|
|
204
|
-
// ESRCH / EPERM — fall through to the narrow kill below so we at
|
|
205
|
-
// least signal the direct child.
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
try {
|
|
209
|
-
child.kill(signal);
|
|
210
|
-
}
|
|
211
|
-
catch {
|
|
212
|
-
// Already exited — nothing to do.
|
|
213
|
-
}
|
|
247
|
+
// Delegates to the shared implementation, which is a strict superset of
|
|
248
|
+
// the hand-rolled one this replaced: same POSIX `kill(-pid)` with the same
|
|
249
|
+
// fall-through to a direct `child.kill`, plus a Windows `taskkill /T /F`
|
|
250
|
+
// branch and an already-exited early return.
|
|
251
|
+
killProcessTree(child, signal, process.platform !== 'win32');
|
|
214
252
|
}
|
|
215
253
|
function fail(layer, message, durationMs) {
|
|
216
254
|
return {
|
|
@@ -291,18 +329,14 @@ export function reportMarionettePreflight(result) {
|
|
|
291
329
|
}
|
|
292
330
|
/**
|
|
293
331
|
* Formats the PASS/FAIL banner as a plain string for direct
|
|
294
|
-
* `process.stdout.write` use —
|
|
295
|
-
* operators running `fireforge test --doctor` under a non-TTY (pipe,
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
* we used to ship still lost the summary under some non-TTY flush
|
|
303
|
-
* races. Returning the raw string here lets the caller compose a single
|
|
304
|
-
* authoritative write without any clack layer between the probe and
|
|
305
|
-
* the captured log.
|
|
332
|
+
* `process.stdout.write` use — bypassing the clack logger entirely so
|
|
333
|
+
* operators running `fireforge test --doctor` under a non-TTY (pipe, CI,
|
|
334
|
+
* `tee`-wrapped capture) always see the final line. Clack's renderer can
|
|
335
|
+
* swallow trailing log output just before process exit, and a
|
|
336
|
+
* `success()` + `outro()` + direct-write combination still loses the
|
|
337
|
+
* summary under some non-TTY flush races. Returning the raw string lets the
|
|
338
|
+
* caller compose a single authoritative write with no clack layer between
|
|
339
|
+
* the probe and the captured log.
|
|
306
340
|
*/
|
|
307
341
|
export function formatMarionettePreflightLine(result) {
|
|
308
342
|
const status = result.ok ? 'PASS' : 'FAIL';
|