@jgengine/assets 0.6.0 → 0.8.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 +214 -2
- package/README.md +22 -3
- package/dist/cli/pull.d.ts +3 -1
- package/dist/cli/pull.js +48 -38
- package/dist/download.d.ts +30 -2
- package/dist/download.js +71 -4
- package/dist/manifest.d.ts +2 -0
- package/llms.txt +1434 -0
- package/package.json +4 -3
package/dist/download.js
CHANGED
|
@@ -26,11 +26,11 @@ function score(candidate) {
|
|
|
26
26
|
value += 1;
|
|
27
27
|
return value;
|
|
28
28
|
}
|
|
29
|
-
export async function resolveArchiveUrl(source) {
|
|
29
|
+
export async function resolveArchiveUrl(source, fetchImpl = fetch) {
|
|
30
30
|
const download = source.download;
|
|
31
31
|
if (!isScrapeDownload(download))
|
|
32
32
|
return download.url;
|
|
33
|
-
const response = await
|
|
33
|
+
const response = await fetchImpl(download.scrape, { redirect: "follow" });
|
|
34
34
|
if (!response.ok)
|
|
35
35
|
throw new Error(`scrape ${download.scrape} -> HTTP ${response.status}`);
|
|
36
36
|
const html = await response.text();
|
|
@@ -40,12 +40,79 @@ export async function resolveArchiveUrl(source) {
|
|
|
40
40
|
}
|
|
41
41
|
return url;
|
|
42
42
|
}
|
|
43
|
-
export async function downloadArchive(url) {
|
|
44
|
-
const response = await
|
|
43
|
+
export async function downloadArchive(url, fetchImpl = fetch) {
|
|
44
|
+
const response = await fetchImpl(url, { redirect: "follow" });
|
|
45
45
|
if (!response.ok)
|
|
46
46
|
throw new Error(`download ${url} -> HTTP ${response.status}`);
|
|
47
47
|
return new Uint8Array(await response.arrayBuffer());
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Layout for the `--mirror` / `JGENGINE_ASSETS_MIRROR` base URL override: the
|
|
51
|
+
* archive for a pack is expected at `<baseUrl>/<provider>/<packId>.zip`, e.g.
|
|
52
|
+
* `https://my-mirror.example.com/kenney/kenney-nature.zip`.
|
|
53
|
+
*/
|
|
54
|
+
export function mirrorOverrideUrl(baseUrl, source) {
|
|
55
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
56
|
+
return `${base}/${source.provider}/${source.id}.zip`;
|
|
57
|
+
}
|
|
58
|
+
async function verifyPinnedSha(source, archive) {
|
|
59
|
+
const download = source.download;
|
|
60
|
+
if (isScrapeDownload(download) || download.sha256 === undefined)
|
|
61
|
+
return;
|
|
62
|
+
const actual = await sha256Hex(archive);
|
|
63
|
+
if (actual !== download.sha256) {
|
|
64
|
+
throw new Error(`sha256 mismatch: expected ${download.sha256}, got ${actual}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolves and downloads a pack's archive, trying sources in order until one
|
|
69
|
+
* succeeds: (1) the mirror base override at `mirrorOverrideUrl`, (2) the
|
|
70
|
+
* primary provider path (`resolveArchiveUrl`: scrape or pinned URL), (3) the
|
|
71
|
+
* pack's own `mirror` URL. A pinned `sha256` is verified against whichever
|
|
72
|
+
* source supplied the bytes; a mismatch is treated as a failed attempt so the
|
|
73
|
+
* next source in the chain is tried. Throws with every attempted URL and its
|
|
74
|
+
* failure reason when all sources fail.
|
|
75
|
+
*/
|
|
76
|
+
export async function downloadPackArchive(source, options = {}) {
|
|
77
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
78
|
+
const attempted = [];
|
|
79
|
+
const errors = [];
|
|
80
|
+
const tryUrl = async (url) => {
|
|
81
|
+
attempted.push(url);
|
|
82
|
+
try {
|
|
83
|
+
const archive = await downloadArchive(url, fetchImpl);
|
|
84
|
+
await verifyPinnedSha(source, archive);
|
|
85
|
+
return { archive, url, attempted };
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
errors.push(`${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
if (options.mirrorBase !== undefined) {
|
|
93
|
+
const result = await tryUrl(mirrorOverrideUrl(options.mirrorBase, source));
|
|
94
|
+
if (result !== undefined)
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const primaryUrl = await resolveArchiveUrl(source, fetchImpl);
|
|
99
|
+
const result = await tryUrl(primaryUrl);
|
|
100
|
+
if (result !== undefined)
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const label = isScrapeDownload(source.download) ? source.download.scrape : source.download.url;
|
|
105
|
+
errors.push(`${label}: ${error instanceof Error ? error.message : String(error)}`);
|
|
106
|
+
attempted.push(label);
|
|
107
|
+
}
|
|
108
|
+
if (source.mirror !== undefined) {
|
|
109
|
+
const result = await tryUrl(source.mirror);
|
|
110
|
+
if (result !== undefined)
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
throw new Error(`failed to download ${source.id} from all ${attempted.length} source(s):\n` +
|
|
114
|
+
errors.map((line) => ` - ${line}`).join("\n"));
|
|
115
|
+
}
|
|
49
116
|
export function extractGlbs(archive) {
|
|
50
117
|
const entries = unzipSync(archive, {
|
|
51
118
|
filter: (file) => /\.glb$/i.test(file.name),
|
package/dist/manifest.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface AssetSource {
|
|
|
19
19
|
categories: readonly string[];
|
|
20
20
|
download: AssetDownload;
|
|
21
21
|
homepage?: string;
|
|
22
|
+
/** Direct archive URL tried as a last resort when the primary provider path fails; see `downloadPackArchive`. */
|
|
23
|
+
mirror?: string;
|
|
22
24
|
}
|
|
23
25
|
export interface IndexEntry {
|
|
24
26
|
id: string;
|