@hominis/fireforge 0.43.0 → 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 +127 -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 +62 -19
- 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
|
@@ -10,14 +10,19 @@ const DEFAULT_LOCK_TIMEOUT_MS = 30_000;
|
|
|
10
10
|
const DEFAULT_LOCK_POLL_MS = 50;
|
|
11
11
|
const DEFAULT_STALE_LOCK_MS = 5 * 60_000;
|
|
12
12
|
/**
|
|
13
|
-
* Minimum interval between stale-lock probes while a waiter polls.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* the build lock
|
|
17
|
-
*
|
|
18
|
-
*
|
|
13
|
+
* Minimum interval between stale-lock probes while a waiter polls. Probing
|
|
14
|
+
* exactly once per waiter means a holder that dies *after* that probe leaves
|
|
15
|
+
* the waiter polling a permanently dead lock until `timeoutMs` — up to 24
|
|
16
|
+
* hours for the build lock (see `withBuildLock` in mach.ts). Re-probing on a
|
|
17
|
+
* small interval bounds that to seconds while keeping the per-poll cost
|
|
18
|
+
* negligible: one stat plus one small read every ~5 s.
|
|
19
19
|
*/
|
|
20
20
|
const STALE_REPROBE_INTERVAL_MS = 5_000;
|
|
21
|
+
/**
|
|
22
|
+
* Default interval between wait probes (holder + queue) when a caller wants
|
|
23
|
+
* deadline extension but no progress line of its own.
|
|
24
|
+
*/
|
|
25
|
+
const WAIT_PROBE_INTERVAL_MS = 5_000;
|
|
21
26
|
function sleep(ms) {
|
|
22
27
|
return new Promise((resolve) => {
|
|
23
28
|
setTimeout(resolve, ms);
|
|
@@ -41,9 +46,9 @@ export const LOCK_PID_FILE = 'pid';
|
|
|
41
46
|
* missing or the PID does not parse as a finite integer (caller falls back
|
|
42
47
|
* to the age-only staleness heuristic).
|
|
43
48
|
*
|
|
44
|
-
* File format note: external readers (
|
|
45
|
-
*
|
|
46
|
-
*
|
|
49
|
+
* File format note: external readers do `parseInt(content.trim(), 10)`,
|
|
50
|
+
* which parses the leading digits of a multi-line file — so the token line
|
|
51
|
+
* stays compatible.
|
|
47
52
|
*/
|
|
48
53
|
async function readLockOwner(lockPath) {
|
|
49
54
|
try {
|
|
@@ -150,37 +155,34 @@ async function registerWaiter(lockPath, startedAt) {
|
|
|
150
155
|
};
|
|
151
156
|
}
|
|
152
157
|
/**
|
|
153
|
-
* Reads the contended lock's owner and
|
|
154
|
-
*
|
|
155
|
-
*
|
|
158
|
+
* Reads the contended lock's owner and queue in one go. One probe per cycle
|
|
159
|
+
* serves both the progress line and the deadline extension, so the two can
|
|
160
|
+
* never disagree about the position they saw. Failure to read the owner
|
|
161
|
+
* degrades to `holder: undefined` — a diagnostic must never break the wait
|
|
162
|
+
* loop.
|
|
156
163
|
*/
|
|
157
|
-
async function
|
|
164
|
+
async function probeWaitState(lockPath, startedAt) {
|
|
158
165
|
const owner = await readLockOwner(lockPath);
|
|
159
166
|
const holder = owner.present
|
|
160
167
|
? { pid: owner.pid, alive: isProcessAlive(owner.pid), metadata: owner.metadata }
|
|
161
168
|
: undefined;
|
|
162
|
-
|
|
163
|
-
waitedMs,
|
|
164
|
-
timeoutMs,
|
|
165
|
-
holder,
|
|
166
|
-
queue: await readLockQueue(lockPath, startedAt),
|
|
167
|
-
});
|
|
169
|
+
return { holder, queue: await readLockQueue(lockPath, startedAt) };
|
|
168
170
|
}
|
|
169
171
|
/**
|
|
170
172
|
* Reaps a lock directory believed to be stale, safely against concurrent
|
|
171
173
|
* reapers and a concurrent legitimate re-acquisition.
|
|
172
174
|
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
175
|
+
* A naive stat → read-PID → `rm` sequence has a TOCTOU hole: two waiters
|
|
176
|
+
* both observe the dead owner, one removes the stale dir and `mkdir`s its
|
|
177
|
+
* own lock, and the other's already-decided `rm` then deletes the *fresh*
|
|
178
|
+
* lock — two processes inside the critical section at once.
|
|
177
179
|
*
|
|
178
180
|
* Rename-aside closes it: `rename()` is atomic, so exactly one reaper wins
|
|
179
|
-
* ownership of the directory (the loser gets ENOENT and
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* rename),
|
|
181
|
+
* ownership of the directory (the loser gets ENOENT and re-polls), and
|
|
182
|
+
* nothing ever deletes the live path directly. After winning the rename the
|
|
183
|
+
* owner is re-read from the renamed dir — if it is now a *live* process (the
|
|
184
|
+
* lock was released and re-acquired between the staleness read and the
|
|
185
|
+
* rename), it is renamed back instead of reaped.
|
|
184
186
|
*
|
|
185
187
|
* @returns true when the caller should immediately retry `mkdir` (the lock
|
|
186
188
|
* path is now free or was freed by someone else), false when the lock is
|
|
@@ -236,15 +238,12 @@ async function removeIfStaleLock(lockPath, staleMs, onStaleLockMessage) {
|
|
|
236
238
|
try {
|
|
237
239
|
const lockStat = await stat(lockPath);
|
|
238
240
|
const ageMs = Date.now() - lockStat.mtimeMs;
|
|
239
|
-
// Check PID FIRST, independent of age.
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
// and the next command has to either wait 5 minutes for the staleness
|
|
246
|
-
// gate or manually remove the lock directory. With the PID-first check,
|
|
247
|
-
// an explicitly-dead owner unblocks immediately.
|
|
241
|
+
// Check PID FIRST, independent of age. Age-gating everything means a
|
|
242
|
+
// lock younger than `staleMs` (default 5 minutes) is never removed even
|
|
243
|
+
// when its PID file points at a process that has already exited — the
|
|
244
|
+
// situation after SIGINT'ing `furnace preview`, where the signal handler
|
|
245
|
+
// calls `process.exit` and `withFileLock`'s release never runs. With the
|
|
246
|
+
// PID-first check an explicitly-dead owner unblocks immediately.
|
|
248
247
|
const owner = await readLockOwner(lockPath);
|
|
249
248
|
if (owner.present) {
|
|
250
249
|
if (!isProcessAlive(owner.pid)) {
|
|
@@ -276,16 +275,15 @@ async function removeIfStaleLock(lockPath, staleMs, onStaleLockMessage) {
|
|
|
276
275
|
}
|
|
277
276
|
/**
|
|
278
277
|
* Releases a lock previously acquired by this process, verifying ownership
|
|
279
|
-
* first.
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
278
|
+
* first. An unconditional `finally { rm }` would remove *whichever* lock
|
|
279
|
+
* directory is present — if a stale-lock reaper had (in a pathological race)
|
|
280
|
+
* replaced our lock, that deletes the new owner's lock and compounds the
|
|
281
|
+
* double-acquisition. When the PID file no longer names us, warn and leave
|
|
282
|
+
* the directory alone.
|
|
284
283
|
*
|
|
285
284
|
* `ownerFileWritten` is false when our own owner-file write failed at
|
|
286
|
-
* acquisition (non-fatal there); ownership then cannot be verified and
|
|
287
|
-
*
|
|
288
|
-
* and strictly better than leaking the lock.
|
|
285
|
+
* acquisition (non-fatal there); ownership then cannot be verified and the
|
|
286
|
+
* removal is unconditional, which is strictly better than leaking the lock.
|
|
289
287
|
*/
|
|
290
288
|
async function releaseLock(lockPath, ownerToken, ownerFileWritten) {
|
|
291
289
|
if (ownerFileWritten) {
|
|
@@ -299,31 +297,130 @@ async function releaseLock(lockPath, ownerToken, ownerFileWritten) {
|
|
|
299
297
|
}
|
|
300
298
|
await rm(lockPath, { recursive: true, force: true });
|
|
301
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Removes a lock directory only if this process still owns it, for callers
|
|
302
|
+
* that hold no acquisition token.
|
|
303
|
+
*
|
|
304
|
+
* The ownership half of {@link releaseLock}, minus the token check — a
|
|
305
|
+
* signal-time sweeper knows which lock paths its own operations opened, but
|
|
306
|
+
* not the per-acquisition UUID those acquisitions minted. The PID check is
|
|
307
|
+
* still the one that matters: it stops the sweeper deleting a lock a
|
|
308
|
+
* DIFFERENT process acquired in the window between our operation dying and
|
|
309
|
+
* the sweep running.
|
|
310
|
+
*
|
|
311
|
+
* Never throws: callers are shutdown paths where a slow or failing I/O must
|
|
312
|
+
* not prevent the process from exiting. Returns whether the directory was
|
|
313
|
+
* removed so callers can log accurately.
|
|
314
|
+
*
|
|
315
|
+
* @param lockPath - Lock directory to remove
|
|
316
|
+
* @returns True when the lock was removed, false when it was left in place
|
|
317
|
+
*/
|
|
318
|
+
export async function releaseLockIfOwned(lockPath) {
|
|
319
|
+
try {
|
|
320
|
+
const owner = await readLockOwner(lockPath);
|
|
321
|
+
if (owner.present && owner.pid !== process.pid) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
302
331
|
/**
|
|
303
332
|
* Builds the contention refusal thrown when the wait budget expires.
|
|
304
333
|
*
|
|
305
334
|
* Typed as {@link LockContentionError} (a `FireForgeError`) so the CLI
|
|
306
335
|
* boundary prints the refusal as one clean line instead of an "Unexpected
|
|
307
|
-
* error" stack. Callers supplying `onTimeoutMessage` still get
|
|
308
|
-
*
|
|
309
|
-
*
|
|
336
|
+
* error" stack. Callers supplying `onTimeoutMessage` still get the holder
|
|
337
|
+
* identification appended — the reason-first copy stays theirs, the "who
|
|
338
|
+
* holds it" is ours.
|
|
310
339
|
*/
|
|
311
|
-
async function buildLockTimeoutError(lockPath, onTimeoutMessage, cause) {
|
|
340
|
+
async function buildLockTimeoutError(lockPath, onTimeoutMessage, cause, queue) {
|
|
312
341
|
const owner = await readLockOwner(lockPath);
|
|
313
342
|
const liveHolder = owner.present && isProcessAlive(owner.pid) ? owner : undefined;
|
|
314
343
|
const details = liveHolder !== undefined && liveHolder.metadata.length > 0
|
|
315
344
|
? ` (${liveHolder.metadata.join(', ')})`
|
|
316
345
|
: '';
|
|
346
|
+
const position = formatQueuePositionReached(queue);
|
|
317
347
|
if (onTimeoutMessage !== undefined) {
|
|
318
348
|
const identifiedHolder = liveHolder !== undefined
|
|
319
349
|
? ` The lock is held by PID ${String(liveHolder.pid)}${details}.`
|
|
320
350
|
: '';
|
|
321
|
-
return new LockContentionError(`${onTimeoutMessage}${identifiedHolder}`, cause);
|
|
351
|
+
return new LockContentionError(`${onTimeoutMessage}${identifiedHolder}${position}`, cause);
|
|
322
352
|
}
|
|
323
353
|
const holderHint = liveHolder !== undefined
|
|
324
354
|
? ` It is held by running process ${String(liveHolder.pid)}${details} — wait for it to finish, or stop it and retry.`
|
|
325
355
|
: ' If no other FireForge process is running, remove the lock directory and retry.';
|
|
326
|
-
return new LockContentionError(`Timed out waiting for file lock ${lockPath}.${holderHint}`, cause);
|
|
356
|
+
return new LockContentionError(`Timed out waiting for file lock ${lockPath}.${holderHint}${position}`, cause);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* States the queue position the expired wait actually reached.
|
|
360
|
+
*
|
|
361
|
+
* Silence here is what made the worst outcome invisible: a wait that expires
|
|
362
|
+
* one position from the head has paid the whole budget for nothing, and the
|
|
363
|
+
* operator cannot tell that from a queue that never moved. Naming the
|
|
364
|
+
* position turns "raise the budget" into an informed decision instead of a
|
|
365
|
+
* guess. Omitted when no queue was observed — an empty registry is not
|
|
366
|
+
* evidence of an empty queue.
|
|
367
|
+
*/
|
|
368
|
+
function formatQueuePositionReached(queue) {
|
|
369
|
+
if (queue === undefined || queue.depth === 0)
|
|
370
|
+
return '';
|
|
371
|
+
if (queue.ahead === 0) {
|
|
372
|
+
return ` You were next in a queue of ${String(queue.depth)} when the wait expired — a larger --wait-lock would very likely have got a turn.`;
|
|
373
|
+
}
|
|
374
|
+
return ` You were still ${String(queue.ahead)} from the head of a queue of ${String(queue.depth)} when the wait expired; re-run with a larger --wait-lock.`;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Runs one wait probe: reads holder and queue, and reports progress when the
|
|
378
|
+
* caller asked for it. Split out of the wait loop so the loop stays a
|
|
379
|
+
* readable state machine.
|
|
380
|
+
*/
|
|
381
|
+
async function runWaitProbe(lockPath, startedAt, timeoutMs, options) {
|
|
382
|
+
const { holder, queue } = await probeWaitState(lockPath, startedAt);
|
|
383
|
+
options.onWaitProgress?.({
|
|
384
|
+
waitedMs: Date.now() - startedAt,
|
|
385
|
+
timeoutMs,
|
|
386
|
+
holder,
|
|
387
|
+
queue,
|
|
388
|
+
});
|
|
389
|
+
return { queue };
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Lock directories this process currently holds.
|
|
393
|
+
*
|
|
394
|
+
* `withFileLock`'s `finally` never runs across a `process.exit`, so a
|
|
395
|
+
* SIGTERM'd command leaves its lock on disk. The next contender reclaims it
|
|
396
|
+
* immediately (the PID-first staleness check sees a dead owner), but in the
|
|
397
|
+
* window between, `fireforge status --lock` reports the lock held by a
|
|
398
|
+
* process that is not running — which is exactly the question an operator
|
|
399
|
+
* asks after a kill. The signal sweep closes that window.
|
|
400
|
+
*
|
|
401
|
+
* Paths only: the per-acquisition token lives in `withFileLock`'s frame and
|
|
402
|
+
* a sweeper cannot see it. {@link releaseLockIfOwned} checks the PID, which
|
|
403
|
+
* is the check that matters — it stops the sweep deleting a lock a
|
|
404
|
+
* DIFFERENT process acquired in the meantime.
|
|
405
|
+
*/
|
|
406
|
+
const heldLocks = new Set();
|
|
407
|
+
/**
|
|
408
|
+
* Releases every lock this process still holds, for the signal path.
|
|
409
|
+
*
|
|
410
|
+
* Never throws and never rejects: a shutdown path must not be able to keep
|
|
411
|
+
* the process alive. Returns the paths actually removed so the caller can
|
|
412
|
+
* report accurately.
|
|
413
|
+
*/
|
|
414
|
+
export async function forceReleaseHeldLocksForSignal() {
|
|
415
|
+
const paths = [...heldLocks];
|
|
416
|
+
const removed = [];
|
|
417
|
+
for (const lockPath of paths) {
|
|
418
|
+
heldLocks.delete(lockPath);
|
|
419
|
+
if (await releaseLockIfOwned(lockPath)) {
|
|
420
|
+
removed.push(lockPath);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return removed;
|
|
327
424
|
}
|
|
328
425
|
/** Runs an async operation while holding a directory lock, with stale-lock recovery. */
|
|
329
426
|
export async function withFileLock(lockPath, operation, options = {}) {
|
|
@@ -332,12 +429,24 @@ export async function withFileLock(lockPath, operation, options = {}) {
|
|
|
332
429
|
const staleMs = options.staleMs ?? DEFAULT_STALE_LOCK_MS;
|
|
333
430
|
const staleReprobeMs = options.staleReprobeMs ?? STALE_REPROBE_INTERVAL_MS;
|
|
334
431
|
const startedAt = Date.now();
|
|
335
|
-
const
|
|
432
|
+
const extend = options.extendWhileAdvancing;
|
|
433
|
+
const hardDeadline = extend !== undefined ? startedAt + extend.maxWaitMs : undefined;
|
|
434
|
+
let deadline = startedAt + timeoutMs;
|
|
336
435
|
let lastStaleProbeAt;
|
|
337
436
|
let currentPollMs = pollMs;
|
|
338
|
-
let
|
|
437
|
+
let lastProbeAt = startedAt;
|
|
438
|
+
/** Best (lowest) queue position seen so far; used to detect an advance. */
|
|
439
|
+
let bestAhead;
|
|
440
|
+
/** Last observed queue state, so the timeout refusal can name the position reached. */
|
|
441
|
+
let lastQueue;
|
|
339
442
|
/** Set on first contention so an uncontended acquire costs nothing. */
|
|
340
443
|
let deregisterWaiter;
|
|
444
|
+
// One cadence drives both the progress line and the deadline extension.
|
|
445
|
+
// Either feature alone is enough to want the probe; neither is worth a
|
|
446
|
+
// second timer.
|
|
447
|
+
const probeIntervalMs = options.waitProgressMs ?? WAIT_PROBE_INTERVAL_MS;
|
|
448
|
+
const probeWanted = (options.onWaitProgress !== undefined && options.waitProgressMs !== undefined) ||
|
|
449
|
+
extend !== undefined;
|
|
341
450
|
await ensureDir(dirname(lockPath));
|
|
342
451
|
try {
|
|
343
452
|
for (;;) {
|
|
@@ -360,13 +469,39 @@ export async function withFileLock(lockPath, operation, options = {}) {
|
|
|
360
469
|
}
|
|
361
470
|
}
|
|
362
471
|
if (Date.now() >= deadline) {
|
|
363
|
-
throw await buildLockTimeoutError(lockPath, options.onTimeoutMessage, error);
|
|
472
|
+
throw await buildLockTimeoutError(lockPath, options.onTimeoutMessage, error, lastQueue);
|
|
364
473
|
}
|
|
365
|
-
if (
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
474
|
+
if (probeWanted && Date.now() - lastProbeAt >= probeIntervalMs) {
|
|
475
|
+
lastProbeAt = Date.now();
|
|
476
|
+
// Report the budget that is CURRENTLY in force, not the one
|
|
477
|
+
// originally requested: once an extension has been granted, the
|
|
478
|
+
// requested figure is stale, and a progress line that keeps
|
|
479
|
+
// quoting it reads as a timeout that does not fire.
|
|
480
|
+
const probe = await runWaitProbe(lockPath, startedAt, deadline - startedAt, options);
|
|
481
|
+
lastQueue = probe.queue;
|
|
482
|
+
// An advance — someone ahead of us took their turn and finished —
|
|
483
|
+
// buys a fresh budget, because the wait is working. Only a queue
|
|
484
|
+
// that stops moving starves, which is the case the timeout is for.
|
|
485
|
+
//
|
|
486
|
+
// The FIRST observation is a seed, never an advance. `bestAhead`
|
|
487
|
+
// used to start `undefined`, so the opening probe always satisfied
|
|
488
|
+
// this test and handed out a free full budget: a `--wait-lock 300`
|
|
489
|
+
// run outlived its 300 s even against a queue that never moved
|
|
490
|
+
// once, which is exactly the "it does not reliably give up after N
|
|
491
|
+
// seconds" a downstream fork reported.
|
|
492
|
+
if (hardDeadline !== undefined) {
|
|
493
|
+
if (bestAhead === undefined) {
|
|
494
|
+
bestAhead = probe.queue.ahead;
|
|
495
|
+
}
|
|
496
|
+
else if (probe.queue.ahead < bestAhead) {
|
|
497
|
+
bestAhead = probe.queue.ahead;
|
|
498
|
+
deadline = Math.min(Date.now() + timeoutMs, hardDeadline);
|
|
499
|
+
options.onWaitExtended?.({
|
|
500
|
+
ahead: probe.queue.ahead,
|
|
501
|
+
budgetMs: deadline - startedAt,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
370
505
|
}
|
|
371
506
|
await sleep(currentPollMs);
|
|
372
507
|
if (options.pollMaxMs !== undefined) {
|
|
@@ -396,10 +531,12 @@ export async function withFileLock(lockPath, operation, options = {}) {
|
|
|
396
531
|
// Non-fatal: stale recovery falls back to age-only heuristic and release
|
|
397
532
|
// falls back to unconditional removal.
|
|
398
533
|
}
|
|
534
|
+
heldLocks.add(lockPath);
|
|
399
535
|
try {
|
|
400
536
|
return await operation();
|
|
401
537
|
}
|
|
402
538
|
finally {
|
|
539
|
+
heldLocks.delete(lockPath);
|
|
403
540
|
await releaseLock(lockPath, ownerToken, ownerFileWritten);
|
|
404
541
|
}
|
|
405
542
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
6
|
import { createReadStream } from 'node:fs';
|
|
7
|
-
import { rename } from 'node:fs/promises';
|
|
7
|
+
import { readdir, rename, stat } from 'node:fs/promises';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { pipeline } from 'node:stream/promises';
|
|
10
10
|
import { ChecksumMismatchError } from '../errors/download.js';
|
|
@@ -108,7 +108,6 @@ async function validateCachedArchive(archive, cacheDir, expectedSha256) {
|
|
|
108
108
|
return false;
|
|
109
109
|
}
|
|
110
110
|
if (metadata.contentLength !== undefined) {
|
|
111
|
-
const { stat } = await import('node:fs/promises');
|
|
112
111
|
const archiveStats = await stat(tarballPath);
|
|
113
112
|
if (archiveStats.size !== metadata.contentLength) {
|
|
114
113
|
return false;
|
|
@@ -213,7 +212,6 @@ export async function invalidateArchiveCache(archive, cacheDir) {
|
|
|
213
212
|
async function removeArchivePartFiles(archive, cacheDir) {
|
|
214
213
|
const partPrefix = `${archive.filename}.part`;
|
|
215
214
|
try {
|
|
216
|
-
const { readdir } = await import('node:fs/promises');
|
|
217
215
|
const entries = await readdir(cacheDir);
|
|
218
216
|
await Promise.all(entries
|
|
219
217
|
.filter((name) => name.startsWith(partPrefix))
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { ExtractionError } from '../errors/download.js';
|
|
7
7
|
import { elapsedSince } from '../utils/elapsed.js';
|
|
8
|
-
import { ensureDir, pathExists } from '../utils/fs.js';
|
|
8
|
+
import { ensureDir, pathExists, readText } from '../utils/fs.js';
|
|
9
9
|
import { exec, execStream, executableExists } from '../utils/process.js';
|
|
10
10
|
/**
|
|
11
11
|
* Returns true when an archive member path could land outside the
|
|
@@ -81,11 +81,8 @@ export function findUnsafeArchiveLink(verboseLines) {
|
|
|
81
81
|
* so a date-shaped owner name could shift the column boundary and hide an
|
|
82
82
|
* absolute member name from the check. Each listing pass costs one full
|
|
83
83
|
* decompression, so the two run concurrently and overlap almost perfectly
|
|
84
|
-
* (
|
|
85
|
-
*
|
|
86
|
-
* 22.1 s, both concurrent 22 s wall, extraction itself 58 s (file-creation
|
|
87
|
-
* syscalls dominate it, not decompression) — so the preflight adds ~22 s
|
|
88
|
-
* (~38%) to this one-time extraction step, versus ~44 s if run
|
|
84
|
+
* (CPU-bound on separate cores): on a 601 MB Firefox ESR source tarball the
|
|
85
|
+
* preflight adds ~22 s to a ~58 s extraction, versus ~44 s if run
|
|
89
86
|
* sequentially.
|
|
90
87
|
*/
|
|
91
88
|
/**
|
|
@@ -116,13 +113,13 @@ function createLineScanner(onLine) {
|
|
|
116
113
|
* Runs one tar listing pass, streaming lines through `checkLine` and
|
|
117
114
|
* returning the first unsafe finding (or null) plus the exit code.
|
|
118
115
|
*
|
|
119
|
-
* Streaming matters for safety, not just memory:
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
116
|
+
* Streaming matters for safety, not just memory: a buffered `exec` collector
|
|
117
|
+
* silently truncates at 50 MB, and a full Firefox `-tvf` listing already
|
|
118
|
+
* sits in the 40–50 MB range — so a buffered implementation scans only the
|
|
119
|
+
* head of the listing while reporting the archive safe. A crafted archive
|
|
120
|
+
* could exploit exactly that by padding benign entries first and hiding a
|
|
121
|
+
* traversal name or absolute link target past the cap. Per-line streaming
|
|
122
|
+
* scans EVERY entry with bounded memory.
|
|
126
123
|
*/
|
|
127
124
|
async function runListingScan(archivePath, tarFlag, checkLine) {
|
|
128
125
|
// LC_ALL=C keeps the -tvf column format stable for the link parse.
|
|
@@ -213,7 +210,6 @@ export async function getFirefoxVersion(engineDir) {
|
|
|
213
210
|
if (!(await pathExists(versionPath))) {
|
|
214
211
|
return undefined;
|
|
215
212
|
}
|
|
216
|
-
const { readText } = await import('../utils/fs.js');
|
|
217
213
|
const version = await readText(versionPath);
|
|
218
214
|
return version.trim();
|
|
219
215
|
}
|
|
@@ -27,11 +27,11 @@ export declare function getDownloadUrl(version: string, product?: FirefoxProduct
|
|
|
27
27
|
export declare function getTarballFilename(version: string, product?: FirefoxProduct, candidate?: string): string;
|
|
28
28
|
/**
|
|
29
29
|
* Lifecycle phase reported by {@link downloadFirefoxSource}. The download
|
|
30
|
-
* CLI command uses this to swap spinners between the bytes-on-the-wire
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
30
|
+
* CLI command uses this to swap spinners between the bytes-on-the-wire phase
|
|
31
|
+
* and the silent tar-xz decompression phase that follows: a single spinner
|
|
32
|
+
* covering both sticks at "Downloading Firefox … 100%" and makes first-run
|
|
33
|
+
* setup look hung precisely when the archive is already on disk and `tar` is
|
|
34
|
+
* the long pole.
|
|
35
35
|
*/
|
|
36
36
|
export type FirefoxSourcePhase = 'download' | 'extract';
|
|
37
37
|
/** Callback fired at phase transitions during {@link downloadFirefoxSource}. */
|
|
@@ -59,6 +59,8 @@ export declare function sweepOrphanedEngineWorkDirs(destDir: string): Promise<st
|
|
|
59
59
|
* @param onPhase - Optional callback fired when the function transitions
|
|
60
60
|
* between phases (`'download'` → `'extract'`). Fires exactly once per
|
|
61
61
|
* phase even if the cached archive path skips the wire entirely.
|
|
62
|
+
* @param expectedSha256 - Expected archive checksum; verified after download when supplied
|
|
63
|
+
* @param onPhaseProgress - Called with byte progress within the current phase
|
|
62
64
|
* @param candidate - Optional release-candidate build directory (e.g.
|
|
63
65
|
* "build2") resolving the archive under `candidates/` instead of
|
|
64
66
|
* `releases/`.
|
package/dist/src/core/firefox.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* {@link downloadFirefoxSource} orchestrator.
|
|
7
7
|
*/
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
|
-
import { rename } from 'node:fs/promises';
|
|
10
|
-
import { join } from 'node:path';
|
|
9
|
+
import { readdir, rename } from 'node:fs/promises';
|
|
10
|
+
import { basename, dirname, join } from 'node:path';
|
|
11
11
|
import { getNodeErrorCode, toError } from '../utils/errors.js';
|
|
12
12
|
import { ensureDir, removeDir } from '../utils/fs.js';
|
|
13
13
|
import { resolveArchive } from './firefox-archive.js';
|
|
@@ -72,8 +72,6 @@ function isLikelyArchiveCorruptionError(error) {
|
|
|
72
72
|
* @returns names of the directories that were removed
|
|
73
73
|
*/
|
|
74
74
|
export async function sweepOrphanedEngineWorkDirs(destDir) {
|
|
75
|
-
const { readdir } = await import('node:fs/promises');
|
|
76
|
-
const { basename, dirname } = await import('node:path');
|
|
77
75
|
const parent = dirname(destDir);
|
|
78
76
|
const enginePrefixTmp = `${basename(destDir)}.tmp-`;
|
|
79
77
|
const enginePrefixReplacement = `${basename(destDir)}.replacement-`;
|
|
@@ -105,6 +103,8 @@ export async function sweepOrphanedEngineWorkDirs(destDir) {
|
|
|
105
103
|
* @param onPhase - Optional callback fired when the function transitions
|
|
106
104
|
* between phases (`'download'` → `'extract'`). Fires exactly once per
|
|
107
105
|
* phase even if the cached archive path skips the wire entirely.
|
|
106
|
+
* @param expectedSha256 - Expected archive checksum; verified after download when supplied
|
|
107
|
+
* @param onPhaseProgress - Called with byte progress within the current phase
|
|
108
108
|
* @param candidate - Optional release-candidate build directory (e.g.
|
|
109
109
|
* "build2") resolving the archive under `candidates/` instead of
|
|
110
110
|
* `releases/`.
|
|
@@ -136,7 +136,6 @@ export async function downloadFirefoxSource(version, product, destDir, cacheDir,
|
|
|
136
136
|
}
|
|
137
137
|
// Firefox source extracts to a subdirectory (e.g., firefox-140.0/)
|
|
138
138
|
// Find it dynamically since ESR versions may have different naming
|
|
139
|
-
const { readdir } = await import('node:fs/promises');
|
|
140
139
|
const entries = await readdir(tempDir, { withFileTypes: true });
|
|
141
140
|
const extractedSubdir = entries.find((entry) => entry.isDirectory() && entry.name.startsWith('firefox-'));
|
|
142
141
|
if (extractedSubdir) {
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* `furnace-apply-helpers.ts` so the apply path and its dry-run mirror each
|
|
4
4
|
* stay within the per-file line budget. Consumed only by that module.
|
|
5
5
|
*/
|
|
6
|
-
import type { CustomComponentConfig, DryRunAction, StepError } from '../types/furnace.js';
|
|
6
|
+
import type { ComponentApplyContext, CustomComponentConfig, DryRunAction, StepError } from '../types/furnace.js';
|
|
7
7
|
interface DirectoryEntry {
|
|
8
8
|
isFile(): boolean;
|
|
9
9
|
isSymbolicLink?(): boolean;
|
|
10
10
|
name: string;
|
|
11
11
|
}
|
|
12
12
|
/** Computes the planned dry-run actions (and pre-flight step errors) for a custom component. */
|
|
13
|
-
export declare function buildCustomDryRunActions(
|
|
13
|
+
export declare function buildCustomDryRunActions(ctx: Pick<ComponentApplyContext, 'engineDir' | 'name' | 'componentDir' | 'ftlDir'>, config: CustomComponentConfig, targetDir: string, entries: DirectoryEntry[]): Promise<{
|
|
14
14
|
actions: DryRunAction[];
|
|
15
15
|
stepErrors: StepError[];
|
|
16
16
|
}>;
|
|
@@ -18,7 +18,8 @@ function isRegularFile(entry) {
|
|
|
18
18
|
return true;
|
|
19
19
|
}
|
|
20
20
|
/** Computes the planned dry-run actions (and pre-flight step errors) for a custom component. */
|
|
21
|
-
export async function buildCustomDryRunActions(
|
|
21
|
+
export async function buildCustomDryRunActions(ctx, config, targetDir, entries) {
|
|
22
|
+
const { engineDir, name, componentDir, ftlDir } = ctx;
|
|
22
23
|
const actions = [];
|
|
23
24
|
const stepErrors = [];
|
|
24
25
|
for (const entry of entries) {
|
|
@@ -60,7 +61,7 @@ export async function buildCustomDryRunActions(name, componentDir, engineDir, co
|
|
|
60
61
|
// A sharedFtl widget owns its strings via the shared bundle; surface the
|
|
61
62
|
// removal of any dangling per-widget locale jar.mn entry so the dry-run
|
|
62
63
|
// plan matches what apply will do (and explains the unblocked build).
|
|
63
|
-
const pruneAction = await describeSharedFtlPrune(
|
|
64
|
+
const pruneAction = await describeSharedFtlPrune(ctx, config);
|
|
64
65
|
if (pruneAction) {
|
|
65
66
|
actions.push(pruneAction);
|
|
66
67
|
}
|
|
@@ -6,26 +6,24 @@
|
|
|
6
6
|
* Every helper here degrades gracefully: if the locale jar.mn is missing or
|
|
7
7
|
* the FTL tree is non-standard, apply logs an ADVISORY `stepError`
|
|
8
8
|
* (`advisory: true`) rather than aborting the whole command. Advisory step
|
|
9
|
-
* errors are reported as warnings and never trigger rollback — missing
|
|
10
|
-
* jar.mn on a fork without a locale package
|
|
11
|
-
* `.mjs`/`.css` from shipping
|
|
12
|
-
* this contract used to be contradicted: any FTL step error rolled back
|
|
13
|
-
* the whole apply).
|
|
9
|
+
* errors are reported as warnings and never trigger rollback — a missing
|
|
10
|
+
* jar.mn on a fork without a locale package must not block a working
|
|
11
|
+
* `.mjs`/`.css` from shipping.
|
|
14
12
|
*/
|
|
15
|
-
import type { CustomComponentConfig, DryRunAction, StepError } from '../types/furnace.js';
|
|
13
|
+
import type { ComponentApplyContext, CustomComponentConfig, DryRunAction, StepError } from '../types/furnace.js';
|
|
16
14
|
import { type RollbackJournal } from './furnace-rollback.js';
|
|
17
15
|
/**
|
|
18
16
|
* Apply-path wrapper around {@link pruneSharedFtlPerWidgetLocaleEntry} that
|
|
19
17
|
* records the affected path / step error in the caller's collectors, mirroring
|
|
20
18
|
* {@link applyCustomFtlFile}'s contract so the main apply helper stays terse.
|
|
21
19
|
*/
|
|
22
|
-
export declare function applySharedFtlPrune(
|
|
20
|
+
export declare function applySharedFtlPrune(ctx: Pick<ComponentApplyContext, 'engineDir' | 'name' | 'ftlDir'>, config: CustomComponentConfig, affectedPaths: string[], stepErrors: StepError[], rollbackJournal?: RollbackJournal): Promise<void>;
|
|
23
21
|
/**
|
|
24
22
|
* Read-only dry-run describer for {@link pruneSharedFtlPerWidgetLocaleEntry}:
|
|
25
23
|
* returns an action when a dangling per-widget locale entry exists for a
|
|
26
24
|
* `sharedFtl` widget, else `undefined`.
|
|
27
25
|
*/
|
|
28
|
-
export declare function describeSharedFtlPrune(
|
|
26
|
+
export declare function describeSharedFtlPrune(ctx: Pick<ComponentApplyContext, 'engineDir' | 'name' | 'ftlDir'>, config: CustomComponentConfig): Promise<DryRunAction | undefined>;
|
|
29
27
|
/**
|
|
30
28
|
* Copies a component's `.ftl` into the FTL tree and registers the chrome URI
|
|
31
29
|
* in the locale jar.mn.
|
|
@@ -34,7 +32,7 @@ export declare function describeSharedFtlPrune(engineDir: string, name: string,
|
|
|
34
32
|
* stepErrors rather than thrown — a well-formed `.mjs`/`.css` must never be
|
|
35
33
|
* blocked by a broken locale path.
|
|
36
34
|
*/
|
|
37
|
-
export declare function applyCustomFtlFile(
|
|
35
|
+
export declare function applyCustomFtlFile(ctx: Pick<ComponentApplyContext, 'engineDir' | 'name' | 'componentDir' | 'ftlDir'>, affectedPaths: string[], stepErrors: StepError[], rollbackJournal?: RollbackJournal): Promise<void>;
|
|
38
36
|
/**
|
|
39
37
|
* Returns a dry-run action for registering a locale jar.mn entry for the
|
|
40
38
|
* `.ftl` that `applyCustomFtlFile` would write. `undefined` when the FTL
|
|
@@ -7,11 +7,9 @@
|
|
|
7
7
|
* Every helper here degrades gracefully: if the locale jar.mn is missing or
|
|
8
8
|
* the FTL tree is non-standard, apply logs an ADVISORY `stepError`
|
|
9
9
|
* (`advisory: true`) rather than aborting the whole command. Advisory step
|
|
10
|
-
* errors are reported as warnings and never trigger rollback — missing
|
|
11
|
-
* jar.mn on a fork without a locale package
|
|
12
|
-
* `.mjs`/`.css` from shipping
|
|
13
|
-
* this contract used to be contradicted: any FTL step error rolled back
|
|
14
|
-
* the whole apply).
|
|
10
|
+
* errors are reported as warnings and never trigger rollback — a missing
|
|
11
|
+
* jar.mn on a fork without a locale package must not block a working
|
|
12
|
+
* `.mjs`/`.css` from shipping.
|
|
15
13
|
*/
|
|
16
14
|
import { join, relative } from 'node:path';
|
|
17
15
|
import { toError } from '../utils/errors.js';
|
|
@@ -46,10 +44,9 @@ function resolveSharedFtlPruneTarget(name, ftlDir) {
|
|
|
46
44
|
* A `localized: true` widget that opts into a feature-scoped `sharedFtl`
|
|
47
45
|
* bundle (its strings live under `browser/...` and load via
|
|
48
46
|
* `insertFTLIfNeeded`) must NOT carry a per-widget
|
|
49
|
-
* `locale/@AB_CD@/<chromeSubPath>/<name>.ftl` line. Such a line
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* hard (`Cannot find <chromeSubPath>/<name>.ftl`) and blocks every build.
|
|
47
|
+
* `locale/@AB_CD@/<chromeSubPath>/<name>.ftl` line. Such a line points at a
|
|
48
|
+
* `.ftl` that does not exist, so `mach build` fails hard (`Cannot find
|
|
49
|
+
* <chromeSubPath>/<name>.ftl`) and blocks every build.
|
|
53
50
|
*
|
|
54
51
|
* The pruned line is the per-widget toolkit entry only; the shared bundle's
|
|
55
52
|
* own line (a different chrome sub-path / base name) is never matched, so
|
|
@@ -83,7 +80,8 @@ async function pruneSharedFtlPerWidgetLocaleEntry(engineDir, name, ftlDir, confi
|
|
|
83
80
|
* records the affected path / step error in the caller's collectors, mirroring
|
|
84
81
|
* {@link applyCustomFtlFile}'s contract so the main apply helper stays terse.
|
|
85
82
|
*/
|
|
86
|
-
export async function applySharedFtlPrune(
|
|
83
|
+
export async function applySharedFtlPrune(ctx, config, affectedPaths, stepErrors, rollbackJournal) {
|
|
84
|
+
const { engineDir, name, ftlDir } = ctx;
|
|
87
85
|
try {
|
|
88
86
|
const prunedPath = await pruneSharedFtlPerWidgetLocaleEntry(engineDir, name, ftlDir, config, rollbackJournal);
|
|
89
87
|
if (prunedPath)
|
|
@@ -98,7 +96,8 @@ export async function applySharedFtlPrune(engineDir, name, ftlDir, config, affec
|
|
|
98
96
|
* returns an action when a dangling per-widget locale entry exists for a
|
|
99
97
|
* `sharedFtl` widget, else `undefined`.
|
|
100
98
|
*/
|
|
101
|
-
export async function describeSharedFtlPrune(
|
|
99
|
+
export async function describeSharedFtlPrune(ctx, config) {
|
|
100
|
+
const { engineDir, name, ftlDir } = ctx;
|
|
102
101
|
if (!config.sharedFtl)
|
|
103
102
|
return undefined;
|
|
104
103
|
const target = resolveSharedFtlPruneTarget(name, ftlDir);
|
|
@@ -123,7 +122,8 @@ export async function describeSharedFtlPrune(engineDir, name, ftlDir, config) {
|
|
|
123
122
|
* stepErrors rather than thrown — a well-formed `.mjs`/`.css` must never be
|
|
124
123
|
* blocked by a broken locale path.
|
|
125
124
|
*/
|
|
126
|
-
export async function applyCustomFtlFile(
|
|
125
|
+
export async function applyCustomFtlFile(ctx, affectedPaths, stepErrors, rollbackJournal) {
|
|
126
|
+
const { engineDir, name, componentDir, ftlDir } = ctx;
|
|
127
127
|
const ftlFile = `${name}.ftl`;
|
|
128
128
|
const ftlSrc = join(componentDir, ftlFile);
|
|
129
129
|
if (!(await pathExists(ftlSrc)))
|