@hominis/fireforge 0.34.2 → 0.36.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 +48 -0
- package/README.md +43 -5
- package/dist/bin/fireforge.d.ts +4 -0
- package/dist/bin/fireforge.js +25 -6
- package/dist/src/cli.js +22 -5
- package/dist/src/commands/bootstrap-checks.js +0 -1
- package/dist/src/commands/bootstrap.d.ts +2 -1
- package/dist/src/commands/bootstrap.js +16 -7
- package/dist/src/commands/build.js +17 -3
- package/dist/src/commands/config.js +30 -10
- package/dist/src/commands/discard.js +24 -5
- package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
- package/dist/src/commands/doctor-check-core.js +0 -1
- package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
- package/dist/src/commands/doctor-external-toolchains.js +54 -0
- package/dist/src/commands/doctor-furnace-jar.js +0 -1
- package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
- package/dist/src/commands/doctor-furnace.js +2 -2
- package/dist/src/commands/doctor-working-tree.js +0 -1
- package/dist/src/commands/doctor.js +4 -1
- package/dist/src/commands/download.js +51 -15
- package/dist/src/commands/export-all.js +5 -3
- package/dist/src/commands/export-flow.js +12 -7
- package/dist/src/commands/export-placement-gate.js +0 -1
- package/dist/src/commands/export-placement-policy.js +0 -1
- package/dist/src/commands/export-shared.d.ts +5 -1
- package/dist/src/commands/export-shared.js +18 -6
- package/dist/src/commands/export.js +15 -10
- package/dist/src/commands/furnace/apply.js +22 -6
- package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
- package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
- package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
- package/dist/src/commands/furnace/chrome-doc.js +0 -1
- package/dist/src/commands/furnace/create-browser-test.js +0 -1
- package/dist/src/commands/furnace/create-dry-run.js +0 -1
- package/dist/src/commands/furnace/create-features.js +0 -1
- package/dist/src/commands/furnace/create-mochikit.js +0 -1
- package/dist/src/commands/furnace/create-readback.js +0 -1
- package/dist/src/commands/furnace/create-templates.js +0 -1
- package/dist/src/commands/furnace/create-validation.js +0 -1
- package/dist/src/commands/furnace/create-xpcshell.js +0 -1
- package/dist/src/commands/furnace/create.js +0 -1
- package/dist/src/commands/furnace/deploy.js +7 -83
- package/dist/src/commands/furnace/diff.js +0 -1
- package/dist/src/commands/furnace/index.js +59 -25
- package/dist/src/commands/furnace/init.js +0 -1
- package/dist/src/commands/furnace/list.js +0 -1
- package/dist/src/commands/furnace/override.js +0 -1
- package/dist/src/commands/furnace/preview.js +3 -3
- package/dist/src/commands/furnace/refresh.js +0 -1
- package/dist/src/commands/furnace/remove-state.d.ts +15 -0
- package/dist/src/commands/furnace/remove-state.js +47 -1
- package/dist/src/commands/furnace/remove.js +3 -9
- package/dist/src/commands/furnace/rename-browser-test.js +1 -5
- package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
- package/dist/src/commands/furnace/rename-helpers.js +30 -1
- package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
- package/dist/src/commands/furnace/rename.js +15 -36
- package/dist/src/commands/furnace/scan.js +0 -1
- package/dist/src/commands/furnace/status.js +0 -1
- package/dist/src/commands/furnace/sync.js +76 -4
- package/dist/src/commands/furnace/validate.js +0 -1
- package/dist/src/commands/furnace/validation-output.js +0 -1
- package/dist/src/commands/import.js +35 -9
- package/dist/src/commands/lint-per-patch.js +19 -7
- package/dist/src/commands/lint.js +13 -3
- package/dist/src/commands/manifest.js +1 -2
- package/dist/src/commands/package.js +2 -1
- package/dist/src/commands/patch/compact.js +1 -1
- package/dist/src/commands/patch/delete.js +1 -1
- package/dist/src/commands/patch/index.js +1 -1
- package/dist/src/commands/patch/lint-ignore.js +21 -6
- package/dist/src/commands/patch/move-files.js +1 -1
- package/dist/src/commands/patch/patch-context.js +0 -1
- package/dist/src/commands/patch/rename.js +4 -4
- package/dist/src/commands/patch/reorder.js +3 -4
- package/dist/src/commands/patch/split-plan.js +2 -1
- package/dist/src/commands/patch/split.js +4 -4
- package/dist/src/commands/patch/staged-dependency.js +1 -1
- package/dist/src/commands/patch/tier.js +18 -5
- package/dist/src/commands/re-export-bulk-scan.js +0 -1
- package/dist/src/commands/re-export-files.js +1 -1
- package/dist/src/commands/re-export-options.js +0 -1
- package/dist/src/commands/re-export-scan.js +0 -1
- package/dist/src/commands/re-export.js +6 -4
- package/dist/src/commands/rebase/abort.js +2 -9
- package/dist/src/commands/rebase/confirm.js +0 -1
- package/dist/src/commands/rebase/conflict-summary.js +0 -1
- package/dist/src/commands/rebase/continue.js +0 -1
- package/dist/src/commands/rebase/index.d.ts +1 -1
- package/dist/src/commands/rebase/index.js +10 -15
- package/dist/src/commands/rebase/patch-loop.js +38 -7
- package/dist/src/commands/rebase/summary.js +2 -3
- package/dist/src/commands/rebase.js +0 -1
- package/dist/src/commands/register.js +1 -1
- package/dist/src/commands/reset.js +16 -9
- package/dist/src/commands/resolve.js +2 -2
- package/dist/src/commands/run.js +18 -6
- package/dist/src/commands/setup-support.js +0 -1
- package/dist/src/commands/setup.js +1 -2
- package/dist/src/commands/source.js +1 -2
- package/dist/src/commands/status-output.js +1 -12
- package/dist/src/commands/status.js +147 -133
- package/dist/src/commands/test-appdir.js +0 -1
- package/dist/src/commands/test-diagnose.js +18 -2
- package/dist/src/commands/test-modes.d.ts +15 -0
- package/dist/src/commands/test-modes.js +61 -0
- package/dist/src/commands/test-register.d.ts +4 -0
- package/dist/src/commands/test-register.js +64 -0
- package/dist/src/commands/test-run.d.ts +23 -7
- package/dist/src/commands/test-run.js +15 -14
- package/dist/src/commands/test.d.ts +0 -4
- package/dist/src/commands/test.js +95 -56
- package/dist/src/commands/token-coverage.js +0 -1
- package/dist/src/commands/token.js +1 -2
- package/dist/src/commands/typecheck.js +1 -1
- package/dist/src/commands/verify.js +3 -9
- package/dist/src/commands/watch.js +5 -1
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.js +0 -1
- package/dist/src/core/brand-validation.js +0 -1
- package/dist/src/core/branding.js +0 -1
- package/dist/src/core/browser-wire.js +0 -1
- package/dist/src/core/build-audit-platform.js +0 -1
- package/dist/src/core/build-audit-registration.js +0 -1
- package/dist/src/core/build-audit-resolve.js +0 -1
- package/dist/src/core/build-audit-transforms.js +0 -1
- package/dist/src/core/build-audit.js +0 -1
- package/dist/src/core/build-baseline-types.js +0 -1
- package/dist/src/core/build-baseline.js +0 -1
- package/dist/src/core/build-prepare.js +21 -7
- package/dist/src/core/config-mutate.js +0 -1
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +6 -1
- package/dist/src/core/config-state.js +0 -1
- package/dist/src/core/config-validate-patch-policy.js +0 -1
- package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
- package/dist/src/core/config-validate-test-toolchains.js +105 -0
- package/dist/src/core/config-validate.js +3 -1
- package/dist/src/core/config.js +0 -1
- package/dist/src/core/destructive.js +0 -1
- package/dist/src/core/diff-hunks.js +13 -7
- package/dist/src/core/engine-changes.js +0 -1
- package/dist/src/core/engine-session-lock.d.ts +12 -0
- package/dist/src/core/engine-session-lock.js +45 -0
- package/dist/src/core/file-lock.d.ts +8 -0
- package/dist/src/core/file-lock.js +157 -48
- package/dist/src/core/firefox-archive.d.ts +4 -0
- package/dist/src/core/firefox-archive.js +6 -1
- package/dist/src/core/firefox-cache.js +67 -8
- package/dist/src/core/firefox-download.js +0 -1
- package/dist/src/core/firefox-extract.d.ts +21 -1
- package/dist/src/core/firefox-extract.js +178 -9
- package/dist/src/core/firefox-ignorefile.js +0 -1
- package/dist/src/core/firefox.d.ts +12 -0
- package/dist/src/core/firefox.js +64 -2
- package/dist/src/core/furnace-apply-dry-run.js +0 -1
- package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
- package/dist/src/core/furnace-apply-ftl.js +10 -5
- package/dist/src/core/furnace-apply-helpers.js +0 -1
- package/dist/src/core/furnace-apply-output.js +2 -2
- package/dist/src/core/furnace-apply.d.ts +7 -1
- package/dist/src/core/furnace-apply.js +47 -13
- package/dist/src/core/furnace-checksum-utils.js +0 -1
- package/dist/src/core/furnace-config-array-utils.js +0 -1
- package/dist/src/core/furnace-config-custom.js +0 -1
- package/dist/src/core/furnace-config-order.js +0 -1
- package/dist/src/core/furnace-config-tokens.js +0 -1
- package/dist/src/core/furnace-config-validate.js +0 -1
- package/dist/src/core/furnace-config.d.ts +11 -0
- package/dist/src/core/furnace-config.js +37 -1
- package/dist/src/core/furnace-constants.js +0 -1
- package/dist/src/core/furnace-css-fragments.js +0 -1
- package/dist/src/core/furnace-graph-utils.js +0 -1
- package/dist/src/core/furnace-jsconfig.d.ts +2 -3
- package/dist/src/core/furnace-jsconfig.js +25 -18
- package/dist/src/core/furnace-marker.js +0 -1
- package/dist/src/core/furnace-operation.js +0 -1
- package/dist/src/core/furnace-refresh.js +0 -1
- package/dist/src/core/furnace-registration-ast.d.ts +14 -0
- package/dist/src/core/furnace-registration-ast.js +1 -2
- package/dist/src/core/furnace-registration-remove.js +0 -1
- package/dist/src/core/furnace-registration-validate.js +1 -5
- package/dist/src/core/furnace-registration.js +0 -1
- package/dist/src/core/furnace-rollback.js +0 -1
- package/dist/src/core/furnace-scanner.js +0 -1
- package/dist/src/core/furnace-staleness.js +0 -1
- package/dist/src/core/furnace-state-persist.d.ts +72 -0
- package/dist/src/core/furnace-state-persist.js +118 -0
- package/dist/src/core/furnace-step-errors.d.ts +23 -0
- package/dist/src/core/furnace-step-errors.js +25 -0
- package/dist/src/core/furnace-stories.js +0 -1
- package/dist/src/core/furnace-validate-accessibility.js +0 -1
- package/dist/src/core/furnace-validate-checks.js +0 -1
- package/dist/src/core/furnace-validate-compatibility.js +0 -1
- package/dist/src/core/furnace-validate-helpers.js +0 -1
- package/dist/src/core/furnace-validate-registration.js +7 -7
- package/dist/src/core/furnace-validate-structure.d.ts +8 -0
- package/dist/src/core/furnace-validate-structure.js +11 -2
- package/dist/src/core/furnace-validate.js +0 -1
- package/dist/src/core/furnace-version-drift.d.ts +6 -3
- package/dist/src/core/furnace-version-drift.js +6 -4
- package/dist/src/core/git-base.js +0 -1
- package/dist/src/core/git-diff.js +0 -1
- package/dist/src/core/git-file-ops.js +0 -1
- package/dist/src/core/git-status.d.ts +13 -0
- package/dist/src/core/git-status.js +32 -2
- package/dist/src/core/git.d.ts +10 -1
- package/dist/src/core/git.js +65 -8
- package/dist/src/core/license-headers.d.ts +16 -0
- package/dist/src/core/license-headers.js +19 -1
- package/dist/src/core/lint-cache.js +0 -1
- package/dist/src/core/lint-projection.js +0 -1
- package/dist/src/core/mach-build-artifacts.js +0 -1
- package/dist/src/core/mach-error-hints.js +27 -1
- package/dist/src/core/mach-mozconfig.js +0 -1
- package/dist/src/core/mach-python.js +0 -1
- package/dist/src/core/mach-resource-shim.js +0 -1
- package/dist/src/core/mach.js +0 -1
- package/dist/src/core/manifest-helpers.js +0 -1
- package/dist/src/core/manifest-register.js +0 -1
- package/dist/src/core/manifest-rules.js +0 -1
- package/dist/src/core/manifest-tokenizers.js +0 -1
- package/dist/src/core/marionette-port.d.ts +8 -0
- package/dist/src/core/marionette-port.js +34 -3
- package/dist/src/core/marionette-preflight.js +0 -1
- package/dist/src/core/markdown-table.js +0 -1
- package/dist/src/core/ownership-table.js +2 -9
- package/dist/src/core/parser-fallback.js +0 -1
- package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
- package/dist/src/core/patch-apply-fuzz.js +71 -34
- package/dist/src/core/patch-apply.d.ts +30 -0
- package/dist/src/core/patch-apply.js +152 -39
- package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
- package/dist/src/core/patch-artifact-normalize.js +19 -15
- package/dist/src/core/patch-export-coverage.js +0 -1
- package/dist/src/core/patch-export-metadata.js +0 -1
- package/dist/src/core/patch-export-update.js +2 -3
- package/dist/src/core/patch-export.d.ts +2 -6
- package/dist/src/core/patch-export.js +24 -48
- package/dist/src/core/patch-files.js +0 -1
- package/dist/src/core/patch-identifier-suggest.js +0 -1
- package/dist/src/core/patch-lint-checkjs.js +0 -1
- package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
- package/dist/src/core/patch-lint-cross.js +0 -1
- package/dist/src/core/patch-lint-css.js +0 -1
- package/dist/src/core/patch-lint-diff-tag.js +0 -1
- package/dist/src/core/patch-lint-diff.d.ts +2 -1
- package/dist/src/core/patch-lint-diff.js +17 -32
- package/dist/src/core/patch-lint-imports.js +0 -1
- package/dist/src/core/patch-lint-jsdoc.js +0 -1
- package/dist/src/core/patch-lint-observer.js +0 -1
- package/dist/src/core/patch-lint-ownership.js +0 -1
- package/dist/src/core/patch-lint-reexports.js +0 -1
- package/dist/src/core/patch-lint.d.ts +37 -2
- package/dist/src/core/patch-lint.js +48 -24
- package/dist/src/core/patch-lock.js +0 -1
- package/dist/src/core/patch-manifest-consistency.js +9 -1
- package/dist/src/core/patch-manifest-io.d.ts +16 -0
- package/dist/src/core/patch-manifest-io.js +37 -12
- package/dist/src/core/patch-manifest-query.d.ts +6 -0
- package/dist/src/core/patch-manifest-query.js +27 -12
- package/dist/src/core/patch-manifest-resolve.js +0 -1
- package/dist/src/core/patch-manifest-validate.js +0 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -2
- package/dist/src/core/patch-parse.d.ts +59 -0
- package/dist/src/core/patch-parse.js +209 -92
- package/dist/src/core/patch-policy.js +0 -1
- package/dist/src/core/patch-registration-refs.js +16 -26
- package/dist/src/core/patch-source-metadata.js +0 -1
- package/dist/src/core/patch-transform.d.ts +4 -0
- package/dist/src/core/patch-transform.js +30 -38
- package/dist/src/core/rebase-session.js +0 -1
- package/dist/src/core/register-browser-content.js +0 -1
- package/dist/src/core/register-module.js +0 -1
- package/dist/src/core/register-result.js +0 -1
- package/dist/src/core/register-scaffold.js +0 -1
- package/dist/src/core/register-shared-css.js +0 -1
- package/dist/src/core/register-test-manifest.js +0 -1
- package/dist/src/core/register-xpcshell-test.js +0 -1
- package/dist/src/core/shared-ftl.js +0 -1
- package/dist/src/core/signal-critical.js +0 -1
- package/dist/src/core/smoke-patterns.js +0 -1
- package/dist/src/core/state-file.js +0 -1
- package/dist/src/core/status-classify.d.ts +16 -0
- package/dist/src/core/status-classify.js +24 -16
- package/dist/src/core/test-harness-crash.d.ts +64 -7
- package/dist/src/core/test-harness-crash.js +196 -15
- package/dist/src/core/test-harness-output.js +0 -1
- package/dist/src/core/test-path-scope.d.ts +62 -0
- package/dist/src/core/test-path-scope.js +148 -0
- package/dist/src/core/test-stale-check.js +0 -1
- package/dist/src/core/test-stale-symlink.js +0 -1
- package/dist/src/core/test-xpcshell-retry.js +0 -1
- package/dist/src/core/token-coverage.js +0 -1
- package/dist/src/core/token-dark-mode.js +0 -1
- package/dist/src/core/token-docs.js +0 -1
- package/dist/src/core/token-manager.js +0 -1
- package/dist/src/core/token-scaffold.js +0 -1
- package/dist/src/core/token-variant.js +0 -1
- package/dist/src/core/toolchain-preflight.d.ts +85 -0
- package/dist/src/core/toolchain-preflight.js +256 -0
- package/dist/src/core/typecheck-shim.d.ts +4 -1
- package/dist/src/core/typecheck-shim.js +61 -18
- package/dist/src/core/typecheck.js +0 -1
- package/dist/src/core/wire-destroy.js +0 -1
- package/dist/src/core/wire-dom-fragment.js +0 -1
- package/dist/src/core/wire-init.js +0 -1
- package/dist/src/core/wire-subscript.js +0 -1
- package/dist/src/core/wire-targets.js +0 -1
- package/dist/src/core/wire-utils.js +0 -1
- package/dist/src/core/xpcshell-appdir.js +1 -4
- package/dist/src/errors/base.d.ts +29 -0
- package/dist/src/errors/base.js +35 -1
- package/dist/src/errors/build.js +0 -1
- package/dist/src/errors/codes.d.ts +6 -0
- package/dist/src/errors/codes.js +6 -1
- package/dist/src/errors/config.js +0 -1
- package/dist/src/errors/download.js +2 -1
- package/dist/src/errors/furnace.d.ts +11 -1
- package/dist/src/errors/furnace.js +6 -5
- package/dist/src/errors/git.js +0 -1
- package/dist/src/errors/patch.d.ts +17 -0
- package/dist/src/errors/patch.js +29 -1
- package/dist/src/errors/rebase.js +0 -1
- package/dist/src/errors/run.js +1 -1
- package/dist/src/index.js +0 -1
- package/dist/src/types/cli.js +0 -1
- package/dist/src/types/commands/index.js +0 -1
- package/dist/src/types/commands/options.d.ts +20 -5
- package/dist/src/types/commands/options.js +0 -1
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/commands/patches.js +0 -1
- package/dist/src/types/commands/project.d.ts +11 -2
- package/dist/src/types/commands/project.js +0 -1
- package/dist/src/types/config.d.ts +32 -0
- package/dist/src/types/config.js +0 -1
- package/dist/src/types/furnace.d.ts +10 -0
- package/dist/src/types/furnace.js +0 -1
- package/dist/src/types/index.js +0 -1
- package/dist/src/types/typecheck.js +0 -1
- package/dist/src/utils/elapsed.js +0 -1
- package/dist/src/utils/errors.js +0 -1
- package/dist/src/utils/fs.js +37 -5
- package/dist/src/utils/logger.d.ts +7 -0
- package/dist/src/utils/logger.js +85 -3
- package/dist/src/utils/options.d.ts +15 -0
- package/dist/src/utils/options.js +25 -1
- package/dist/src/utils/package-root.js +11 -4
- package/dist/src/utils/parse.js +0 -1
- package/dist/src/utils/paths.js +0 -1
- package/dist/src/utils/platform.js +0 -1
- package/dist/src/utils/process.d.ts +33 -4
- package/dist/src/utils/process.js +183 -28
- package/dist/src/utils/regex.d.ts +13 -3
- package/dist/src/utils/regex.js +53 -4
- package/dist/src/utils/validation.d.ts +0 -12
- package/dist/src/utils/validation.js +13 -26
- package/package.json +8 -7
|
@@ -9,8 +9,8 @@ import { join } from 'node:path';
|
|
|
9
9
|
import { pipeline } from 'node:stream/promises';
|
|
10
10
|
import { ChecksumMismatchError } from '../errors/download.js';
|
|
11
11
|
import { toError } from '../utils/errors.js';
|
|
12
|
-
import {
|
|
13
|
-
import { verbose } from '../utils/logger.js';
|
|
12
|
+
import { pathExistsStrict, readJson, removeFile, writeJson } from '../utils/fs.js';
|
|
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
16
|
import { downloadFile } from './firefox-download.js';
|
|
@@ -24,6 +24,39 @@ async function sha256File(filePath) {
|
|
|
24
24
|
await pipeline(stream, hash);
|
|
25
25
|
return hash.digest('hex');
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Fetches Mozilla's published SHA256SUMS for the release and extracts the
|
|
29
|
+
* digest for this archive. Returns null when the checksum file cannot be
|
|
30
|
+
* fetched or does not list the archive — the caller warns loudly and
|
|
31
|
+
* continues (offline mirrors and staging hosts legitimately lack it), while
|
|
32
|
+
* a FETCHED-but-mismatching digest always fails closed.
|
|
33
|
+
*
|
|
34
|
+
* Line format (both variants appear in the wild):
|
|
35
|
+
* `<64-hex-digest> <release-relative-path>`
|
|
36
|
+
* `<64-hex-digest> *<release-relative-path>`
|
|
37
|
+
*/
|
|
38
|
+
async function fetchPublishedSha256(archive) {
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch(archive.checksumsUrl);
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
verbose(`SHA256SUMS fetch returned HTTP ${String(response.status)} for ${archive.checksumsUrl}`);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const body = await response.text();
|
|
46
|
+
for (const line of body.split('\n')) {
|
|
47
|
+
const match = /^([0-9a-f]{64})\s+\*?(.+)$/.exec(line.trim());
|
|
48
|
+
if (match?.[1] && match[2] === archive.pathInChecksums) {
|
|
49
|
+
return match[1];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
verbose(`SHA256SUMS at ${archive.checksumsUrl} does not list ${archive.pathInChecksums}`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
verbose(`SHA256SUMS fetch failed for ${archive.checksumsUrl}: ${toError(error).message}`);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
27
60
|
/**
|
|
28
61
|
* Ensures a valid cached archive exists, downloading it if needed.
|
|
29
62
|
* @param archive - Resolved archive descriptor
|
|
@@ -49,8 +82,8 @@ export async function ensureCachedArchive(archive, cacheDir, onProgress, expecte
|
|
|
49
82
|
});
|
|
50
83
|
}
|
|
51
84
|
async function cacheEntryExists(archive, cacheDir) {
|
|
52
|
-
return ((await
|
|
53
|
-
(await
|
|
85
|
+
return ((await pathExistsStrict(join(cacheDir, archive.filename))) ||
|
|
86
|
+
(await pathExistsStrict(join(cacheDir, archive.metadataFilename))));
|
|
54
87
|
}
|
|
55
88
|
/**
|
|
56
89
|
* Validates a cached archive using sidecar metadata and SHA-256 checksum.
|
|
@@ -61,7 +94,10 @@ async function cacheEntryExists(archive, cacheDir) {
|
|
|
61
94
|
async function validateCachedArchive(archive, cacheDir, expectedSha256) {
|
|
62
95
|
const tarballPath = join(cacheDir, archive.filename);
|
|
63
96
|
const metadataPath = join(cacheDir, archive.metadataFilename);
|
|
64
|
-
|
|
97
|
+
// Deliberately outside the try/catch below: a permission error probing the
|
|
98
|
+
// cache must propagate instead of reading as "invalid cache" and triggering
|
|
99
|
+
// a re-download into a directory we cannot write anyway.
|
|
100
|
+
if (!(await pathExistsStrict(tarballPath)) || !(await pathExistsStrict(metadataPath))) {
|
|
65
101
|
return false;
|
|
66
102
|
}
|
|
67
103
|
try {
|
|
@@ -114,8 +150,32 @@ async function downloadToCache(archive, cacheDir, onProgress, expectedSha256, on
|
|
|
114
150
|
promotedTarball = true;
|
|
115
151
|
onCacheProgress?.(`Calculating source archive SHA-256 for ${archive.filename}...`);
|
|
116
152
|
const sha256 = await sha256File(tarballPath);
|
|
117
|
-
if (expectedSha256
|
|
118
|
-
|
|
153
|
+
if (expectedSha256) {
|
|
154
|
+
// Operator-pinned digest (firefox.sha256 config): takes precedence
|
|
155
|
+
// and always fails closed.
|
|
156
|
+
if (sha256 !== expectedSha256) {
|
|
157
|
+
throw new ChecksumMismatchError(archive.product, expectedSha256, sha256, archive.url);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// Default integrity check: verify against Mozilla's published
|
|
162
|
+
// SHA256SUMS. TLS alone is thin trust for the artifact that becomes
|
|
163
|
+
// the git baseline every patch is built on — a compromised CDN
|
|
164
|
+
// response would otherwise be trusted with no signal. Mismatch fails
|
|
165
|
+
// closed (the catch below deletes the artifact); an unfetchable
|
|
166
|
+
// SHA256SUMS degrades to a loud warning so offline/mirror workflows
|
|
167
|
+
// keep working (pin firefox.sha256 in fireforge.json to fail closed
|
|
168
|
+
// even then).
|
|
169
|
+
onCacheProgress?.(`Verifying archive against published SHA256SUMS...`);
|
|
170
|
+
const publishedSha256 = await fetchPublishedSha256(archive);
|
|
171
|
+
if (publishedSha256 === null) {
|
|
172
|
+
warn(`Could not verify ${archive.filename} against Mozilla's published SHA256SUMS ` +
|
|
173
|
+
`(${archive.checksumsUrl} unavailable). The download is trusted on TLS alone — ` +
|
|
174
|
+
'set firefox.sha256 in fireforge.json to require checksum verification.');
|
|
175
|
+
}
|
|
176
|
+
else if (sha256 !== publishedSha256) {
|
|
177
|
+
throw new ChecksumMismatchError(archive.product, publishedSha256, sha256, archive.url);
|
|
178
|
+
}
|
|
119
179
|
}
|
|
120
180
|
onCacheProgress?.(`Writing source archive cache metadata for ${archive.metadataFilename}...`);
|
|
121
181
|
await writeJson(metadataPath, {
|
|
@@ -164,4 +224,3 @@ async function removeArchivePartFiles(archive, cacheDir) {
|
|
|
164
224
|
// Cache dir may not exist yet — that's fine.
|
|
165
225
|
}
|
|
166
226
|
}
|
|
167
|
-
//# sourceMappingURL=firefox-cache.js.map
|
|
@@ -2,7 +2,27 @@
|
|
|
2
2
|
* Firefox source extraction and installed version detection.
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Validates entry names from a `tar -tf` listing.
|
|
6
|
+
* @param names - Listing lines (one member name per line)
|
|
7
|
+
* @returns The first unsafe member name, or null when all are safe
|
|
8
|
+
*/
|
|
9
|
+
export declare function findUnsafeArchiveEntryName(names: readonly string[]): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Validates symlink and hardlink targets from a `tar -tvf` listing.
|
|
12
|
+
*
|
|
13
|
+
* A relative link target without `..` segments can only resolve inside the
|
|
14
|
+
* extraction root, so only absolute or `..`-containing targets are rejected.
|
|
15
|
+
* Symlinks are `l`-typed lines with a ` -> target` suffix (the LAST arrow is
|
|
16
|
+
* taken, so a link whose own name contains ` -> ` still parses to its real
|
|
17
|
+
* target); hardlinks print ` link to target` on GNU tar and bsdtar alike.
|
|
18
|
+
*
|
|
19
|
+
* @param verboseLines - `tar -tvf` listing lines
|
|
20
|
+
* @returns A description of the first unsafe link found, or null
|
|
21
|
+
*/
|
|
22
|
+
export declare function findUnsafeArchiveLink(verboseLines: readonly string[]): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Extracts a tar.xz archive after a listing preflight that rejects
|
|
25
|
+
* path-traversal member names and escaping link targets.
|
|
6
26
|
* @param archivePath - Path to the archive
|
|
7
27
|
* @param destDir - Destination directory
|
|
8
28
|
*/
|
|
@@ -6,9 +6,173 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { ExtractionError } from '../errors/download.js';
|
|
7
7
|
import { elapsedSince } from '../utils/elapsed.js';
|
|
8
8
|
import { ensureDir, pathExists } from '../utils/fs.js';
|
|
9
|
-
import { exec, executableExists } from '../utils/process.js';
|
|
9
|
+
import { exec, execStream, executableExists } from '../utils/process.js';
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Returns true when an archive member path could land outside the
|
|
12
|
+
* extraction root: absolute (POSIX or Windows drive/backslash rooted) or
|
|
13
|
+
* containing a `..` segment.
|
|
14
|
+
*/
|
|
15
|
+
function isUnsafeArchivePath(path) {
|
|
16
|
+
if (path.startsWith('/') || path.startsWith('\\'))
|
|
17
|
+
return true;
|
|
18
|
+
if (/^[A-Za-z]:[\\/]/.test(path))
|
|
19
|
+
return true;
|
|
20
|
+
return path.split(/[\\/]/).includes('..');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Validates entry names from a `tar -tf` listing.
|
|
24
|
+
* @param names - Listing lines (one member name per line)
|
|
25
|
+
* @returns The first unsafe member name, or null when all are safe
|
|
26
|
+
*/
|
|
27
|
+
export function findUnsafeArchiveEntryName(names) {
|
|
28
|
+
for (const raw of names) {
|
|
29
|
+
const name = raw.trim();
|
|
30
|
+
if (name.length === 0)
|
|
31
|
+
continue;
|
|
32
|
+
if (isUnsafeArchivePath(name))
|
|
33
|
+
return name;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validates symlink and hardlink targets from a `tar -tvf` listing.
|
|
39
|
+
*
|
|
40
|
+
* A relative link target without `..` segments can only resolve inside the
|
|
41
|
+
* extraction root, so only absolute or `..`-containing targets are rejected.
|
|
42
|
+
* Symlinks are `l`-typed lines with a ` -> target` suffix (the LAST arrow is
|
|
43
|
+
* taken, so a link whose own name contains ` -> ` still parses to its real
|
|
44
|
+
* target); hardlinks print ` link to target` on GNU tar and bsdtar alike.
|
|
45
|
+
*
|
|
46
|
+
* @param verboseLines - `tar -tvf` listing lines
|
|
47
|
+
* @returns A description of the first unsafe link found, or null
|
|
48
|
+
*/
|
|
49
|
+
export function findUnsafeArchiveLink(verboseLines) {
|
|
50
|
+
for (const line of verboseLines) {
|
|
51
|
+
if (line.startsWith('l')) {
|
|
52
|
+
const arrow = line.lastIndexOf(' -> ');
|
|
53
|
+
if (arrow !== -1) {
|
|
54
|
+
const target = line.slice(arrow + 4).trim();
|
|
55
|
+
if (isUnsafeArchivePath(target))
|
|
56
|
+
return `symlink target: ${target}`;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const hardlink = line.lastIndexOf(' link to ');
|
|
61
|
+
if (hardlink !== -1) {
|
|
62
|
+
const target = line.slice(hardlink + 9).trim();
|
|
63
|
+
if (isUnsafeArchivePath(target))
|
|
64
|
+
return `hardlink target: ${target}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Lists the archive and rejects members that could escape the extraction
|
|
71
|
+
* root before anything is written to disk: absolute names, `..` traversal,
|
|
72
|
+
* and symlink/hardlink targets that are absolute or `..`-escaping. Modern
|
|
73
|
+
* GNU tar / bsdtar refuse most of these at extraction time; the preflight
|
|
74
|
+
* makes the guarantee explicit and independent of the host tar's defaults.
|
|
75
|
+
*
|
|
76
|
+
* Two listings are needed because they answer different questions and
|
|
77
|
+
* neither is safe to derive from the other: member names come from `-tf`,
|
|
78
|
+
* where the whole line IS the name, while link targets only appear in
|
|
79
|
+
* `-tvf`. Names are deliberately NOT parsed out of the `-tvf` columns — the
|
|
80
|
+
* adjacent uname/gname fields are attacker-controlled in a crafted archive,
|
|
81
|
+
* so a date-shaped owner name could shift the column boundary and hide an
|
|
82
|
+
* absolute member name from the check. Each listing pass costs one full
|
|
83
|
+
* decompression, so the two run concurrently and overlap almost perfectly
|
|
84
|
+
* (they are CPU-bound on separate cores). Measured on a real Firefox ESR
|
|
85
|
+
* 140.9 source tarball (601 MB, bsdtar 3.5.3, macOS): -tf 21.7 s, -tvf
|
|
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
|
|
89
|
+
* sequentially.
|
|
90
|
+
*/
|
|
91
|
+
/**
|
|
92
|
+
* Incremental line splitter for streamed listings. Keeps only the current
|
|
93
|
+
* partial line in memory, so a 350k-entry Firefox listing costs O(longest
|
|
94
|
+
* line), not O(listing).
|
|
95
|
+
*/
|
|
96
|
+
function createLineScanner(onLine) {
|
|
97
|
+
let buffer = '';
|
|
98
|
+
return {
|
|
99
|
+
push(chunk) {
|
|
100
|
+
buffer += chunk;
|
|
101
|
+
let idx;
|
|
102
|
+
while ((idx = buffer.indexOf('\n')) !== -1) {
|
|
103
|
+
onLine(buffer.slice(0, idx));
|
|
104
|
+
buffer = buffer.slice(idx + 1);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
flush() {
|
|
108
|
+
if (buffer.length > 0) {
|
|
109
|
+
onLine(buffer);
|
|
110
|
+
buffer = '';
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Runs one tar listing pass, streaming lines through `checkLine` and
|
|
117
|
+
* returning the first unsafe finding (or null) plus the exit code.
|
|
118
|
+
*
|
|
119
|
+
* Streaming matters for safety, not just memory: the buffered `exec`
|
|
120
|
+
* collector silently truncates at 50 MB, and a full Firefox `-tvf` listing
|
|
121
|
+
* already sits in the 40–50 MB range — so the buffered implementation could
|
|
122
|
+
* scan only the head of the listing while reporting the archive safe. A
|
|
123
|
+
* crafted archive could exploit exactly that by padding benign entries
|
|
124
|
+
* first and hiding a traversal name or absolute link target past the cap.
|
|
125
|
+
* With per-line streaming EVERY entry is scanned, with bounded memory.
|
|
126
|
+
*/
|
|
127
|
+
async function runListingScan(archivePath, tarFlag, checkLine) {
|
|
128
|
+
// LC_ALL=C keeps the -tvf column format stable for the link parse.
|
|
129
|
+
const listEnv = { LC_ALL: 'C', LANG: 'C' };
|
|
130
|
+
let unsafe = null;
|
|
131
|
+
let stderrTail = '';
|
|
132
|
+
const scanner = createLineScanner((line) => {
|
|
133
|
+
if (unsafe === null) {
|
|
134
|
+
unsafe = checkLine(line);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
const exitCode = await execStream('tar', [tarFlag, archivePath], {
|
|
138
|
+
env: listEnv,
|
|
139
|
+
onStdout: (chunk) => {
|
|
140
|
+
scanner.push(chunk);
|
|
141
|
+
},
|
|
142
|
+
onStderr: (chunk) => {
|
|
143
|
+
// Keep a small diagnostic tail; the listing itself is the big stream.
|
|
144
|
+
stderrTail = (stderrTail + chunk).slice(-4096);
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
scanner.flush();
|
|
148
|
+
return { unsafe, exitCode, stderrTail };
|
|
149
|
+
}
|
|
150
|
+
async function preflightArchiveEntries(archivePath) {
|
|
151
|
+
const [nameScan, linkScan] = await Promise.all([
|
|
152
|
+
runListingScan(archivePath, '-tf', (line) => {
|
|
153
|
+
const name = line.trim();
|
|
154
|
+
if (name.length === 0)
|
|
155
|
+
return null;
|
|
156
|
+
return isUnsafeArchivePath(name) ? name : null;
|
|
157
|
+
}),
|
|
158
|
+
runListingScan(archivePath, '-tvf', (line) => findUnsafeArchiveLink([line])),
|
|
159
|
+
]);
|
|
160
|
+
if (nameScan.exitCode !== 0) {
|
|
161
|
+
throw new ExtractionError(archivePath, new Error(`tar -tf preflight exited with code ${nameScan.exitCode}:\n${nameScan.stderrTail}`));
|
|
162
|
+
}
|
|
163
|
+
if (nameScan.unsafe !== null) {
|
|
164
|
+
throw new ExtractionError(archivePath, new Error(`Archive rejected: member name could escape the extraction root: ${nameScan.unsafe}`));
|
|
165
|
+
}
|
|
166
|
+
if (linkScan.exitCode !== 0) {
|
|
167
|
+
throw new ExtractionError(archivePath, new Error(`tar -tvf preflight exited with code ${linkScan.exitCode}:\n${linkScan.stderrTail}`));
|
|
168
|
+
}
|
|
169
|
+
if (linkScan.unsafe !== null) {
|
|
170
|
+
throw new ExtractionError(archivePath, new Error(`Archive rejected: link could escape the extraction root (${linkScan.unsafe})`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Extracts a tar.xz archive after a listing preflight that rejects
|
|
175
|
+
* path-traversal member names and escaping link targets.
|
|
12
176
|
* @param archivePath - Path to the archive
|
|
13
177
|
* @param destDir - Destination directory
|
|
14
178
|
*/
|
|
@@ -18,18 +182,24 @@ export async function extractTarXz(archivePath, destDir, onProgress) {
|
|
|
18
182
|
}
|
|
19
183
|
await ensureDir(destDir);
|
|
20
184
|
const startedAt = Date.now();
|
|
21
|
-
onProgress?.(`
|
|
185
|
+
onProgress?.(`Validating source archive entries (${elapsedSince(startedAt)} elapsed)...`);
|
|
22
186
|
const heartbeat = onProgress
|
|
23
187
|
? setInterval(() => {
|
|
24
188
|
onProgress(`Extracting source archive (${elapsedSince(startedAt)} elapsed)...`);
|
|
25
189
|
}, 15_000)
|
|
26
190
|
: null;
|
|
27
191
|
heartbeat?.unref();
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
192
|
+
try {
|
|
193
|
+
await preflightArchiveEntries(archivePath);
|
|
194
|
+
onProgress?.(`Extracting source archive (${elapsedSince(startedAt)} elapsed)...`);
|
|
195
|
+
const result = await exec('tar', ['-xf', archivePath, '-C', destDir]);
|
|
196
|
+
if (result.exitCode !== 0) {
|
|
197
|
+
throw new ExtractionError(archivePath, new Error(`tar exited with code ${result.exitCode}:\n${result.stderr}`));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
if (heartbeat)
|
|
202
|
+
clearInterval(heartbeat);
|
|
33
203
|
}
|
|
34
204
|
onProgress?.(`Source archive extracted (${elapsedSince(startedAt)} elapsed)`);
|
|
35
205
|
}
|
|
@@ -62,4 +232,3 @@ export function formatBytes(bytes) {
|
|
|
62
232
|
}
|
|
63
233
|
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
64
234
|
}
|
|
65
|
-
//# sourceMappingURL=firefox-extract.js.map
|
|
@@ -35,6 +35,18 @@ export type FirefoxSourcePhase = 'download' | 'extract';
|
|
|
35
35
|
/** Callback fired at phase transitions during {@link downloadFirefoxSource}. */
|
|
36
36
|
export type FirefoxSourcePhaseCallback = (phase: FirefoxSourcePhase) => void;
|
|
37
37
|
export type FirefoxSourceProgressCallback = (message: string) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Sweeps orphaned working directories left behind by interrupted downloads
|
|
40
|
+
* (`<engine>.tmp-*` from extraction, `<engine>.replacement-*` from --force
|
|
41
|
+
* replacement). Both hold multi-GB partial trees, are worthless after the
|
|
42
|
+
* owning process died, and nothing else ever reclaimed them. Deliberately
|
|
43
|
+
* does NOT touch `<engine>.backup-*`: a backup may hold the operator's
|
|
44
|
+
* previous engine after a failed forced replacement and is their recovery
|
|
45
|
+
* copy — callers should surface its existence instead.
|
|
46
|
+
*
|
|
47
|
+
* @returns names of the directories that were removed
|
|
48
|
+
*/
|
|
49
|
+
export declare function sweepOrphanedEngineWorkDirs(destDir: string): Promise<string[]>;
|
|
38
50
|
/**
|
|
39
51
|
* Downloads and extracts Firefox source.
|
|
40
52
|
* @param version - Firefox version to download
|
package/dist/src/core/firefox.js
CHANGED
|
@@ -33,6 +33,63 @@ export function getDownloadUrl(version, product = 'firefox') {
|
|
|
33
33
|
export function getTarballFilename(version, product = 'firefox') {
|
|
34
34
|
return resolveArchive(version, product).filename;
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Classifies an extraction failure as archive corruption (invalidate the
|
|
38
|
+
* cache and re-download) versus an environmental problem (keep the cached
|
|
39
|
+
* archive; the retry can reuse it). Environmental signatures are the
|
|
40
|
+
* conservative list — anything unrecognized is treated as corruption so a
|
|
41
|
+
* genuinely bad archive can never survive in the cache.
|
|
42
|
+
*/
|
|
43
|
+
function isLikelyArchiveCorruptionError(error) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
const code = error instanceof Error ? error.code : undefined;
|
|
46
|
+
if (code === 'ENOSPC' || code === 'EACCES' || code === 'EPERM' || code === 'EDQUOT') {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (/No space left on device|Permission denied|Disk quota exceeded/i.test(message)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
// Missing tar binary / preflight refusing an unsafe archive both carry
|
|
53
|
+
// ExtractionError text that names the condition; a missing tar is
|
|
54
|
+
// environmental, an unsafe-entry rejection indicts the archive.
|
|
55
|
+
if (/"tar" command was not found/i.test(message)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Sweeps orphaned working directories left behind by interrupted downloads
|
|
62
|
+
* (`<engine>.tmp-*` from extraction, `<engine>.replacement-*` from --force
|
|
63
|
+
* replacement). Both hold multi-GB partial trees, are worthless after the
|
|
64
|
+
* owning process died, and nothing else ever reclaimed them. Deliberately
|
|
65
|
+
* does NOT touch `<engine>.backup-*`: a backup may hold the operator's
|
|
66
|
+
* previous engine after a failed forced replacement and is their recovery
|
|
67
|
+
* copy — callers should surface its existence instead.
|
|
68
|
+
*
|
|
69
|
+
* @returns names of the directories that were removed
|
|
70
|
+
*/
|
|
71
|
+
export async function sweepOrphanedEngineWorkDirs(destDir) {
|
|
72
|
+
const { readdir } = await import('node:fs/promises');
|
|
73
|
+
const { basename, dirname } = await import('node:path');
|
|
74
|
+
const parent = dirname(destDir);
|
|
75
|
+
const enginePrefixTmp = `${basename(destDir)}.tmp-`;
|
|
76
|
+
const enginePrefixReplacement = `${basename(destDir)}.replacement-`;
|
|
77
|
+
let entries;
|
|
78
|
+
try {
|
|
79
|
+
entries = await readdir(parent);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const removed = [];
|
|
85
|
+
for (const name of entries) {
|
|
86
|
+
if (name.startsWith(enginePrefixTmp) || name.startsWith(enginePrefixReplacement)) {
|
|
87
|
+
await removeDir(join(parent, name));
|
|
88
|
+
removed.push(name);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return removed;
|
|
92
|
+
}
|
|
36
93
|
/**
|
|
37
94
|
* Downloads and extracts Firefox source.
|
|
38
95
|
* @param version - Firefox version to download
|
|
@@ -60,7 +117,13 @@ export async function downloadFirefoxSource(version, product, destDir, cacheDir,
|
|
|
60
117
|
}
|
|
61
118
|
catch (error) {
|
|
62
119
|
await removeDir(tempDir);
|
|
63
|
-
|
|
120
|
+
// Only throw away the ~600 MB cached archive when the failure indicts
|
|
121
|
+
// the ARCHIVE, not the environment. ENOSPC, a permission problem in
|
|
122
|
+
// destDir, or tar disappearing mid-run used to invalidate a perfectly
|
|
123
|
+
// good download and force a full re-fetch on retry.
|
|
124
|
+
if (isLikelyArchiveCorruptionError(error)) {
|
|
125
|
+
await invalidateArchiveCache(archive, cacheDir);
|
|
126
|
+
}
|
|
64
127
|
throw error;
|
|
65
128
|
}
|
|
66
129
|
// Firefox source extracts to a subdirectory (e.g., firefox-140.0/)
|
|
@@ -80,4 +143,3 @@ export async function downloadFirefoxSource(version, product, destDir, cacheDir,
|
|
|
80
143
|
await rename(tempDir, destDir);
|
|
81
144
|
}
|
|
82
145
|
}
|
|
83
|
-
//# sourceMappingURL=firefox.js.map
|
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
* per-file LOC budget.
|
|
5
5
|
*
|
|
6
6
|
* Every helper here degrades gracefully: if the locale jar.mn is missing or
|
|
7
|
-
* the FTL tree is non-standard, apply logs
|
|
8
|
-
* aborting the whole command.
|
|
9
|
-
*
|
|
7
|
+
* the FTL tree is non-standard, apply logs an ADVISORY `stepError`
|
|
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 should not block a working
|
|
11
|
+
* `.mjs`/`.css` from shipping (the blocking/advisory split exists because
|
|
12
|
+
* this contract used to be contradicted: any FTL step error rolled back
|
|
13
|
+
* the whole apply).
|
|
10
14
|
*/
|
|
11
15
|
import type { CustomComponentConfig, DryRunAction, StepError } from '../types/furnace.js';
|
|
12
16
|
import { type RollbackJournal } from './furnace-rollback.js';
|
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
* per-file LOC budget.
|
|
6
6
|
*
|
|
7
7
|
* Every helper here degrades gracefully: if the locale jar.mn is missing or
|
|
8
|
-
* the FTL tree is non-standard, apply logs
|
|
9
|
-
* aborting the whole command.
|
|
10
|
-
*
|
|
8
|
+
* the FTL tree is non-standard, apply logs an ADVISORY `stepError`
|
|
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 should not block a working
|
|
12
|
+
* `.mjs`/`.css` from shipping (the blocking/advisory split exists because
|
|
13
|
+
* this contract used to be contradicted: any FTL step error rolled back
|
|
14
|
+
* the whole apply).
|
|
11
15
|
*/
|
|
12
16
|
import { join, relative } from 'node:path';
|
|
13
17
|
import { toError } from '../utils/errors.js';
|
|
@@ -86,7 +90,7 @@ export async function applySharedFtlPrune(engineDir, name, ftlDir, config, affec
|
|
|
86
90
|
affectedPaths.push(prunedPath);
|
|
87
91
|
}
|
|
88
92
|
catch (error) {
|
|
89
|
-
stepErrors.push({ step: 'locale jar.mn prune', error: toError(error).message });
|
|
93
|
+
stepErrors.push({ step: 'locale jar.mn prune', error: toError(error).message, advisory: true });
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
/**
|
|
@@ -139,6 +143,7 @@ export async function applyCustomFtlFile(engineDir, name, componentDir, ftlDir,
|
|
|
139
143
|
stepErrors.push({
|
|
140
144
|
step: 'locale jar.mn registration',
|
|
141
145
|
error: `Locale jar.mn not found at ${localeJarRel}; component "${name}" ships without a chrome URI for ${ftlFile}. Add the file manually or set furnace.json "ftlBasePath" to a tree that owns a jar.mn.`,
|
|
146
|
+
advisory: true,
|
|
142
147
|
});
|
|
143
148
|
return;
|
|
144
149
|
}
|
|
@@ -155,6 +160,7 @@ export async function applyCustomFtlFile(engineDir, name, componentDir, ftlDir,
|
|
|
155
160
|
stepErrors.push({
|
|
156
161
|
step: 'locale jar.mn registration',
|
|
157
162
|
error: toError(error).message,
|
|
163
|
+
advisory: true,
|
|
158
164
|
});
|
|
159
165
|
}
|
|
160
166
|
}
|
|
@@ -199,4 +205,3 @@ export async function removeCustomFtlJarMnEntry(engineDir, fileName, ftlDir, con
|
|
|
199
205
|
}
|
|
200
206
|
await removeLocaleFtlJarMnEntry(engineDir, localeJarRel, tagName, chromeSubPath);
|
|
201
207
|
}
|
|
202
|
-
//# sourceMappingURL=furnace-apply-ftl.js.map
|
|
@@ -421,4 +421,3 @@ export async function applyOverrideComponent(engineDir, name, componentDir, conf
|
|
|
421
421
|
return { affectedPaths };
|
|
422
422
|
}
|
|
423
423
|
export { diffDeletedFiles, extractComponentChecksums, prefixChecksums, } from './furnace-checksum-utils.js';
|
|
424
|
-
//# sourceMappingURL=furnace-apply-helpers.js.map
|
|
@@ -45,7 +45,8 @@ export function logApplyResult(result, isDryRun) {
|
|
|
45
45
|
for (const applied of result.applied) {
|
|
46
46
|
if (applied.stepErrors && applied.stepErrors.length > 0) {
|
|
47
47
|
for (const stepErr of applied.stepErrors) {
|
|
48
|
-
|
|
48
|
+
const advisoryPrefix = stepErr.advisory === true ? '(advisory) ' : '';
|
|
49
|
+
warn(`${applied.name}: ${advisoryPrefix}[${stepErr.step}] ${stepErr.error}`);
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -54,4 +55,3 @@ export function logApplyResult(result, isDryRun) {
|
|
|
54
55
|
error(`${err.name} — ${err.error}`);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
|
-
//# sourceMappingURL=furnace-apply-output.js.map
|
|
@@ -31,4 +31,10 @@ export declare function applyAllComponents(root: string, dryRun?: boolean, optio
|
|
|
31
31
|
}): Promise<ApplyResult & {
|
|
32
32
|
actions?: DryRunAction[];
|
|
33
33
|
rollbackJournal?: RollbackJournal;
|
|
34
|
-
}>;
|
|
34
|
+
}>; /**
|
|
35
|
+
* Shared context threaded through the batch apply loops. Bundles the state
|
|
36
|
+
* both loops mutate (result/actions/checksums) with the immutable run
|
|
37
|
+
* parameters — the previous 12–13 positional parameters let call sites
|
|
38
|
+
* drift (the named-apply state-wipe bug) and made every new parameter a
|
|
39
|
+
* two-signature change.
|
|
40
|
+
*/
|