@hominis/fireforge 0.9.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 +7 -0
- package/LICENSE.md +294 -0
- package/README.md +435 -0
- package/dist/bin/fireforge.d.ts +10 -0
- package/dist/bin/fireforge.js +29 -0
- package/dist/src/cli.d.ts +33 -0
- package/dist/src/cli.js +180 -0
- package/dist/src/commands/bootstrap.d.ts +9 -0
- package/dist/src/commands/bootstrap.js +73 -0
- package/dist/src/commands/build.d.ts +11 -0
- package/dist/src/commands/build.js +102 -0
- package/dist/src/commands/config.d.ts +13 -0
- package/dist/src/commands/config.js +135 -0
- package/dist/src/commands/discard.d.ts +12 -0
- package/dist/src/commands/discard.js +84 -0
- package/dist/src/commands/doctor.d.ts +18 -0
- package/dist/src/commands/doctor.js +356 -0
- package/dist/src/commands/download.d.ts +11 -0
- package/dist/src/commands/download.js +127 -0
- package/dist/src/commands/export-all.d.ts +11 -0
- package/dist/src/commands/export-all.js +122 -0
- package/dist/src/commands/export-shared.d.ts +48 -0
- package/dist/src/commands/export-shared.js +208 -0
- package/dist/src/commands/export.d.ts +13 -0
- package/dist/src/commands/export.js +178 -0
- package/dist/src/commands/furnace/apply.d.ts +7 -0
- package/dist/src/commands/furnace/apply.js +80 -0
- package/dist/src/commands/furnace/create.d.ts +8 -0
- package/dist/src/commands/furnace/create.js +377 -0
- package/dist/src/commands/furnace/deploy.d.ts +8 -0
- package/dist/src/commands/furnace/deploy.js +338 -0
- package/dist/src/commands/furnace/diff.d.ts +7 -0
- package/dist/src/commands/furnace/diff.js +119 -0
- package/dist/src/commands/furnace/index.d.ts +16 -0
- package/dist/src/commands/furnace/index.js +121 -0
- package/dist/src/commands/furnace/list.d.ts +5 -0
- package/dist/src/commands/furnace/list.js +65 -0
- package/dist/src/commands/furnace/override.d.ts +8 -0
- package/dist/src/commands/furnace/override.js +188 -0
- package/dist/src/commands/furnace/preview.d.ts +7 -0
- package/dist/src/commands/furnace/preview.js +96 -0
- package/dist/src/commands/furnace/remove.d.ts +8 -0
- package/dist/src/commands/furnace/remove.js +159 -0
- package/dist/src/commands/furnace/scan.d.ts +5 -0
- package/dist/src/commands/furnace/scan.js +112 -0
- package/dist/src/commands/furnace/status.d.ts +7 -0
- package/dist/src/commands/furnace/status.js +137 -0
- package/dist/src/commands/furnace/validate.d.ts +6 -0
- package/dist/src/commands/furnace/validate.js +91 -0
- package/dist/src/commands/furnace/validation-output.d.ts +7 -0
- package/dist/src/commands/furnace/validation-output.js +22 -0
- package/dist/src/commands/import.d.ts +11 -0
- package/dist/src/commands/import.js +241 -0
- package/dist/src/commands/lint.d.ts +10 -0
- package/dist/src/commands/lint.js +118 -0
- package/dist/src/commands/package.d.ts +11 -0
- package/dist/src/commands/package.js +80 -0
- package/dist/src/commands/re-export.d.ts +12 -0
- package/dist/src/commands/re-export.js +242 -0
- package/dist/src/commands/rebase/abort.d.ts +7 -0
- package/dist/src/commands/rebase/abort.js +49 -0
- package/dist/src/commands/rebase/confirm.d.ts +18 -0
- package/dist/src/commands/rebase/confirm.js +33 -0
- package/dist/src/commands/rebase/continue.d.ts +7 -0
- package/dist/src/commands/rebase/continue.js +81 -0
- package/dist/src/commands/rebase/index.d.ts +22 -0
- package/dist/src/commands/rebase/index.js +127 -0
- package/dist/src/commands/rebase/patch-loop.d.ts +9 -0
- package/dist/src/commands/rebase/patch-loop.js +135 -0
- package/dist/src/commands/rebase/summary.d.ts +12 -0
- package/dist/src/commands/rebase/summary.js +43 -0
- package/dist/src/commands/rebase.d.ts +4 -0
- package/dist/src/commands/rebase.js +6 -0
- package/dist/src/commands/register.d.ts +13 -0
- package/dist/src/commands/register.js +67 -0
- package/dist/src/commands/reset.d.ts +11 -0
- package/dist/src/commands/reset.js +83 -0
- package/dist/src/commands/resolve.d.ts +9 -0
- package/dist/src/commands/resolve.js +124 -0
- package/dist/src/commands/run.d.ts +9 -0
- package/dist/src/commands/run.js +91 -0
- package/dist/src/commands/setup-support.d.ts +23 -0
- package/dist/src/commands/setup-support.js +310 -0
- package/dist/src/commands/setup.d.ts +11 -0
- package/dist/src/commands/setup.js +94 -0
- package/dist/src/commands/status.d.ts +11 -0
- package/dist/src/commands/status.js +268 -0
- package/dist/src/commands/test.d.ts +12 -0
- package/dist/src/commands/test.js +182 -0
- package/dist/src/commands/token-coverage.d.ts +5 -0
- package/dist/src/commands/token-coverage.js +57 -0
- package/dist/src/commands/token.d.ts +14 -0
- package/dist/src/commands/token.js +121 -0
- package/dist/src/commands/watch.d.ts +9 -0
- package/dist/src/commands/watch.js +112 -0
- package/dist/src/commands/wire.d.ts +13 -0
- package/dist/src/commands/wire.js +149 -0
- package/dist/src/core/ast-utils.d.ts +47 -0
- package/dist/src/core/ast-utils.js +57 -0
- package/dist/src/core/brand-validation.d.ts +7 -0
- package/dist/src/core/brand-validation.js +15 -0
- package/dist/src/core/branding.d.ts +49 -0
- package/dist/src/core/branding.js +229 -0
- package/dist/src/core/browser-wire.d.ts +40 -0
- package/dist/src/core/browser-wire.js +66 -0
- package/dist/src/core/build-prepare.d.ts +25 -0
- package/dist/src/core/build-prepare.js +93 -0
- package/dist/src/core/config-mutate.d.ts +15 -0
- package/dist/src/core/config-mutate.js +51 -0
- package/dist/src/core/config-paths.d.ts +28 -0
- package/dist/src/core/config-paths.js +65 -0
- package/dist/src/core/config-state.d.ts +28 -0
- package/dist/src/core/config-state.js +152 -0
- package/dist/src/core/config-validate.d.ts +11 -0
- package/dist/src/core/config-validate.js +141 -0
- package/dist/src/core/config.d.ts +39 -0
- package/dist/src/core/config.js +70 -0
- package/dist/src/core/file-lock.d.ts +11 -0
- package/dist/src/core/file-lock.js +80 -0
- package/dist/src/core/firefox-archive.d.ts +40 -0
- package/dist/src/core/firefox-archive.js +63 -0
- package/dist/src/core/firefox-cache.d.ts +23 -0
- package/dist/src/core/firefox-cache.js +134 -0
- package/dist/src/core/firefox-download.d.ts +21 -0
- package/dist/src/core/firefox-download.js +129 -0
- package/dist/src/core/firefox-extract.d.ts +21 -0
- package/dist/src/core/firefox-extract.js +53 -0
- package/dist/src/core/firefox.d.ts +34 -0
- package/dist/src/core/firefox.js +78 -0
- package/dist/src/core/furnace-apply-helpers.d.ts +21 -0
- package/dist/src/core/furnace-apply-helpers.js +244 -0
- package/dist/src/core/furnace-apply.d.ts +16 -0
- package/dist/src/core/furnace-apply.js +147 -0
- package/dist/src/core/furnace-config.d.ts +94 -0
- package/dist/src/core/furnace-config.js +372 -0
- package/dist/src/core/furnace-constants.d.ts +4 -0
- package/dist/src/core/furnace-constants.js +6 -0
- package/dist/src/core/furnace-registration-ast.d.ts +24 -0
- package/dist/src/core/furnace-registration-ast.js +218 -0
- package/dist/src/core/furnace-registration-remove.d.ts +14 -0
- package/dist/src/core/furnace-registration-remove.js +89 -0
- package/dist/src/core/furnace-registration-validate.d.ts +20 -0
- package/dist/src/core/furnace-registration-validate.js +40 -0
- package/dist/src/core/furnace-registration.d.ts +29 -0
- package/dist/src/core/furnace-registration.js +96 -0
- package/dist/src/core/furnace-rollback.d.ts +20 -0
- package/dist/src/core/furnace-rollback.js +66 -0
- package/dist/src/core/furnace-scanner.d.ts +40 -0
- package/dist/src/core/furnace-scanner.js +143 -0
- package/dist/src/core/furnace-stories.d.ts +37 -0
- package/dist/src/core/furnace-stories.js +185 -0
- package/dist/src/core/furnace-validate-accessibility.d.ts +6 -0
- package/dist/src/core/furnace-validate-accessibility.js +32 -0
- package/dist/src/core/furnace-validate-checks.d.ts +4 -0
- package/dist/src/core/furnace-validate-checks.js +7 -0
- package/dist/src/core/furnace-validate-compatibility.d.ts +6 -0
- package/dist/src/core/furnace-validate-compatibility.js +57 -0
- package/dist/src/core/furnace-validate-helpers.d.ts +28 -0
- package/dist/src/core/furnace-validate-helpers.js +129 -0
- package/dist/src/core/furnace-validate-registration.d.ts +37 -0
- package/dist/src/core/furnace-validate-registration.js +220 -0
- package/dist/src/core/furnace-validate-structure.d.ts +6 -0
- package/dist/src/core/furnace-validate-structure.js +66 -0
- package/dist/src/core/furnace-validate.d.ts +16 -0
- package/dist/src/core/furnace-validate.js +103 -0
- package/dist/src/core/git-base.d.ts +47 -0
- package/dist/src/core/git-base.js +50 -0
- package/dist/src/core/git-diff.d.ts +63 -0
- package/dist/src/core/git-diff.js +246 -0
- package/dist/src/core/git-file-ops.d.ts +65 -0
- package/dist/src/core/git-file-ops.js +141 -0
- package/dist/src/core/git-status.d.ts +65 -0
- package/dist/src/core/git-status.js +163 -0
- package/dist/src/core/git.d.ts +113 -0
- package/dist/src/core/git.js +363 -0
- package/dist/src/core/license-headers.d.ts +36 -0
- package/dist/src/core/license-headers.js +83 -0
- package/dist/src/core/mach-build-artifacts.d.ts +29 -0
- package/dist/src/core/mach-build-artifacts.js +117 -0
- package/dist/src/core/mach-mozconfig.d.ts +17 -0
- package/dist/src/core/mach-mozconfig.js +50 -0
- package/dist/src/core/mach-python.d.ts +16 -0
- package/dist/src/core/mach-python.js +126 -0
- package/dist/src/core/mach.d.ts +106 -0
- package/dist/src/core/mach.js +166 -0
- package/dist/src/core/manifest-helpers.d.ts +25 -0
- package/dist/src/core/manifest-helpers.js +96 -0
- package/dist/src/core/manifest-register.d.ts +30 -0
- package/dist/src/core/manifest-register.js +65 -0
- package/dist/src/core/manifest-rules.d.ts +39 -0
- package/dist/src/core/manifest-rules.js +151 -0
- package/dist/src/core/manifest-tokenizers.d.ts +34 -0
- package/dist/src/core/manifest-tokenizers.js +84 -0
- package/dist/src/core/parser-fallback.d.ts +36 -0
- package/dist/src/core/parser-fallback.js +43 -0
- package/dist/src/core/patch-apply-fuzz.d.ts +29 -0
- package/dist/src/core/patch-apply-fuzz.js +70 -0
- package/dist/src/core/patch-apply.d.ts +46 -0
- package/dist/src/core/patch-apply.js +235 -0
- package/dist/src/core/patch-export.d.ts +99 -0
- package/dist/src/core/patch-export.js +314 -0
- package/dist/src/core/patch-files.d.ts +11 -0
- package/dist/src/core/patch-files.js +51 -0
- package/dist/src/core/patch-lint.d.ts +72 -0
- package/dist/src/core/patch-lint.js +403 -0
- package/dist/src/core/patch-lock.d.ts +8 -0
- package/dist/src/core/patch-lock.js +29 -0
- package/dist/src/core/patch-manifest-consistency.d.ts +24 -0
- package/dist/src/core/patch-manifest-consistency.js +135 -0
- package/dist/src/core/patch-manifest-io.d.ts +36 -0
- package/dist/src/core/patch-manifest-io.js +77 -0
- package/dist/src/core/patch-manifest-query.d.ts +48 -0
- package/dist/src/core/patch-manifest-query.js +124 -0
- package/dist/src/core/patch-manifest-validate.d.ts +22 -0
- package/dist/src/core/patch-manifest-validate.js +72 -0
- package/dist/src/core/patch-manifest.d.ts +11 -0
- package/dist/src/core/patch-manifest.js +12 -0
- package/dist/src/core/patch-parse.d.ts +43 -0
- package/dist/src/core/patch-parse.js +143 -0
- package/dist/src/core/patch-transform.d.ts +21 -0
- package/dist/src/core/patch-transform.js +138 -0
- package/dist/src/core/rebase-session.d.ts +47 -0
- package/dist/src/core/rebase-session.js +65 -0
- package/dist/src/core/register-browser-content.d.ts +11 -0
- package/dist/src/core/register-browser-content.js +116 -0
- package/dist/src/core/register-module.d.ts +11 -0
- package/dist/src/core/register-module.js +76 -0
- package/dist/src/core/register-shared-css.d.ts +11 -0
- package/dist/src/core/register-shared-css.js +117 -0
- package/dist/src/core/register-test-manifest.d.ts +18 -0
- package/dist/src/core/register-test-manifest.js +99 -0
- package/dist/src/core/state-file.d.ts +4 -0
- package/dist/src/core/state-file.js +25 -0
- package/dist/src/core/token-coverage.d.ts +12 -0
- package/dist/src/core/token-coverage.js +74 -0
- package/dist/src/core/token-manager.d.ts +55 -0
- package/dist/src/core/token-manager.js +387 -0
- package/dist/src/core/wire-destroy.d.ts +21 -0
- package/dist/src/core/wire-destroy.js +103 -0
- package/dist/src/core/wire-dom-fragment.d.ts +23 -0
- package/dist/src/core/wire-dom-fragment.js +129 -0
- package/dist/src/core/wire-init.d.ts +23 -0
- package/dist/src/core/wire-init.js +201 -0
- package/dist/src/core/wire-subscript.d.ts +20 -0
- package/dist/src/core/wire-subscript.js +134 -0
- package/dist/src/core/wire-targets.d.ts +7 -0
- package/dist/src/core/wire-targets.js +9 -0
- package/dist/src/core/wire-utils.d.ts +88 -0
- package/dist/src/core/wire-utils.js +279 -0
- package/dist/src/errors/base.d.ts +60 -0
- package/dist/src/errors/base.js +87 -0
- package/dist/src/errors/build.d.ts +52 -0
- package/dist/src/errors/build.js +114 -0
- package/dist/src/errors/codes.d.ts +29 -0
- package/dist/src/errors/codes.js +30 -0
- package/dist/src/errors/config.d.ts +31 -0
- package/dist/src/errors/config.js +61 -0
- package/dist/src/errors/download.d.ts +42 -0
- package/dist/src/errors/download.js +95 -0
- package/dist/src/errors/furnace.d.ts +10 -0
- package/dist/src/errors/furnace.js +22 -0
- package/dist/src/errors/git.d.ts +41 -0
- package/dist/src/errors/git.js +99 -0
- package/dist/src/errors/patch.d.ts +10 -0
- package/dist/src/errors/patch.js +26 -0
- package/dist/src/errors/rebase.d.ts +20 -0
- package/dist/src/errors/rebase.js +30 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.js +21 -0
- package/dist/src/types/cli.d.ts +14 -0
- package/dist/src/types/cli.js +2 -0
- package/dist/src/types/commands/index.d.ts +6 -0
- package/dist/src/types/commands/index.js +6 -0
- package/dist/src/types/commands/options.d.ts +239 -0
- package/dist/src/types/commands/options.js +6 -0
- package/dist/src/types/commands/patches.d.ts +89 -0
- package/dist/src/types/commands/patches.js +6 -0
- package/dist/src/types/commands/project.d.ts +71 -0
- package/dist/src/types/commands/project.js +6 -0
- package/dist/src/types/config.d.ts +101 -0
- package/dist/src/types/config.js +2 -0
- package/dist/src/types/furnace.d.ts +158 -0
- package/dist/src/types/furnace.js +2 -0
- package/dist/src/types/index.d.ts +6 -0
- package/dist/src/types/index.js +6 -0
- package/dist/src/utils/errors.d.ts +2 -0
- package/dist/src/utils/errors.js +15 -0
- package/dist/src/utils/fs.d.ts +72 -0
- package/dist/src/utils/fs.js +179 -0
- package/dist/src/utils/logger.d.ts +58 -0
- package/dist/src/utils/logger.js +120 -0
- package/dist/src/utils/options.d.ts +8 -0
- package/dist/src/utils/options.js +16 -0
- package/dist/src/utils/package-root.d.ts +10 -0
- package/dist/src/utils/package-root.js +53 -0
- package/dist/src/utils/parse.d.ts +110 -0
- package/dist/src/utils/parse.js +200 -0
- package/dist/src/utils/paths.d.ts +10 -0
- package/dist/src/utils/paths.js +43 -0
- package/dist/src/utils/platform.d.ts +38 -0
- package/dist/src/utils/platform.js +56 -0
- package/dist/src/utils/process.d.ts +80 -0
- package/dist/src/utils/process.js +188 -0
- package/dist/src/utils/regex.d.ts +24 -0
- package/dist/src/utils/regex.js +40 -0
- package/dist/src/utils/validation.d.ts +133 -0
- package/dist/src/utils/validation.js +250 -0
- package/package.json +106 -0
- package/templates/configs/common.mozconfig +24 -0
- package/templates/configs/darwin.mozconfig +10 -0
- package/templates/configs/linux.mozconfig +12 -0
- package/templates/configs/win32.mozconfig +14 -0
- package/templates/licenses/0BSD.md +14 -0
- package/templates/licenses/EUPL-1.2.md +294 -0
- package/templates/licenses/GPL-2.0-or-later.md +339 -0
- package/templates/licenses/MPL-2.0.md +383 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Cache validation, invalidation, and download-to-cache logic.
|
|
4
|
+
*/
|
|
5
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
6
|
+
import { createReadStream } from 'node:fs';
|
|
7
|
+
import { rename } from 'node:fs/promises';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { pipeline } from 'node:stream/promises';
|
|
10
|
+
import { toError } from '../utils/errors.js';
|
|
11
|
+
import { pathExists, readJson, removeFile, writeJson } from '../utils/fs.js';
|
|
12
|
+
import { verbose } from '../utils/logger.js';
|
|
13
|
+
import { validateArchiveMetadata } from './firefox-archive.js';
|
|
14
|
+
import { downloadFile } from './firefox-download.js';
|
|
15
|
+
/**
|
|
16
|
+
* Computes the SHA-256 hex digest of a file.
|
|
17
|
+
* @param filePath - Path to the file
|
|
18
|
+
*/
|
|
19
|
+
export async function sha256File(filePath) {
|
|
20
|
+
const hash = createHash('sha256');
|
|
21
|
+
const stream = createReadStream(filePath);
|
|
22
|
+
await pipeline(stream, hash);
|
|
23
|
+
return hash.digest('hex');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Ensures a valid cached archive exists, downloading it if needed.
|
|
27
|
+
* @param archive - Resolved archive descriptor
|
|
28
|
+
* @param cacheDir - Cache directory
|
|
29
|
+
* @param onProgress - Optional progress callback
|
|
30
|
+
*/
|
|
31
|
+
export async function ensureCachedArchive(archive, cacheDir, onProgress) {
|
|
32
|
+
if (await validateCachedArchive(archive, cacheDir)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await invalidateArchiveCache(archive, cacheDir);
|
|
36
|
+
await downloadToCache(archive, cacheDir, onProgress);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Validates a cached archive using sidecar metadata and SHA-256 checksum.
|
|
40
|
+
* @param archive - Resolved archive descriptor
|
|
41
|
+
* @param cacheDir - Cache directory
|
|
42
|
+
* @returns True if the cache entry is valid
|
|
43
|
+
*/
|
|
44
|
+
async function validateCachedArchive(archive, cacheDir) {
|
|
45
|
+
const tarballPath = join(cacheDir, archive.filename);
|
|
46
|
+
const metadataPath = join(cacheDir, archive.metadataFilename);
|
|
47
|
+
if (!(await pathExists(tarballPath)) || !(await pathExists(metadataPath))) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const metadata = validateArchiveMetadata(await readJson(metadataPath));
|
|
52
|
+
if (metadata.product !== archive.product ||
|
|
53
|
+
metadata.archiveVersion !== archive.archiveVersion ||
|
|
54
|
+
metadata.url !== archive.url) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (metadata.contentLength !== undefined) {
|
|
58
|
+
const { stat } = await import('node:fs/promises');
|
|
59
|
+
const archiveStats = await stat(tarballPath);
|
|
60
|
+
if (archiveStats.size !== metadata.contentLength) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (metadata.sha256) {
|
|
65
|
+
const actualHash = await sha256File(tarballPath);
|
|
66
|
+
if (actualHash !== metadata.sha256) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
verbose(`Cache validation failed for ${tarballPath}; treating cache entry as invalid: ${toError(error).message}`);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Downloads an archive to cache using an atomic temp file and sidecar metadata.
|
|
79
|
+
* @param archive - Resolved archive descriptor
|
|
80
|
+
* @param cacheDir - Cache directory
|
|
81
|
+
* @param onProgress - Optional progress callback
|
|
82
|
+
*/
|
|
83
|
+
async function downloadToCache(archive, cacheDir, onProgress) {
|
|
84
|
+
const tarballPath = join(cacheDir, archive.filename);
|
|
85
|
+
// Use a unique .part path so concurrent downloads for the same archive
|
|
86
|
+
// do not clobber each other's partial files.
|
|
87
|
+
const partPath = `${tarballPath}.part-${randomUUID()}`;
|
|
88
|
+
const metadataPath = join(cacheDir, archive.metadataFilename);
|
|
89
|
+
try {
|
|
90
|
+
const contentLength = await downloadFile(archive.url, partPath, onProgress);
|
|
91
|
+
await rename(partPath, tarballPath);
|
|
92
|
+
const sha256 = await sha256File(tarballPath);
|
|
93
|
+
await writeJson(metadataPath, {
|
|
94
|
+
requestedVersion: archive.requestedVersion,
|
|
95
|
+
product: archive.product,
|
|
96
|
+
archiveVersion: archive.archiveVersion,
|
|
97
|
+
url: archive.url,
|
|
98
|
+
...(contentLength !== undefined ? { contentLength } : {}),
|
|
99
|
+
sha256,
|
|
100
|
+
downloadedAt: new Date().toISOString(),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
await removeFile(partPath);
|
|
105
|
+
await removeFile(tarballPath);
|
|
106
|
+
await removeFile(metadataPath);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Removes cached tarball, metadata, and partial download files for an archive.
|
|
112
|
+
* @param archive - Resolved archive descriptor
|
|
113
|
+
* @param cacheDir - Cache directory
|
|
114
|
+
*/
|
|
115
|
+
export async function invalidateArchiveCache(archive, cacheDir) {
|
|
116
|
+
const tarballPath = join(cacheDir, archive.filename);
|
|
117
|
+
const metadataPath = join(cacheDir, archive.metadataFilename);
|
|
118
|
+
// Clean up any partial download files (may have unique suffixes from
|
|
119
|
+
// concurrent download attempts).
|
|
120
|
+
const partPrefix = `${archive.filename}.part`;
|
|
121
|
+
try {
|
|
122
|
+
const { readdir } = await import('node:fs/promises');
|
|
123
|
+
const entries = await readdir(cacheDir);
|
|
124
|
+
await Promise.all(entries
|
|
125
|
+
.filter((name) => name.startsWith(partPrefix))
|
|
126
|
+
.map((name) => removeFile(join(cacheDir, name))));
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Cache dir may not exist yet — that's fine.
|
|
130
|
+
}
|
|
131
|
+
await removeFile(tarballPath);
|
|
132
|
+
await removeFile(metadataPath);
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=firefox-cache.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Download with retry, stall detection, and progress tracking.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Progress callback for download operations.
|
|
6
|
+
*/
|
|
7
|
+
export type ProgressCallback = (downloaded: number, total: number) => void;
|
|
8
|
+
/**
|
|
9
|
+
* Fetches a URL with timeout and bounded retry for transient failures.
|
|
10
|
+
*
|
|
11
|
+
* Non-retryable errors (e.g. 404) are thrown immediately.
|
|
12
|
+
*/
|
|
13
|
+
export declare function fetchWithRetry(url: string): Promise<Response>;
|
|
14
|
+
/**
|
|
15
|
+
* Downloads a file from a URL with progress tracking, timeout, and retry.
|
|
16
|
+
* @param url - URL to download
|
|
17
|
+
* @param destPath - Destination file path
|
|
18
|
+
* @param onProgress - Optional progress callback
|
|
19
|
+
* @returns The content-length if available, otherwise undefined
|
|
20
|
+
*/
|
|
21
|
+
export declare function downloadFile(url: string, destPath: string, onProgress?: ProgressCallback): Promise<number | undefined>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Download with retry, stall detection, and progress tracking.
|
|
4
|
+
*/
|
|
5
|
+
import { createWriteStream } from 'node:fs';
|
|
6
|
+
import { basename } from 'node:path';
|
|
7
|
+
import { Readable, Transform } from 'node:stream';
|
|
8
|
+
import { pipeline } from 'node:stream/promises';
|
|
9
|
+
import { DownloadError, VersionNotFoundError } from '../errors/download.js';
|
|
10
|
+
/** Default request timeout in milliseconds (60 seconds). */
|
|
11
|
+
const REQUEST_TIMEOUT_MS = 60_000;
|
|
12
|
+
/** Maximum number of download attempts. */
|
|
13
|
+
const MAX_ATTEMPTS = 3;
|
|
14
|
+
/** HTTP status codes that are retryable. */
|
|
15
|
+
const RETRYABLE_STATUS_CODES = new Set([429, 500, 502, 503, 504]);
|
|
16
|
+
/** Base delay between retries in milliseconds. */
|
|
17
|
+
const RETRY_BASE_DELAY_MS = 1_000;
|
|
18
|
+
/** Stall detection timeout — abort if no data received for this duration (30 seconds). */
|
|
19
|
+
const DOWNLOAD_STALL_TIMEOUT_MS = 30_000;
|
|
20
|
+
/**
|
|
21
|
+
* Fetches a URL with timeout and bounded retry for transient failures.
|
|
22
|
+
*
|
|
23
|
+
* Non-retryable errors (e.g. 404) are thrown immediately.
|
|
24
|
+
*/
|
|
25
|
+
export async function fetchWithRetry(url) {
|
|
26
|
+
let lastError;
|
|
27
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
const timer = setTimeout(() => {
|
|
30
|
+
controller.abort();
|
|
31
|
+
}, REQUEST_TIMEOUT_MS);
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
34
|
+
if (response.status === 404) {
|
|
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 {
|
|
44
|
+
return response;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error instanceof VersionNotFoundError || error instanceof DownloadError) {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
// Network / timeout errors are retryable
|
|
52
|
+
lastError = error;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
}
|
|
57
|
+
if (attempt < MAX_ATTEMPTS - 1) {
|
|
58
|
+
await new Promise((resolve) => {
|
|
59
|
+
setTimeout(resolve, RETRY_BASE_DELAY_MS * (attempt + 1) ** 2);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (lastError instanceof DownloadError) {
|
|
64
|
+
throw lastError;
|
|
65
|
+
}
|
|
66
|
+
throw new DownloadError(lastError instanceof Error ? lastError.message : 'Download failed after retries', url, lastError instanceof Error ? lastError : undefined);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Creates a Transform stream that aborts if no data is received within the stall timeout.
|
|
70
|
+
* The timer resets on each chunk. If it fires, the stream is destroyed with a DownloadError.
|
|
71
|
+
* @param url - URL being downloaded (for error messages)
|
|
72
|
+
* @param timeoutMs - Stall timeout in milliseconds
|
|
73
|
+
*/
|
|
74
|
+
function createStallDetector(url, timeoutMs = DOWNLOAD_STALL_TIMEOUT_MS) {
|
|
75
|
+
let timer;
|
|
76
|
+
const detector = new Transform({
|
|
77
|
+
transform(chunk, _encoding, callback) {
|
|
78
|
+
if (timer !== undefined)
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
timer = setTimeout(() => {
|
|
81
|
+
detector.destroy(new DownloadError(`Download stalled: no data received for ${Math.round(timeoutMs / 1000)} seconds`, url));
|
|
82
|
+
}, timeoutMs);
|
|
83
|
+
callback(null, chunk);
|
|
84
|
+
},
|
|
85
|
+
flush(callback) {
|
|
86
|
+
if (timer !== undefined)
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
callback();
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
// Start the initial timer (covers the case where the first chunk never arrives).
|
|
92
|
+
timer = setTimeout(() => {
|
|
93
|
+
detector.destroy(new DownloadError(`Download stalled: no data received for ${Math.round(timeoutMs / 1000)} seconds`, url));
|
|
94
|
+
}, timeoutMs);
|
|
95
|
+
return detector;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Downloads a file from a URL with progress tracking, timeout, and retry.
|
|
99
|
+
* @param url - URL to download
|
|
100
|
+
* @param destPath - Destination file path
|
|
101
|
+
* @param onProgress - Optional progress callback
|
|
102
|
+
* @returns The content-length if available, otherwise undefined
|
|
103
|
+
*/
|
|
104
|
+
export async function downloadFile(url, destPath, onProgress) {
|
|
105
|
+
const response = await fetchWithRetry(url);
|
|
106
|
+
if (!response.body) {
|
|
107
|
+
throw new DownloadError('No response body received', url);
|
|
108
|
+
}
|
|
109
|
+
const totalSize = parseInt(response.headers.get('content-length') ?? '0', 10);
|
|
110
|
+
let downloadedSize = 0;
|
|
111
|
+
const fileStream = createWriteStream(destPath);
|
|
112
|
+
const nodeStream = Readable.fromWeb(response.body);
|
|
113
|
+
const stallDetector = createStallDetector(url);
|
|
114
|
+
if (onProgress && totalSize > 0) {
|
|
115
|
+
const progress = new Transform({
|
|
116
|
+
transform(chunk, _encoding, callback) {
|
|
117
|
+
downloadedSize += chunk.length;
|
|
118
|
+
onProgress(downloadedSize, totalSize);
|
|
119
|
+
callback(null, chunk);
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
await pipeline(nodeStream, stallDetector, progress, fileStream);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
await pipeline(nodeStream, stallDetector, fileStream);
|
|
126
|
+
}
|
|
127
|
+
return totalSize > 0 ? totalSize : undefined;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=firefox-download.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firefox source extraction and installed version detection.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Extracts a tar.xz archive.
|
|
6
|
+
* @param archivePath - Path to the archive
|
|
7
|
+
* @param destDir - Destination directory
|
|
8
|
+
*/
|
|
9
|
+
export declare function extractTarXz(archivePath: string, destDir: string): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Gets the Firefox version from an existing source directory.
|
|
12
|
+
* @param engineDir - Path to the engine directory
|
|
13
|
+
* @returns Firefox version string
|
|
14
|
+
*/
|
|
15
|
+
export declare function getFirefoxVersion(engineDir: string): Promise<string | undefined>;
|
|
16
|
+
/**
|
|
17
|
+
* Formats bytes into a human-readable string.
|
|
18
|
+
* @param bytes - Number of bytes
|
|
19
|
+
* @returns Formatted string (e.g., "1.5 GB")
|
|
20
|
+
*/
|
|
21
|
+
export declare function formatBytes(bytes: number): string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Firefox source extraction and installed version detection.
|
|
4
|
+
*/
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { ExtractionError } from '../errors/download.js';
|
|
7
|
+
import { ensureDir, pathExists } from '../utils/fs.js';
|
|
8
|
+
import { exec, executableExists } from '../utils/process.js';
|
|
9
|
+
/**
|
|
10
|
+
* Extracts a tar.xz archive.
|
|
11
|
+
* @param archivePath - Path to the archive
|
|
12
|
+
* @param destDir - Destination directory
|
|
13
|
+
*/
|
|
14
|
+
export async function extractTarXz(archivePath, destDir) {
|
|
15
|
+
if (!(await executableExists('tar'))) {
|
|
16
|
+
throw new ExtractionError(archivePath, new Error('The "tar" command was not found. Please install tar (or ensure it is on your PATH) and try again.'));
|
|
17
|
+
}
|
|
18
|
+
await ensureDir(destDir);
|
|
19
|
+
const result = await exec('tar', ['-xf', archivePath, '-C', destDir]);
|
|
20
|
+
if (result.exitCode !== 0) {
|
|
21
|
+
throw new ExtractionError(archivePath, new Error(`tar exited with code ${result.exitCode}:\n${result.stderr}`));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Gets the Firefox version from an existing source directory.
|
|
26
|
+
* @param engineDir - Path to the engine directory
|
|
27
|
+
* @returns Firefox version string
|
|
28
|
+
*/
|
|
29
|
+
export async function getFirefoxVersion(engineDir) {
|
|
30
|
+
const versionPath = join(engineDir, 'browser', 'config', 'version.txt');
|
|
31
|
+
if (!(await pathExists(versionPath))) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const { readText } = await import('../utils/fs.js');
|
|
35
|
+
const version = await readText(versionPath);
|
|
36
|
+
return version.trim();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Formats bytes into a human-readable string.
|
|
40
|
+
* @param bytes - Number of bytes
|
|
41
|
+
* @returns Formatted string (e.g., "1.5 GB")
|
|
42
|
+
*/
|
|
43
|
+
export function formatBytes(bytes) {
|
|
44
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
45
|
+
let size = bytes;
|
|
46
|
+
let unitIndex = 0;
|
|
47
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
48
|
+
size /= 1024;
|
|
49
|
+
unitIndex++;
|
|
50
|
+
}
|
|
51
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=firefox-extract.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firefox source download, extraction, and cache management.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports from focused sub-modules and provides the top-level
|
|
5
|
+
* {@link downloadFirefoxSource} orchestrator.
|
|
6
|
+
*/
|
|
7
|
+
import type { FirefoxProduct } from '../types/config.js';
|
|
8
|
+
import type { ProgressCallback } from './firefox-download.js';
|
|
9
|
+
export { resolveArchive } from './firefox-archive.js';
|
|
10
|
+
export type { ProgressCallback } from './firefox-download.js';
|
|
11
|
+
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.0esr")
|
|
15
|
+
* @param product - Firefox product type
|
|
16
|
+
* @returns Full URL to the source tarball
|
|
17
|
+
*/
|
|
18
|
+
export declare function getDownloadUrl(version: string, product?: FirefoxProduct): string;
|
|
19
|
+
/**
|
|
20
|
+
* Gets the filename for a Firefox source tarball.
|
|
21
|
+
* @param version - Firefox version
|
|
22
|
+
* @param product - Firefox product type
|
|
23
|
+
* @returns Tarball filename
|
|
24
|
+
*/
|
|
25
|
+
export declare function getTarballFilename(version: string, product?: FirefoxProduct): string;
|
|
26
|
+
/**
|
|
27
|
+
* Downloads and extracts Firefox source.
|
|
28
|
+
* @param version - Firefox version to download
|
|
29
|
+
* @param product - Firefox product type
|
|
30
|
+
* @param destDir - Destination directory for extracted source
|
|
31
|
+
* @param cacheDir - Directory to store downloaded tarball
|
|
32
|
+
* @param onProgress - Optional progress callback
|
|
33
|
+
*/
|
|
34
|
+
export declare function downloadFirefoxSource(version: string, product: FirefoxProduct, destDir: string, cacheDir: string, onProgress?: ProgressCallback): Promise<void>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Firefox source download, extraction, and cache management.
|
|
4
|
+
*
|
|
5
|
+
* Re-exports from focused sub-modules and provides the top-level
|
|
6
|
+
* {@link downloadFirefoxSource} orchestrator.
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import { rename } from 'node:fs/promises';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { ensureDir, removeDir } from '../utils/fs.js';
|
|
12
|
+
import { resolveArchive } from './firefox-archive.js';
|
|
13
|
+
import { ensureCachedArchive, invalidateArchiveCache } from './firefox-cache.js';
|
|
14
|
+
import { extractTarXz } from './firefox-extract.js';
|
|
15
|
+
// ── Re-exports (preserve public API) ──
|
|
16
|
+
export { resolveArchive } from './firefox-archive.js';
|
|
17
|
+
export { formatBytes, getFirefoxVersion } from './firefox-extract.js';
|
|
18
|
+
/**
|
|
19
|
+
* Gets the download URL for a Firefox source tarball.
|
|
20
|
+
* @param version - Firefox version (e.g., "140.0esr")
|
|
21
|
+
* @param product - Firefox product type
|
|
22
|
+
* @returns Full URL to the source tarball
|
|
23
|
+
*/
|
|
24
|
+
export function getDownloadUrl(version, product = 'firefox') {
|
|
25
|
+
return resolveArchive(version, product).url;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Gets the filename for a Firefox source tarball.
|
|
29
|
+
* @param version - Firefox version
|
|
30
|
+
* @param product - Firefox product type
|
|
31
|
+
* @returns Tarball filename
|
|
32
|
+
*/
|
|
33
|
+
export function getTarballFilename(version, product = 'firefox') {
|
|
34
|
+
return resolveArchive(version, product).filename;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Downloads and extracts Firefox source.
|
|
38
|
+
* @param version - Firefox version to download
|
|
39
|
+
* @param product - Firefox product type
|
|
40
|
+
* @param destDir - Destination directory for extracted source
|
|
41
|
+
* @param cacheDir - Directory to store downloaded tarball
|
|
42
|
+
* @param onProgress - Optional progress callback
|
|
43
|
+
*/
|
|
44
|
+
export async function downloadFirefoxSource(version, product, destDir, cacheDir, onProgress) {
|
|
45
|
+
const archive = resolveArchive(version, product);
|
|
46
|
+
const tarballPath = join(cacheDir, archive.filename);
|
|
47
|
+
// Ensure cache directory exists
|
|
48
|
+
await ensureDir(cacheDir);
|
|
49
|
+
await ensureCachedArchive(archive, cacheDir, onProgress);
|
|
50
|
+
// Extract to a unique temporary directory so concurrent downloads for
|
|
51
|
+
// the same destination do not clobber each other.
|
|
52
|
+
const tempDir = `${destDir}.tmp-${randomUUID()}`;
|
|
53
|
+
try {
|
|
54
|
+
await extractTarXz(tarballPath, tempDir);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
await removeDir(tempDir);
|
|
58
|
+
await invalidateArchiveCache(archive, cacheDir);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
// Firefox source extracts to a subdirectory (e.g., firefox-140.0/)
|
|
62
|
+
// Find it dynamically since ESR versions may have different naming
|
|
63
|
+
const { readdir } = await import('node:fs/promises');
|
|
64
|
+
const entries = await readdir(tempDir, { withFileTypes: true });
|
|
65
|
+
const extractedSubdir = entries.find((entry) => entry.isDirectory() && entry.name.startsWith('firefox-'));
|
|
66
|
+
if (extractedSubdir) {
|
|
67
|
+
const extractedDir = join(tempDir, extractedSubdir.name);
|
|
68
|
+
await removeDir(destDir);
|
|
69
|
+
await rename(extractedDir, destDir);
|
|
70
|
+
await removeDir(tempDir);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// If no subdirectory, the temp dir is the source
|
|
74
|
+
await removeDir(destDir);
|
|
75
|
+
await rename(tempDir, destDir);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=firefox.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CustomComponentConfig, DryRunAction, OverrideComponentConfig, StepError } from '../types/furnace.js';
|
|
2
|
+
import { type RollbackJournal } from './furnace-rollback.js';
|
|
3
|
+
/** Computes stable checksums for the source files that define a component. */
|
|
4
|
+
export declare function computeComponentChecksums(componentDir: string): Promise<Record<string, string>>;
|
|
5
|
+
/** Compares current component file checksums against the previously recorded state. */
|
|
6
|
+
export declare function hasComponentChanged(componentDir: string, previousChecksums: Record<string, string>): Promise<boolean>;
|
|
7
|
+
/** Applies a custom component into the engine tree and captures registration step errors. */
|
|
8
|
+
export declare function applyCustomComponent(engineDir: string, name: string, componentDir: string, config: CustomComponentConfig, dryRun?: boolean, rollbackJournal?: RollbackJournal): Promise<{
|
|
9
|
+
affectedPaths: string[];
|
|
10
|
+
stepErrors: StepError[];
|
|
11
|
+
actions?: DryRunAction[];
|
|
12
|
+
}>;
|
|
13
|
+
/** Applies an override component by copying its matching files onto the engine tree. */
|
|
14
|
+
export declare function applyOverrideComponent(engineDir: string, name: string, componentDir: string, config: OverrideComponentConfig, dryRun?: boolean, rollbackJournal?: RollbackJournal): Promise<{
|
|
15
|
+
affectedPaths: string[];
|
|
16
|
+
actions?: DryRunAction[];
|
|
17
|
+
}>;
|
|
18
|
+
/** Extracts per-component checksums from the flattened state-file checksum map. */
|
|
19
|
+
export declare function extractComponentChecksums(allChecksums: Record<string, string> | undefined, type: string, name: string): Record<string, string>;
|
|
20
|
+
/** Prefixes component checksums so they can be stored in the flattened state format. */
|
|
21
|
+
export declare function prefixChecksums(checksums: Record<string, string>, type: string, name: string): Record<string, string>;
|