@hominis/fireforge 0.44.4 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +120 -0
- package/dist/bin/fireforge.js +6 -0
- package/dist/build-info.json +4 -4
- package/dist/src/cli-usage-error.d.ts +46 -0
- package/dist/src/cli-usage-error.js +81 -0
- package/dist/src/cli.d.ts +5 -1
- package/dist/src/cli.js +20 -21
- package/dist/src/commands/bootstrap.js +1 -2
- package/dist/src/commands/build.js +15 -6
- package/dist/src/commands/destructive-decision.d.ts +19 -0
- package/dist/src/commands/destructive-decision.js +31 -0
- package/dist/src/commands/doctor-check-core.d.ts +0 -6
- package/dist/src/commands/doctor-check-core.js +0 -8
- package/dist/src/commands/doctor-orphaned-harness.d.ts +0 -6
- package/dist/src/commands/doctor-orphaned-harness.js +4 -12
- package/dist/src/commands/doctor-post-rebase-audit.js +1 -1
- package/dist/src/commands/doctor-source-pin.d.ts +29 -0
- package/dist/src/commands/doctor-source-pin.js +81 -0
- package/dist/src/commands/doctor.d.ts +2 -0
- package/dist/src/commands/doctor.js +9 -8
- package/dist/src/commands/download.js +35 -24
- package/dist/src/commands/export-all.js +21 -4
- package/dist/src/commands/export-flow.js +15 -60
- package/dist/src/commands/export-placement-conflicts.js +3 -3
- package/dist/src/commands/export-placement-gate.js +2 -8
- package/dist/src/commands/export-placement-policy.js +3 -5
- package/dist/src/commands/export-shared.d.ts +42 -23
- package/dist/src/commands/export-shared.js +4 -19
- package/dist/src/commands/export.js +10 -1
- package/dist/src/commands/furnace/authoring-config.d.ts +13 -0
- package/dist/src/commands/furnace/authoring-config.js +19 -0
- package/dist/src/commands/furnace/chrome-doc-remove.js +1 -1
- package/dist/src/commands/furnace/create-browser-test.js +3 -8
- package/dist/src/commands/furnace/create-dry-run.js +2 -5
- package/dist/src/commands/furnace/create-templates.d.ts +17 -1
- package/dist/src/commands/furnace/create-templates.js +2 -1
- package/dist/src/commands/furnace/create.js +25 -19
- package/dist/src/commands/furnace/deploy.js +11 -2
- package/dist/src/commands/furnace/init.js +2 -1
- package/dist/src/commands/furnace/override.js +28 -19
- package/dist/src/commands/furnace/refresh.js +9 -1
- package/dist/src/commands/furnace/remove-mutations.d.ts +6 -7
- package/dist/src/commands/furnace/remove-mutations.js +0 -1
- package/dist/src/commands/furnace/remove.js +2 -6
- package/dist/src/commands/furnace/rename-browser-test.js +1 -7
- package/dist/src/commands/furnace/rename.js +29 -29
- package/dist/src/commands/furnace/status.js +12 -9
- package/dist/src/commands/furnace/test-file-name.d.ts +28 -0
- package/dist/src/commands/furnace/test-file-name.js +39 -0
- package/dist/src/commands/furnace/validation-output.d.ts +21 -9
- package/dist/src/commands/furnace/validation-output.js +3 -8
- package/dist/src/commands/import.js +2 -9
- package/dist/src/commands/lint-per-patch.d.ts +2 -2
- package/dist/src/commands/lint-per-patch.js +19 -4
- package/dist/src/commands/lint-per-run-checkjs.d.ts +2 -2
- package/dist/src/commands/lint.d.ts +2 -1
- package/dist/src/commands/lint.js +1 -1
- package/dist/src/commands/manifest.js +1 -1
- package/dist/src/commands/package.js +2 -4
- package/dist/src/commands/patch/compact.js +14 -15
- package/dist/src/commands/patch/delete.js +15 -25
- package/dist/src/commands/patch/entry-projection.d.ts +13 -0
- package/dist/src/commands/patch/entry-projection.js +24 -0
- package/dist/src/commands/patch/lint-ignore.d.ts +0 -8
- package/dist/src/commands/patch/lint-ignore.js +19 -29
- package/dist/src/commands/patch/move-files-create.js +24 -10
- package/dist/src/commands/patch/move-files-into.js +38 -37
- package/dist/src/commands/patch/move-files.js +4 -13
- package/dist/src/commands/patch/rename.js +27 -41
- package/dist/src/commands/patch/reorder.js +25 -38
- package/dist/src/commands/patch/split-plan.d.ts +4 -5
- package/dist/src/commands/patch/split-plan.js +4 -11
- package/dist/src/commands/patch/split.js +45 -43
- package/dist/src/commands/patch/staged-dependency.js +16 -22
- package/dist/src/commands/patch/tier.js +15 -25
- package/dist/src/commands/patch-rename-map.d.ts +19 -0
- package/dist/src/commands/patch-rename-map.js +36 -0
- package/dist/src/commands/pending-resolution.d.ts +15 -0
- package/dist/src/commands/pending-resolution.js +25 -0
- package/dist/src/commands/re-export-adjacent.d.ts +32 -2
- package/dist/src/commands/re-export-adjacent.js +51 -10
- package/dist/src/commands/re-export-bulk-scan.js +6 -10
- package/dist/src/commands/re-export-drift.d.ts +8 -0
- package/dist/src/commands/re-export-drift.js +65 -4
- package/dist/src/commands/re-export-files.d.ts +2 -3
- package/dist/src/commands/re-export-files.js +38 -32
- package/dist/src/commands/re-export-lint.d.ts +3 -4
- package/dist/src/commands/re-export-lint.js +9 -2
- package/dist/src/commands/re-export-options.js +19 -5
- package/dist/src/commands/re-export-register.js +5 -1
- package/dist/src/commands/re-export-scan.js +8 -8
- package/dist/src/commands/re-export-single.d.ts +22 -3
- package/dist/src/commands/re-export-single.js +19 -11
- package/dist/src/commands/re-export.js +44 -27
- package/dist/src/commands/rebase/abort.js +3 -8
- package/dist/src/commands/rebase/continue.js +12 -12
- package/dist/src/commands/rebase/index.js +2 -2
- package/dist/src/commands/rebase/patch-loop.d.ts +2 -2
- package/dist/src/commands/rebase/patch-loop.js +2 -7
- package/dist/src/commands/resolve.js +18 -10
- package/dist/src/commands/run.d.ts +0 -13
- package/dist/src/commands/run.js +12 -25
- package/dist/src/commands/setup-support.d.ts +16 -4
- package/dist/src/commands/setup-support.js +16 -16
- package/dist/src/commands/setup.js +3 -9
- package/dist/src/commands/source.js +8 -1
- package/dist/src/commands/status-check.js +2 -2
- package/dist/src/commands/status-output.js +2 -2
- package/dist/src/commands/status.js +3 -4
- package/dist/src/commands/test-build-phase.d.ts +2 -2
- package/dist/src/commands/test-build-phase.js +33 -8
- package/dist/src/commands/test-diagnose.js +14 -5
- package/dist/src/commands/test-modes.d.ts +2 -2
- package/dist/src/commands/test-modes.js +0 -1
- package/dist/src/commands/test-register.js +6 -3
- package/dist/src/commands/test-run.js +29 -21
- package/dist/src/commands/test-stale-gate.js +8 -5
- package/dist/src/commands/test-verdict.d.ts +16 -2
- package/dist/src/commands/test-verdict.js +18 -3
- package/dist/src/commands/test.js +51 -7
- package/dist/src/commands/token-list.d.ts +26 -0
- package/dist/src/commands/token-list.js +153 -0
- package/dist/src/commands/token.js +17 -1
- package/dist/src/commands/tree.js +7 -2
- package/dist/src/commands/typecheck.js +9 -9
- package/dist/src/commands/wire.js +4 -3
- package/dist/src/core/branding.d.ts +0 -7
- package/dist/src/core/branding.js +3 -2
- package/dist/src/core/browser-wire.js +6 -3
- package/dist/src/core/build-audit-platform.d.ts +29 -0
- package/dist/src/core/build-audit-platform.js +140 -0
- package/dist/src/core/build-audit-registration.js +3 -2
- package/dist/src/core/build-audit-skip.d.ts +57 -0
- package/dist/src/core/build-audit-skip.js +112 -0
- package/dist/src/core/build-audit.d.ts +15 -2
- package/dist/src/core/build-audit.js +154 -12
- package/dist/src/core/build-baseline.d.ts +45 -28
- package/dist/src/core/build-baseline.js +6 -29
- package/dist/src/core/build-jar-escalation.d.ts +64 -0
- package/dist/src/core/build-jar-escalation.js +128 -0
- package/dist/src/core/build-overwrite-guard.js +1 -1
- package/dist/src/core/build-prepare.d.ts +5 -2
- package/dist/src/core/build-prepare.js +34 -6
- package/dist/src/core/config-paths.d.ts +2 -6
- package/dist/src/core/config-paths.js +4 -2
- package/dist/src/core/config-state.d.ts +0 -6
- package/dist/src/core/config-state.js +1 -19
- package/dist/src/core/config-validate-build-audit.d.ts +8 -0
- package/dist/src/core/config-validate-build-audit.js +80 -0
- package/dist/src/core/config-validate-file-size.d.ts +12 -0
- package/dist/src/core/config-validate-file-size.js +65 -0
- package/dist/src/core/config-validate-patch-policy.d.ts +2 -2
- package/dist/src/core/config-validate-patch-policy.js +3 -3
- package/dist/src/core/config-validate-test-toolchains.d.ts +2 -2
- package/dist/src/core/config-validate-test-toolchains.js +4 -5
- package/dist/src/core/config-validate.js +20 -3
- package/dist/src/core/config.d.ts +2 -2
- package/dist/src/core/config.js +11 -15
- package/dist/src/core/coverage-extend.js +4 -4
- package/dist/src/core/discard-baseline.js +7 -7
- package/dist/src/core/engine-precondition.d.ts +1 -9
- package/dist/src/core/engine-precondition.js +3 -8
- package/dist/src/core/engine-session-lock.js +13 -17
- package/dist/src/core/export-moved-code-shape.js +2 -2
- package/dist/src/core/file-lock-owner.d.ts +103 -0
- package/dist/src/core/file-lock-owner.js +259 -0
- package/dist/src/core/file-lock.d.ts +2 -7
- package/dist/src/core/file-lock.js +40 -91
- package/dist/src/core/firefox-cache.d.ts +13 -1
- package/dist/src/core/firefox-cache.js +59 -34
- package/dist/src/core/firefox-download.d.ts +15 -0
- package/dist/src/core/firefox-download.js +37 -21
- package/dist/src/core/firefox-extract.d.ts +6 -5
- package/dist/src/core/firefox-extract.js +36 -18
- package/dist/src/core/firefox.d.ts +30 -32
- package/dist/src/core/firefox.js +5 -36
- package/dist/src/core/furnace-apply-dry-run.d.ts +1 -6
- package/dist/src/core/furnace-apply-dry-run.js +1 -7
- package/dist/src/core/furnace-apply-helpers.d.ts +0 -11
- package/dist/src/core/furnace-apply-helpers.js +8 -19
- package/dist/src/core/furnace-apply-overwrite-warn.js +4 -2
- package/dist/src/core/furnace-apply.d.ts +1 -1
- package/dist/src/core/furnace-apply.js +30 -10
- package/dist/src/core/furnace-config.d.ts +1 -1
- package/dist/src/core/furnace-config.js +23 -36
- package/dist/src/core/furnace-constants.js +3 -2
- package/dist/src/core/furnace-dir-entry.d.ts +20 -0
- package/dist/src/core/furnace-dir-entry.js +21 -0
- package/dist/src/core/furnace-operation.d.ts +2 -0
- package/dist/src/core/furnace-operation.js +3 -1
- package/dist/src/core/furnace-refresh.d.ts +18 -6
- package/dist/src/core/furnace-refresh.js +3 -6
- package/dist/src/core/furnace-registration-ast.d.ts +0 -1
- package/dist/src/core/furnace-registration-ast.js +0 -3
- package/dist/src/core/furnace-registration.d.ts +2 -1
- package/dist/src/core/furnace-registration.js +22 -29
- package/dist/src/core/furnace-scanner.js +3 -6
- package/dist/src/core/furnace-state-persist.d.ts +2 -2
- package/dist/src/core/furnace-validate-registration.js +5 -7
- package/dist/src/core/git-base.d.ts +3 -1
- package/dist/src/core/git-base.js +25 -3
- package/dist/src/core/git-diff-new-file.d.ts +40 -0
- package/dist/src/core/git-diff-new-file.js +119 -0
- package/dist/src/core/git-diff.d.ts +9 -9
- package/dist/src/core/git-diff.js +88 -157
- package/dist/src/core/git-file-ops.d.ts +0 -7
- package/dist/src/core/git-file-ops.js +4 -28
- package/dist/src/core/git-readonly-index.d.ts +0 -2
- package/dist/src/core/git-readonly-index.js +0 -4
- package/dist/src/core/git-status.d.ts +7 -0
- package/dist/src/core/git-status.js +21 -9
- package/dist/src/core/git.d.ts +0 -6
- package/dist/src/core/git.js +14 -43
- package/dist/src/core/harness-orphans.d.ts +73 -0
- package/dist/src/core/harness-orphans.js +187 -0
- package/dist/src/core/history-log.d.ts +19 -0
- package/dist/src/core/history-log.js +29 -0
- package/dist/src/core/mach-error-hints.d.ts +0 -5
- package/dist/src/core/mach-error-hints.js +1 -1
- package/dist/src/core/mach-known-noise-filter.d.ts +25 -3
- package/dist/src/core/mach-known-noise-filter.js +31 -10
- package/dist/src/core/mach-mozconfig.js +2 -1
- package/dist/src/core/mach-python.d.ts +2 -0
- package/dist/src/core/mach-python.js +12 -7
- package/dist/src/core/mach-resource-shim.d.ts +13 -1
- package/dist/src/core/mach-resource-shim.js +112 -9
- package/dist/src/core/mach.d.ts +46 -53
- package/dist/src/core/mach.js +31 -83
- package/dist/src/core/marionette-port.d.ts +0 -17
- package/dist/src/core/marionette-port.js +12 -58
- package/dist/src/core/marionette-preflight.js +9 -27
- package/dist/src/core/mochitest-server-port.js +3 -3
- package/dist/src/core/moz-manifest-rules.js +37 -33
- package/dist/src/core/patch-apply-fuzz.js +4 -4
- package/dist/src/core/patch-apply.d.ts +4 -5
- package/dist/src/core/patch-apply.js +55 -32
- package/dist/src/core/patch-export-coverage.d.ts +0 -9
- package/dist/src/core/patch-export-coverage.js +1 -30
- package/dist/src/core/patch-export-update.d.ts +18 -1
- package/dist/src/core/patch-export-update.js +4 -3
- package/dist/src/core/patch-export.d.ts +11 -21
- package/dist/src/core/patch-export.js +44 -66
- package/dist/src/core/patch-files.d.ts +0 -4
- package/dist/src/core/patch-files.js +0 -11
- package/dist/src/core/patch-lint-binary.d.ts +6 -12
- package/dist/src/core/patch-lint-cache.d.ts +17 -1
- package/dist/src/core/patch-lint-cache.js +3 -6
- package/dist/src/core/patch-lint-checkjs.d.ts +33 -16
- package/dist/src/core/patch-lint-checkjs.js +43 -25
- package/dist/src/core/patch-lint-creators.d.ts +2 -6
- package/dist/src/core/patch-lint-cross.d.ts +16 -8
- package/dist/src/core/patch-lint-cross.js +49 -92
- package/dist/src/core/patch-lint-css.js +10 -6
- package/dist/src/core/patch-lint-file-size.d.ts +52 -0
- package/dist/src/core/patch-lint-file-size.js +53 -0
- package/dist/src/core/patch-lint-forward-registration.d.ts +58 -0
- package/dist/src/core/patch-lint-forward-registration.js +210 -0
- package/dist/src/core/patch-lint-jsdoc.js +36 -36
- package/dist/src/core/patch-lint-module-registration.d.ts +2 -8
- package/dist/src/core/patch-lint-prettier.d.ts +36 -0
- package/dist/src/core/patch-lint-prettier.js +126 -0
- package/dist/src/core/patch-lint-queue-types.d.ts +60 -0
- package/dist/src/core/patch-lint-queue-types.js +12 -0
- package/dist/src/core/patch-lint-staged-registration.d.ts +61 -0
- package/dist/src/core/patch-lint-staged-registration.js +90 -0
- package/dist/src/core/patch-lint-unmanaged-companion.d.ts +106 -0
- package/dist/src/core/patch-lint-unmanaged-companion.js +214 -0
- package/dist/src/core/patch-lint.d.ts +14 -9
- package/dist/src/core/patch-lint.js +27 -27
- package/dist/src/core/patch-lock.js +3 -3
- package/dist/src/core/patch-manifest-io.js +8 -7
- package/dist/src/core/patch-parse.d.ts +19 -8
- package/dist/src/core/patch-parse.js +25 -0
- package/dist/src/core/patch-policy.js +15 -27
- package/dist/src/core/patch-source-metadata.d.ts +1 -3
- package/dist/src/core/patch-source-metadata.js +0 -4
- package/dist/src/core/patch-transform.d.ts +21 -8
- package/dist/src/core/patch-transform.js +38 -10
- package/dist/src/core/process-cpu-time.js +1 -1
- package/dist/src/core/rebase-session.d.ts +2 -8
- package/dist/src/core/rebase-session.js +5 -17
- package/dist/src/core/register-browser-content.js +3 -2
- package/dist/src/core/register-browser-test.js +2 -1
- package/dist/src/core/register-module.js +2 -1
- package/dist/src/core/register-scaffold.js +5 -4
- package/dist/src/core/register-shared-css.js +2 -1
- package/dist/src/core/run-log-redact.d.ts +38 -0
- package/dist/src/core/run-log-redact.js +69 -0
- package/dist/src/core/run-log.d.ts +13 -2
- package/dist/src/core/run-log.js +67 -18
- package/dist/src/core/signal-critical.js +2 -4
- package/dist/src/core/smoke-patterns.d.ts +0 -1
- package/dist/src/core/smoke-patterns.js +18 -11
- package/dist/src/core/state-file.d.ts +12 -1
- package/dist/src/core/state-file.js +14 -1
- package/dist/src/core/status-classify.js +2 -1
- package/dist/src/core/test-changed-prefs.js +26 -11
- package/dist/src/core/test-harness-crash.js +61 -48
- package/dist/src/core/test-harness-verdict-notes.d.ts +55 -0
- package/dist/src/core/test-harness-verdict-notes.js +48 -0
- package/dist/src/core/test-stale-check.d.ts +23 -8
- package/dist/src/core/test-stale-check.js +77 -4
- package/dist/src/core/test-stall-triage.js +37 -19
- package/dist/src/core/test-xpcshell-retry.d.ts +26 -7
- package/dist/src/core/test-xpcshell-retry.js +14 -11
- package/dist/src/core/token-category.d.ts +33 -0
- package/dist/src/core/token-category.js +56 -48
- package/dist/src/core/token-dark-mode.js +2 -14
- package/dist/src/core/token-inventory.d.ts +56 -0
- package/dist/src/core/token-inventory.js +109 -0
- package/dist/src/core/token-manager.d.ts +1 -1
- package/dist/src/core/token-manager.js +1 -1
- package/dist/src/core/token-variant.js +21 -22
- package/dist/src/core/toolchain-preflight.js +1 -1
- package/dist/src/core/tree-cow.d.ts +0 -2
- package/dist/src/core/tree-cow.js +2 -2
- package/dist/src/core/tree-store.js +4 -5
- package/dist/src/core/typecheck.js +2 -2
- package/dist/src/core/wire-dom-fragment.js +6 -17
- package/dist/src/core/wire-utils.js +5 -17
- package/dist/src/core/xpcshell-appdir.js +2 -4
- package/dist/src/errors/base.d.ts +53 -1
- package/dist/src/errors/base.js +79 -11
- package/dist/src/errors/build.d.ts +28 -0
- package/dist/src/errors/build.js +51 -10
- package/dist/src/errors/config.d.ts +0 -14
- package/dist/src/errors/config.js +13 -34
- package/dist/src/errors/download.d.ts +13 -0
- package/dist/src/errors/download.js +53 -24
- package/dist/src/errors/furnace.js +8 -9
- package/dist/src/errors/git.d.ts +8 -8
- package/dist/src/errors/git.js +20 -30
- package/dist/src/errors/patch.js +8 -10
- package/dist/src/errors/rebase.js +7 -7
- package/dist/src/types/commands/options.d.ts +96 -123
- package/dist/src/types/commands/patches.d.ts +17 -6
- package/dist/src/types/config.d.ts +75 -2
- package/dist/src/types/index.d.ts +0 -1
- package/dist/src/utils/assert.d.ts +3 -15
- package/dist/src/utils/assert.js +1 -18
- package/dist/src/utils/build-info.d.ts +1 -1
- package/dist/src/utils/child-env.d.ts +2 -1
- package/dist/src/utils/elapsed.js +10 -1
- package/dist/src/utils/fs.d.ts +4 -2
- package/dist/src/utils/fs.js +11 -14
- package/dist/src/utils/hash.d.ts +12 -0
- package/dist/src/utils/hash.js +16 -0
- package/dist/src/utils/line-dispatch.d.ts +15 -0
- package/dist/src/utils/line-dispatch.js +48 -0
- package/dist/src/utils/options.d.ts +1 -7
- package/dist/src/utils/options.js +6 -7
- package/dist/src/utils/package-root.js +3 -12
- package/dist/src/utils/platform.d.ts +0 -4
- package/dist/src/utils/process-group.d.ts +2 -1
- package/dist/src/utils/process-group.js +20 -19
- package/dist/src/utils/process.d.ts +40 -26
- package/dist/src/utils/process.js +115 -166
- package/dist/src/utils/ps-duration.js +3 -3
- package/dist/src/utils/sleep.d.ts +26 -0
- package/dist/src/utils/sleep.js +31 -0
- package/dist/src/utils/validation.d.ts +2 -38
- package/dist/src/utils/validation.js +7 -16
- package/docs/configuration.md +136 -1
- package/docs/exit-codes.md +34 -20
- package/docs/lifecycle-invariants.md +18 -12
- package/docs/machine-output.md +31 -8
- package/docs/patch-workflow.md +2 -1
- package/docs/run-logs.md +44 -4
- package/docs/testing.md +70 -11
- package/package.json +8 -7
- package/dist/src/commands/rebase.d.ts +0 -4
- package/dist/src/commands/rebase.js +0 -5
- package/dist/src/core/patch-artifact-normalize.d.ts +0 -21
- package/dist/src/core/patch-artifact-normalize.js +0 -24
- package/dist/src/core/patch-lint-reexports.d.ts +0 -7
- package/dist/src/core/patch-lint-reexports.js +0 -8
- package/dist/src/core/wire-targets.d.ts +0 -7
- package/dist/src/core/wire-targets.js +0 -8
|
@@ -7,13 +7,13 @@ import { createReadStream } from 'node:fs';
|
|
|
7
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
|
-
import { ChecksumMismatchError } from '../errors/download.js';
|
|
10
|
+
import { ChecksumMismatchError, ChecksumUnavailableError } from '../errors/download.js';
|
|
11
11
|
import { toError } from '../utils/errors.js';
|
|
12
12
|
import { pathExistsStrict, readJson, removeFile, writeJson } from '../utils/fs.js';
|
|
13
13
|
import { verbose, warn } from '../utils/logger.js';
|
|
14
14
|
import { createSiblingLockPath, withFileLock } from './file-lock.js';
|
|
15
15
|
import { validateArchiveMetadata } from './firefox-archive.js';
|
|
16
|
-
import { downloadFile } from './firefox-download.js';
|
|
16
|
+
import { downloadFile, fetchWithRetry } from './firefox-download.js';
|
|
17
17
|
/**
|
|
18
18
|
* Computes the SHA-256 hex digest of a file.
|
|
19
19
|
* @param filePath - Path to the file
|
|
@@ -26,10 +26,15 @@ async function sha256File(filePath) {
|
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Fetches Mozilla's published SHA256SUMS for the release and extracts the
|
|
29
|
-
* digest for this archive.
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
29
|
+
* digest for this archive. Yields a reason instead of a digest when the
|
|
30
|
+
* checksum file cannot be fetched (non-2xx, network error, timeout) or does
|
|
31
|
+
* not list the archive (a captive portal's HTML page lands here too) — the
|
|
32
|
+
* caller decides whether that fails closed. The request goes through the
|
|
33
|
+
* same `fetchWithRetry` as the archive download — same 60 s abort ceiling
|
|
34
|
+
* per attempt, same three-attempt back-off on 429/5xx and network errors —
|
|
35
|
+
* so an unresponsive checksum host cannot hang the command after the
|
|
36
|
+
* tarball has landed, and one transient blip on the small checksum request
|
|
37
|
+
* does not fail the verification closed and discard the archive.
|
|
33
38
|
*
|
|
34
39
|
* Line format (both variants appear in the wild):
|
|
35
40
|
* `<64-hex-digest> <release-relative-path>`
|
|
@@ -37,33 +42,67 @@ async function sha256File(filePath) {
|
|
|
37
42
|
*/
|
|
38
43
|
async function fetchPublishedSha256(archive) {
|
|
39
44
|
try {
|
|
40
|
-
const response = await
|
|
45
|
+
const response = await fetchWithRetry(archive.checksumsUrl);
|
|
41
46
|
if (!response.ok) {
|
|
42
|
-
|
|
43
|
-
return null;
|
|
47
|
+
return { sha256: null, reason: `HTTP ${response.status} fetching SHA256SUMS` };
|
|
44
48
|
}
|
|
45
49
|
const body = await response.text();
|
|
46
50
|
for (const line of body.split('\n')) {
|
|
47
51
|
const match = /^([0-9a-f]{64})\s+\*?(.+)$/.exec(line.trim());
|
|
48
52
|
if (match?.[1] && match[2] === archive.pathInChecksums) {
|
|
49
|
-
return match[1];
|
|
53
|
+
return { sha256: match[1] };
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
return {
|
|
57
|
+
sha256: null,
|
|
58
|
+
reason: `SHA256SUMS does not list ${archive.pathInChecksums} (wrong file, or a non-Mozilla response)`,
|
|
59
|
+
};
|
|
54
60
|
}
|
|
55
61
|
catch (error) {
|
|
56
|
-
|
|
57
|
-
return null;
|
|
62
|
+
return { sha256: null, reason: `SHA256SUMS fetch failed: ${toError(error).message}` };
|
|
58
63
|
}
|
|
59
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Default integrity check for a download with no operator pin: the archive
|
|
67
|
+
* digest must equal the one Mozilla publishes. TLS alone is thin trust for
|
|
68
|
+
* the artifact that becomes the git baseline every patch is built on — a
|
|
69
|
+
* compromised CDN response would otherwise be trusted with no signal.
|
|
70
|
+
*
|
|
71
|
+
* Fails CLOSED both on a mismatch and when the published digest cannot be
|
|
72
|
+
* obtained: a 404, a captive-portal HTML page, or a dropped connection
|
|
73
|
+
* would otherwise quietly downgrade the check to "TLS looked fine", which
|
|
74
|
+
* is precisely the condition an attacker positioned on the network can
|
|
75
|
+
* arrange. `allowUnverifiedDownload` (an explicit config opt-in for offline
|
|
76
|
+
* mirrors) degrades the unavailable case to a loud warning; a FETCHED but
|
|
77
|
+
* mismatching digest is never accepted.
|
|
78
|
+
*/
|
|
79
|
+
async function verifyAgainstPublishedSha256(archive, actualSha256, options) {
|
|
80
|
+
const published = await fetchPublishedSha256(archive);
|
|
81
|
+
if (published.sha256 !== null) {
|
|
82
|
+
if (actualSha256 !== published.sha256) {
|
|
83
|
+
throw new ChecksumMismatchError(archive.product, published.sha256, actualSha256, archive.url);
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
verbose(`${published.reason} (${archive.checksumsUrl})`);
|
|
88
|
+
if (options.allowUnverifiedDownload !== true) {
|
|
89
|
+
throw new ChecksumUnavailableError(archive.product, archive.checksumsUrl, published.reason, archive.url);
|
|
90
|
+
}
|
|
91
|
+
warn(`Could not verify ${archive.filename} against Mozilla's published SHA256SUMS ` +
|
|
92
|
+
`(${published.reason}). firefox.allowUnverifiedDownload is set, so the download is ` +
|
|
93
|
+
'trusted on TLS alone — pin firefox.sha256 in fireforge.json to verify it.');
|
|
94
|
+
}
|
|
60
95
|
/**
|
|
61
96
|
* Ensures a valid cached archive exists, downloading it if needed.
|
|
62
97
|
* @param archive - Resolved archive descriptor
|
|
63
98
|
* @param cacheDir - Cache directory
|
|
64
99
|
* @param onProgress - Optional progress callback
|
|
100
|
+
* @param expectedSha256 - Operator-pinned digest; when set it replaces the
|
|
101
|
+
* published-SHA256SUMS check
|
|
102
|
+
* @param onCacheProgress - Phase progress messages
|
|
103
|
+
* @param integrity - Default integrity-check options (unpinned downloads)
|
|
65
104
|
*/
|
|
66
|
-
export async function ensureCachedArchive(archive, cacheDir, onProgress, expectedSha256, onCacheProgress) {
|
|
105
|
+
export async function ensureCachedArchive(archive, cacheDir, onProgress, expectedSha256, onCacheProgress, integrity = {}) {
|
|
67
106
|
const lockPath = createSiblingLockPath(join(cacheDir, archive.filename), '.fireforge-cache.lock');
|
|
68
107
|
await withFileLock(lockPath, async () => {
|
|
69
108
|
onCacheProgress?.(`Validating source archive cache metadata for ${archive.filename}...`);
|
|
@@ -78,7 +117,7 @@ export async function ensureCachedArchive(archive, cacheDir, onProgress, expecte
|
|
|
78
117
|
else {
|
|
79
118
|
await removeArchivePartFiles(archive, cacheDir);
|
|
80
119
|
}
|
|
81
|
-
await downloadToCache(archive, cacheDir, onProgress, expectedSha256, onCacheProgress);
|
|
120
|
+
await downloadToCache(archive, cacheDir, onProgress, expectedSha256, onCacheProgress, integrity);
|
|
82
121
|
});
|
|
83
122
|
}
|
|
84
123
|
async function cacheEntryExists(archive, cacheDir) {
|
|
@@ -135,7 +174,7 @@ async function validateCachedArchive(archive, cacheDir, expectedSha256) {
|
|
|
135
174
|
* @param cacheDir - Cache directory
|
|
136
175
|
* @param onProgress - Optional progress callback
|
|
137
176
|
*/
|
|
138
|
-
async function downloadToCache(archive, cacheDir, onProgress, expectedSha256, onCacheProgress) {
|
|
177
|
+
async function downloadToCache(archive, cacheDir, onProgress, expectedSha256, onCacheProgress, integrity = {}) {
|
|
139
178
|
const tarballPath = join(cacheDir, archive.filename);
|
|
140
179
|
// Use a unique .part path so concurrent downloads for the same archive
|
|
141
180
|
// do not clobber each other's partial files.
|
|
@@ -157,24 +196,10 @@ async function downloadToCache(archive, cacheDir, onProgress, expectedSha256, on
|
|
|
157
196
|
}
|
|
158
197
|
}
|
|
159
198
|
else {
|
|
160
|
-
// Default integrity check
|
|
161
|
-
//
|
|
162
|
-
// the git baseline every patch is built on — a compromised CDN
|
|
163
|
-
// response would otherwise be trusted with no signal. Mismatch fails
|
|
164
|
-
// closed (the catch below deletes the artifact); an unfetchable
|
|
165
|
-
// SHA256SUMS degrades to a loud warning so offline/mirror workflows
|
|
166
|
-
// keep working (pin firefox.sha256 in fireforge.json to fail closed
|
|
167
|
-
// even then).
|
|
199
|
+
// Default integrity check (see verifyAgainstPublishedSha256): any
|
|
200
|
+
// failure throws, and the catch below deletes the promoted artifact.
|
|
168
201
|
onCacheProgress?.(`Verifying archive against published SHA256SUMS...`);
|
|
169
|
-
|
|
170
|
-
if (publishedSha256 === null) {
|
|
171
|
-
warn(`Could not verify ${archive.filename} against Mozilla's published SHA256SUMS ` +
|
|
172
|
-
`(${archive.checksumsUrl} unavailable). The download is trusted on TLS alone — ` +
|
|
173
|
-
'set firefox.sha256 in fireforge.json to require checksum verification.');
|
|
174
|
-
}
|
|
175
|
-
else if (sha256 !== publishedSha256) {
|
|
176
|
-
throw new ChecksumMismatchError(archive.product, publishedSha256, sha256, archive.url);
|
|
177
|
-
}
|
|
202
|
+
await verifyAgainstPublishedSha256(archive, sha256, integrity);
|
|
178
203
|
}
|
|
179
204
|
onCacheProgress?.(`Writing source archive cache metadata for ${archive.metadataFilename}...`);
|
|
180
205
|
await writeJson(metadataPath, {
|
|
@@ -5,6 +5,21 @@
|
|
|
5
5
|
* Progress callback for download operations.
|
|
6
6
|
*/
|
|
7
7
|
export type ProgressCallback = (downloaded: number, total: number) => void;
|
|
8
|
+
/**
|
|
9
|
+
* Fetches a URL with timeout and bounded retry for transient failures
|
|
10
|
+
* (429/5xx, network errors, the per-request timeout).
|
|
11
|
+
*
|
|
12
|
+
* Any other response — 2xx or a non-retryable failure such as 404 — is
|
|
13
|
+
* returned as-is on the first attempt; the caller decides what a given
|
|
14
|
+
* status means for ITS request. Shared by the tarball download and the
|
|
15
|
+
* SHA256SUMS lookup so both requests the download makes survive the same
|
|
16
|
+
* transient blips: with checksum verification failing closed, an
|
|
17
|
+
* un-retried checksum fetch would throw away a just-landed 600 MB archive
|
|
18
|
+
* over one dropped connection.
|
|
19
|
+
*
|
|
20
|
+
* @throws DownloadError once every attempt has failed with a retryable error
|
|
21
|
+
*/
|
|
22
|
+
export declare function fetchWithRetry(url: string): Promise<Response>;
|
|
8
23
|
/**
|
|
9
24
|
* Downloads a file from a URL with progress tracking, timeout, and retry.
|
|
10
25
|
* @param url - URL to download
|
|
@@ -7,7 +7,13 @@ import { basename } from 'node:path';
|
|
|
7
7
|
import { Readable, Transform } from 'node:stream';
|
|
8
8
|
import { pipeline } from 'node:stream/promises';
|
|
9
9
|
import { DownloadError, VersionNotFoundError } from '../errors/download.js';
|
|
10
|
-
|
|
10
|
+
import { sleep } from '../utils/sleep.js';
|
|
11
|
+
/**
|
|
12
|
+
* Per-attempt request timeout in milliseconds (60 seconds). Every request
|
|
13
|
+
* the download makes — the tarball and the SHA256SUMS lookup in
|
|
14
|
+
* `firefox-cache.ts` — goes through {@link fetchWithRetry} and so shares
|
|
15
|
+
* this abort ceiling.
|
|
16
|
+
*/
|
|
11
17
|
const REQUEST_TIMEOUT_MS = 60_000;
|
|
12
18
|
/** Maximum number of download attempts. */
|
|
13
19
|
const MAX_ATTEMPTS = 3;
|
|
@@ -18,11 +24,20 @@ const RETRY_BASE_DELAY_MS = 1_000;
|
|
|
18
24
|
/** Stall detection timeout — abort if no data received for this duration (30 seconds). */
|
|
19
25
|
const DOWNLOAD_STALL_TIMEOUT_MS = 30_000;
|
|
20
26
|
/**
|
|
21
|
-
* Fetches a URL with timeout and bounded retry for transient failures
|
|
27
|
+
* Fetches a URL with timeout and bounded retry for transient failures
|
|
28
|
+
* (429/5xx, network errors, the per-request timeout).
|
|
22
29
|
*
|
|
23
|
-
*
|
|
30
|
+
* Any other response — 2xx or a non-retryable failure such as 404 — is
|
|
31
|
+
* returned as-is on the first attempt; the caller decides what a given
|
|
32
|
+
* status means for ITS request. Shared by the tarball download and the
|
|
33
|
+
* SHA256SUMS lookup so both requests the download makes survive the same
|
|
34
|
+
* transient blips: with checksum verification failing closed, an
|
|
35
|
+
* un-retried checksum fetch would throw away a just-landed 600 MB archive
|
|
36
|
+
* over one dropped connection.
|
|
37
|
+
*
|
|
38
|
+
* @throws DownloadError once every attempt has failed with a retryable error
|
|
24
39
|
*/
|
|
25
|
-
async function fetchWithRetry(url) {
|
|
40
|
+
export async function fetchWithRetry(url) {
|
|
26
41
|
let lastError;
|
|
27
42
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
28
43
|
const controller = new AbortController();
|
|
@@ -31,23 +46,12 @@ async function fetchWithRetry(url) {
|
|
|
31
46
|
}, REQUEST_TIMEOUT_MS);
|
|
32
47
|
try {
|
|
33
48
|
const response = await fetch(url, { signal: controller.signal });
|
|
34
|
-
if (response.status
|
|
35
|
-
throw new VersionNotFoundError(basename(url).replace('.source.tar.xz', ''));
|
|
36
|
-
}
|
|
37
|
-
if (RETRYABLE_STATUS_CODES.has(response.status)) {
|
|
38
|
-
lastError = new DownloadError(`HTTP ${response.status}: ${response.statusText}`, url);
|
|
39
|
-
}
|
|
40
|
-
else if (!response.ok) {
|
|
41
|
-
throw new DownloadError(`HTTP ${response.status}: ${response.statusText}`, url);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
49
|
+
if (!RETRYABLE_STATUS_CODES.has(response.status)) {
|
|
44
50
|
return response;
|
|
45
51
|
}
|
|
52
|
+
lastError = new DownloadError(`HTTP ${response.status}: ${response.statusText}`, url);
|
|
46
53
|
}
|
|
47
54
|
catch (error) {
|
|
48
|
-
if (error instanceof VersionNotFoundError || error instanceof DownloadError) {
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
55
|
// Network / timeout errors are retryable
|
|
52
56
|
lastError = error;
|
|
53
57
|
}
|
|
@@ -55,9 +59,7 @@ async function fetchWithRetry(url) {
|
|
|
55
59
|
clearTimeout(timer);
|
|
56
60
|
}
|
|
57
61
|
if (attempt < MAX_ATTEMPTS - 1) {
|
|
58
|
-
await
|
|
59
|
-
setTimeout(resolve, RETRY_BASE_DELAY_MS * (attempt + 1) ** 2);
|
|
60
|
-
});
|
|
62
|
+
await sleep(RETRY_BASE_DELAY_MS * (attempt + 1) ** 2);
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
65
|
if (lastError instanceof DownloadError) {
|
|
@@ -65,6 +67,20 @@ async function fetchWithRetry(url) {
|
|
|
65
67
|
}
|
|
66
68
|
throw new DownloadError(lastError instanceof Error ? lastError.message : 'Download failed after retries', url, lastError instanceof Error ? lastError : undefined);
|
|
67
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Fetches the archive itself: a 404 is the release not existing
|
|
72
|
+
* (`VersionNotFoundError`), any other non-2xx is a `DownloadError`.
|
|
73
|
+
*/
|
|
74
|
+
async function fetchArchive(url) {
|
|
75
|
+
const response = await fetchWithRetry(url);
|
|
76
|
+
if (response.status === 404) {
|
|
77
|
+
throw new VersionNotFoundError(basename(url).replace('.source.tar.xz', ''));
|
|
78
|
+
}
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new DownloadError(`HTTP ${response.status}: ${response.statusText}`, url);
|
|
81
|
+
}
|
|
82
|
+
return response;
|
|
83
|
+
}
|
|
68
84
|
/**
|
|
69
85
|
* Creates a Transform stream that aborts if no data is received within the stall timeout.
|
|
70
86
|
* The timer resets on each chunk. If it fires, the stream is destroyed with a DownloadError.
|
|
@@ -110,7 +126,7 @@ function createStallDetector(url, timeoutMs = DOWNLOAD_STALL_TIMEOUT_MS) {
|
|
|
110
126
|
* @returns The content-length if available, otherwise undefined
|
|
111
127
|
*/
|
|
112
128
|
export async function downloadFile(url, destPath, onProgress) {
|
|
113
|
-
const response = await
|
|
129
|
+
const response = await fetchArchive(url);
|
|
114
130
|
if (!response.body) {
|
|
115
131
|
throw new DownloadError('No response body received', url);
|
|
116
132
|
}
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* Firefox source extraction and installed version detection.
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* Validates entry names from a `tar -tf` listing
|
|
5
|
+
* Validates entry names from a `tar -tf` listing: escaping paths and
|
|
6
|
+
* `.git` members are both rejected.
|
|
6
7
|
* @param names - Listing lines (one member name per line)
|
|
7
|
-
* @returns The first unsafe member name, or
|
|
8
|
+
* @returns The first unsafe member name, or undefined when all are safe
|
|
8
9
|
*/
|
|
9
|
-
export declare function findUnsafeArchiveEntryName(names: readonly string[]): string |
|
|
10
|
+
export declare function findUnsafeArchiveEntryName(names: readonly string[]): string | undefined;
|
|
10
11
|
/**
|
|
11
12
|
* Validates symlink and hardlink targets from a `tar -tvf` listing.
|
|
12
13
|
*
|
|
@@ -17,9 +18,9 @@ export declare function findUnsafeArchiveEntryName(names: readonly string[]): st
|
|
|
17
18
|
* target); hardlinks print ` link to target` on GNU tar and bsdtar alike.
|
|
18
19
|
*
|
|
19
20
|
* @param verboseLines - `tar -tvf` listing lines
|
|
20
|
-
* @returns A description of the first unsafe link found, or
|
|
21
|
+
* @returns A description of the first unsafe link found, or undefined
|
|
21
22
|
*/
|
|
22
|
-
export declare function findUnsafeArchiveLink(verboseLines: readonly string[]): string |
|
|
23
|
+
export declare function findUnsafeArchiveLink(verboseLines: readonly string[]): string | undefined;
|
|
23
24
|
/**
|
|
24
25
|
* Extracts a tar.xz archive after a listing preflight that rejects
|
|
25
26
|
* path-traversal member names and escaping link targets.
|
|
@@ -20,19 +20,42 @@ function isUnsafeArchivePath(path) {
|
|
|
20
20
|
return path.split(/[\\/]/).includes('..');
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Returns true when an archive member path has a `.git` segment at any
|
|
24
|
+
* depth (`.git`, `.git/config`, `foo/.git/hooks/pre-commit`). Such a member
|
|
25
|
+
* lands INSIDE the extraction root, so the traversal check passes it — but
|
|
26
|
+
* the extracted tree is immediately turned into a git repository and
|
|
27
|
+
* committed, and git honours a pre-seeded `.git/config` (`core.fsmonitor`,
|
|
28
|
+
* `core.hooksPath`) or `.git/hooks/*` on that first commit. Only the `.git`
|
|
29
|
+
* directory/file is rejected; `.gitmodules`, `.gitignore` and the like are
|
|
30
|
+
* legitimate upstream files.
|
|
31
|
+
*/
|
|
32
|
+
function hasGitSegment(path) {
|
|
33
|
+
return path.split(/[\\/]/).some((segment) => segment.toLowerCase() === '.git');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Returns true when an archive member NAME must be rejected: an escaping
|
|
37
|
+
* path, or a `.git` segment (see {@link hasGitSegment}). Link targets use
|
|
38
|
+
* the narrower {@link isUnsafeArchivePath} — a target can only be reached
|
|
39
|
+
* through a member name that was checked here.
|
|
40
|
+
*/
|
|
41
|
+
function isUnsafeArchiveMemberName(name) {
|
|
42
|
+
return isUnsafeArchivePath(name) || hasGitSegment(name);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Validates entry names from a `tar -tf` listing: escaping paths and
|
|
46
|
+
* `.git` members are both rejected.
|
|
24
47
|
* @param names - Listing lines (one member name per line)
|
|
25
|
-
* @returns The first unsafe member name, or
|
|
48
|
+
* @returns The first unsafe member name, or undefined when all are safe
|
|
26
49
|
*/
|
|
27
50
|
export function findUnsafeArchiveEntryName(names) {
|
|
28
51
|
for (const raw of names) {
|
|
29
52
|
const name = raw.trim();
|
|
30
53
|
if (name.length === 0)
|
|
31
54
|
continue;
|
|
32
|
-
if (
|
|
55
|
+
if (isUnsafeArchiveMemberName(name))
|
|
33
56
|
return name;
|
|
34
57
|
}
|
|
35
|
-
return
|
|
58
|
+
return undefined;
|
|
36
59
|
}
|
|
37
60
|
/**
|
|
38
61
|
* Validates symlink and hardlink targets from a `tar -tvf` listing.
|
|
@@ -44,7 +67,7 @@ export function findUnsafeArchiveEntryName(names) {
|
|
|
44
67
|
* target); hardlinks print ` link to target` on GNU tar and bsdtar alike.
|
|
45
68
|
*
|
|
46
69
|
* @param verboseLines - `tar -tvf` listing lines
|
|
47
|
-
* @returns A description of the first unsafe link found, or
|
|
70
|
+
* @returns A description of the first unsafe link found, or undefined
|
|
48
71
|
*/
|
|
49
72
|
export function findUnsafeArchiveLink(verboseLines) {
|
|
50
73
|
for (const line of verboseLines) {
|
|
@@ -64,7 +87,7 @@ export function findUnsafeArchiveLink(verboseLines) {
|
|
|
64
87
|
return `hardlink target: ${target}`;
|
|
65
88
|
}
|
|
66
89
|
}
|
|
67
|
-
return
|
|
90
|
+
return undefined;
|
|
68
91
|
}
|
|
69
92
|
/**
|
|
70
93
|
* Lists the archive and rejects members that could escape the extraction
|
|
@@ -111,7 +134,7 @@ function createLineScanner(onLine) {
|
|
|
111
134
|
}
|
|
112
135
|
/**
|
|
113
136
|
* Runs one tar listing pass, streaming lines through `checkLine` and
|
|
114
|
-
* returning the first unsafe finding (or
|
|
137
|
+
* returning the first unsafe finding (or undefined) plus the exit code.
|
|
115
138
|
*
|
|
116
139
|
* Streaming matters for safety, not just memory: a buffered `exec` collector
|
|
117
140
|
* silently truncates at 50 MB, and a full Firefox `-tvf` listing already
|
|
@@ -124,10 +147,10 @@ function createLineScanner(onLine) {
|
|
|
124
147
|
async function runListingScan(archivePath, tarFlag, checkLine) {
|
|
125
148
|
// LC_ALL=C keeps the -tvf column format stable for the link parse.
|
|
126
149
|
const listEnv = { LC_ALL: 'C', LANG: 'C' };
|
|
127
|
-
let unsafe
|
|
150
|
+
let unsafe;
|
|
128
151
|
let stderrTail = '';
|
|
129
152
|
const scanner = createLineScanner((line) => {
|
|
130
|
-
if (unsafe ===
|
|
153
|
+
if (unsafe === undefined) {
|
|
131
154
|
unsafe = checkLine(line);
|
|
132
155
|
}
|
|
133
156
|
});
|
|
@@ -146,24 +169,19 @@ async function runListingScan(archivePath, tarFlag, checkLine) {
|
|
|
146
169
|
}
|
|
147
170
|
async function preflightArchiveEntries(archivePath) {
|
|
148
171
|
const [nameScan, linkScan] = await Promise.all([
|
|
149
|
-
runListingScan(archivePath, '-tf', (line) =>
|
|
150
|
-
const name = line.trim();
|
|
151
|
-
if (name.length === 0)
|
|
152
|
-
return null;
|
|
153
|
-
return isUnsafeArchivePath(name) ? name : null;
|
|
154
|
-
}),
|
|
172
|
+
runListingScan(archivePath, '-tf', (line) => findUnsafeArchiveEntryName([line])),
|
|
155
173
|
runListingScan(archivePath, '-tvf', (line) => findUnsafeArchiveLink([line])),
|
|
156
174
|
]);
|
|
157
175
|
if (nameScan.exitCode !== 0) {
|
|
158
176
|
throw new ExtractionError(archivePath, new Error(`tar -tf preflight exited with code ${nameScan.exitCode}:\n${nameScan.stderrTail}`));
|
|
159
177
|
}
|
|
160
|
-
if (nameScan.unsafe !==
|
|
161
|
-
throw new ExtractionError(archivePath, new Error(`Archive rejected: member name could escape the extraction root: ${nameScan.unsafe}`));
|
|
178
|
+
if (nameScan.unsafe !== undefined) {
|
|
179
|
+
throw new ExtractionError(archivePath, new Error(`Archive rejected: member name could escape the extraction root or seed .git: ${nameScan.unsafe}`));
|
|
162
180
|
}
|
|
163
181
|
if (linkScan.exitCode !== 0) {
|
|
164
182
|
throw new ExtractionError(archivePath, new Error(`tar -tvf preflight exited with code ${linkScan.exitCode}:\n${linkScan.stderrTail}`));
|
|
165
183
|
}
|
|
166
|
-
if (linkScan.unsafe !==
|
|
184
|
+
if (linkScan.unsafe !== undefined) {
|
|
167
185
|
throw new ExtractionError(archivePath, new Error(`Archive rejected: link could escape the extraction root (${linkScan.unsafe})`));
|
|
168
186
|
}
|
|
169
187
|
}
|
|
@@ -5,26 +5,9 @@
|
|
|
5
5
|
* {@link downloadFirefoxSource} orchestrator.
|
|
6
6
|
*/
|
|
7
7
|
import type { FirefoxProduct } from '../types/config.js';
|
|
8
|
+
import type { ArchiveIntegrityOptions } from './firefox-cache.js';
|
|
8
9
|
import type { ProgressCallback } from './firefox-download.js';
|
|
9
|
-
export { resolveArchive } from './firefox-archive.js';
|
|
10
|
-
export type { ProgressCallback } from './firefox-download.js';
|
|
11
10
|
export { formatBytes, getFirefoxVersion } from './firefox-extract.js';
|
|
12
|
-
/**
|
|
13
|
-
* Gets the download URL for a Firefox source tarball.
|
|
14
|
-
* @param version - Firefox version (e.g., "140.9.0esr")
|
|
15
|
-
* @param product - Firefox product type
|
|
16
|
-
* @param candidate - Optional release-candidate build directory (e.g. "build2")
|
|
17
|
-
* @returns Full URL to the source tarball
|
|
18
|
-
*/
|
|
19
|
-
export declare function getDownloadUrl(version: string, product?: FirefoxProduct, candidate?: string): string;
|
|
20
|
-
/**
|
|
21
|
-
* Gets the filename for a Firefox source tarball.
|
|
22
|
-
* @param version - Firefox version
|
|
23
|
-
* @param product - Firefox product type
|
|
24
|
-
* @param candidate - Optional release-candidate build directory (e.g. "build2")
|
|
25
|
-
* @returns Tarball filename
|
|
26
|
-
*/
|
|
27
|
-
export declare function getTarballFilename(version: string, product?: FirefoxProduct, candidate?: string): string;
|
|
28
11
|
/**
|
|
29
12
|
* Lifecycle phase reported by {@link downloadFirefoxSource}. The download
|
|
30
13
|
* CLI command uses this to swap spinners between the bytes-on-the-wire phase
|
|
@@ -49,20 +32,35 @@ export type FirefoxSourceProgressCallback = (message: string) => void;
|
|
|
49
32
|
* @returns names of the directories that were removed
|
|
50
33
|
*/
|
|
51
34
|
export declare function sweepOrphanedEngineWorkDirs(destDir: string): Promise<string[]>;
|
|
35
|
+
/** Inputs for {@link downloadFirefoxSource}. */
|
|
36
|
+
export interface DownloadFirefoxSourceOptions {
|
|
37
|
+
/** Firefox version to fetch, e.g. `140.9.0esr`. */
|
|
38
|
+
version: string;
|
|
39
|
+
/** Product channel the version belongs to. */
|
|
40
|
+
product: FirefoxProduct;
|
|
41
|
+
/** Directory the extracted source tree is moved into. */
|
|
42
|
+
destDir: string;
|
|
43
|
+
/** Directory holding downloaded tarballs between runs. */
|
|
44
|
+
cacheDir: string;
|
|
45
|
+
/** Called with byte progress for the whole transfer. */
|
|
46
|
+
onProgress?: ProgressCallback | undefined;
|
|
47
|
+
/** Called when the run moves from downloading to extracting. */
|
|
48
|
+
onPhase?: FirefoxSourcePhaseCallback | undefined;
|
|
49
|
+
/** Expected archive checksum; verified after download when supplied. */
|
|
50
|
+
expectedSha256?: string | undefined;
|
|
51
|
+
/** Called with byte progress within the current phase. */
|
|
52
|
+
onPhaseProgress?: FirefoxSourceProgressCallback | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Release-candidate build directory (e.g. `build2`), resolving the archive
|
|
55
|
+
* under `candidates/` instead of `releases/`.
|
|
56
|
+
*/
|
|
57
|
+
candidate?: string | undefined;
|
|
58
|
+
/** Integrity-check defaults for unpinned downloads. */
|
|
59
|
+
integrity?: ArchiveIntegrityOptions | undefined;
|
|
60
|
+
}
|
|
52
61
|
/**
|
|
53
62
|
* Downloads and extracts Firefox source.
|
|
54
|
-
*
|
|
55
|
-
* @param
|
|
56
|
-
* @param destDir - Destination directory for extracted source
|
|
57
|
-
* @param cacheDir - Directory to store downloaded tarball
|
|
58
|
-
* @param onProgress - Optional progress callback for the download byte stream
|
|
59
|
-
* @param onPhase - Optional callback fired when the function transitions
|
|
60
|
-
* between phases (`'download'` → `'extract'`). Fires exactly once per
|
|
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
|
|
64
|
-
* @param candidate - Optional release-candidate build directory (e.g.
|
|
65
|
-
* "build2") resolving the archive under `candidates/` instead of
|
|
66
|
-
* `releases/`.
|
|
63
|
+
*
|
|
64
|
+
* @param options - See {@link DownloadFirefoxSourceOptions}.
|
|
67
65
|
*/
|
|
68
|
-
export declare function downloadFirefoxSource(
|
|
66
|
+
export declare function downloadFirefoxSource(options: DownloadFirefoxSourceOptions): Promise<void>;
|
package/dist/src/core/firefox.js
CHANGED
|
@@ -14,28 +14,7 @@ import { resolveArchive } from './firefox-archive.js';
|
|
|
14
14
|
import { ensureCachedArchive, invalidateArchiveCache } from './firefox-cache.js';
|
|
15
15
|
import { extractTarXz } from './firefox-extract.js';
|
|
16
16
|
// ── Re-exports (preserve public API) ──
|
|
17
|
-
export { resolveArchive } from './firefox-archive.js';
|
|
18
17
|
export { formatBytes, getFirefoxVersion } from './firefox-extract.js';
|
|
19
|
-
/**
|
|
20
|
-
* Gets the download URL for a Firefox source tarball.
|
|
21
|
-
* @param version - Firefox version (e.g., "140.9.0esr")
|
|
22
|
-
* @param product - Firefox product type
|
|
23
|
-
* @param candidate - Optional release-candidate build directory (e.g. "build2")
|
|
24
|
-
* @returns Full URL to the source tarball
|
|
25
|
-
*/
|
|
26
|
-
export function getDownloadUrl(version, product = 'firefox', candidate) {
|
|
27
|
-
return resolveArchive(version, product, candidate).url;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Gets the filename for a Firefox source tarball.
|
|
31
|
-
* @param version - Firefox version
|
|
32
|
-
* @param product - Firefox product type
|
|
33
|
-
* @param candidate - Optional release-candidate build directory (e.g. "build2")
|
|
34
|
-
* @returns Tarball filename
|
|
35
|
-
*/
|
|
36
|
-
export function getTarballFilename(version, product = 'firefox', candidate) {
|
|
37
|
-
return resolveArchive(version, product, candidate).filename;
|
|
38
|
-
}
|
|
39
18
|
/**
|
|
40
19
|
* Classifies an extraction failure as archive corruption (invalidate the
|
|
41
20
|
* cache and re-download) versus an environmental problem (keep the cached
|
|
@@ -95,27 +74,17 @@ export async function sweepOrphanedEngineWorkDirs(destDir) {
|
|
|
95
74
|
}
|
|
96
75
|
/**
|
|
97
76
|
* Downloads and extracts Firefox source.
|
|
98
|
-
*
|
|
99
|
-
* @param
|
|
100
|
-
* @param destDir - Destination directory for extracted source
|
|
101
|
-
* @param cacheDir - Directory to store downloaded tarball
|
|
102
|
-
* @param onProgress - Optional progress callback for the download byte stream
|
|
103
|
-
* @param onPhase - Optional callback fired when the function transitions
|
|
104
|
-
* between phases (`'download'` → `'extract'`). Fires exactly once per
|
|
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
|
-
* @param candidate - Optional release-candidate build directory (e.g.
|
|
109
|
-
* "build2") resolving the archive under `candidates/` instead of
|
|
110
|
-
* `releases/`.
|
|
77
|
+
*
|
|
78
|
+
* @param options - See {@link DownloadFirefoxSourceOptions}.
|
|
111
79
|
*/
|
|
112
|
-
export async function downloadFirefoxSource(
|
|
80
|
+
export async function downloadFirefoxSource(options) {
|
|
81
|
+
const { version, product, destDir, cacheDir, onProgress, onPhase, expectedSha256, onPhaseProgress, candidate, integrity, } = options;
|
|
113
82
|
const archive = resolveArchive(version, product, candidate);
|
|
114
83
|
const tarballPath = join(cacheDir, archive.filename);
|
|
115
84
|
// Ensure cache directory exists
|
|
116
85
|
await ensureDir(cacheDir);
|
|
117
86
|
onPhase?.('download');
|
|
118
|
-
await ensureCachedArchive(archive, cacheDir, onProgress, expectedSha256, onPhaseProgress);
|
|
87
|
+
await ensureCachedArchive(archive, cacheDir, onProgress, expectedSha256, onPhaseProgress, integrity);
|
|
119
88
|
// Extract to a unique temporary directory so concurrent downloads for
|
|
120
89
|
// the same destination do not clobber each other.
|
|
121
90
|
onPhase?.('extract');
|
|
@@ -4,14 +4,9 @@
|
|
|
4
4
|
* stay within the per-file line budget. Consumed only by that module.
|
|
5
5
|
*/
|
|
6
6
|
import type { ComponentApplyContext, CustomComponentConfig, DryRunAction, StepError } from '../types/furnace.js';
|
|
7
|
-
|
|
8
|
-
isFile(): boolean;
|
|
9
|
-
isSymbolicLink?(): boolean;
|
|
10
|
-
name: string;
|
|
11
|
-
}
|
|
7
|
+
import { type DirectoryEntry } from './furnace-dir-entry.js';
|
|
12
8
|
/** Computes the planned dry-run actions (and pre-flight step errors) for a custom component. */
|
|
13
9
|
export declare function buildCustomDryRunActions(ctx: Pick<ComponentApplyContext, 'engineDir' | 'name' | 'componentDir' | 'ftlDir'>, config: CustomComponentConfig, targetDir: string, entries: DirectoryEntry[]): Promise<{
|
|
14
10
|
actions: DryRunAction[];
|
|
15
11
|
stepErrors: StepError[];
|
|
16
12
|
}>;
|
|
17
|
-
export {};
|
|
@@ -9,14 +9,8 @@ import { toError } from '../utils/errors.js';
|
|
|
9
9
|
import { pathExists } from '../utils/fs.js';
|
|
10
10
|
import { describeLocaleFtlJarMnRegistration, describeSharedFtlPrune } from './furnace-apply-ftl.js';
|
|
11
11
|
import { describeFragmentExpansion } from './furnace-css-fragments.js';
|
|
12
|
+
import { isRegularFile } from './furnace-dir-entry.js';
|
|
12
13
|
import { validateCustomElementRegistration, validateJarMnInsertionForFiles, } from './furnace-registration.js';
|
|
13
|
-
function isRegularFile(entry) {
|
|
14
|
-
if (!entry.isFile())
|
|
15
|
-
return false;
|
|
16
|
-
if (typeof entry.isSymbolicLink === 'function' && entry.isSymbolicLink())
|
|
17
|
-
return false;
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
14
|
/** Computes the planned dry-run actions (and pre-flight step errors) for a custom component. */
|
|
21
15
|
export async function buildCustomDryRunActions(ctx, config, targetDir, entries) {
|
|
22
16
|
const { engineDir, name, componentDir, ftlDir } = ctx;
|
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
import type { ComponentApplyContext, CustomComponentConfig, DryRunAction, OverrideComponentConfig, StepError } from '../types/furnace.js';
|
|
2
2
|
import { type RollbackJournal } from './furnace-rollback.js';
|
|
3
|
-
interface DirectoryEntry {
|
|
4
|
-
isFile(): boolean;
|
|
5
|
-
isSymbolicLink?(): boolean;
|
|
6
|
-
name: string;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* True for a plain-file directory entry — symlinks and directories are
|
|
10
|
-
* never copy candidates. Exported for the patch-owned overwrite probe,
|
|
11
|
-
* which walks the same override copy-candidate set as apply.
|
|
12
|
-
*/
|
|
13
|
-
export declare function isRegularFile(entry: DirectoryEntry): boolean;
|
|
14
3
|
/**
|
|
15
4
|
* Filter deciding which files in an override workspace directory are candidates
|
|
16
5
|
* for copying into the engine. Exported so `furnace remove` can invert apply
|