@jongleberry/vurst-html 0.2.2 → 0.2.3
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jongleberry/vurst-html",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Rust + N-API: HTML sanitization, extraction, and boilerstrip.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"index.js",
|
|
25
25
|
"index.d.ts",
|
|
26
26
|
"README.md",
|
|
27
|
-
"
|
|
27
|
+
"scripts/"
|
|
28
28
|
],
|
|
29
29
|
"napi": {
|
|
30
30
|
"binaryName": "vurst-html",
|
|
@@ -42,14 +42,13 @@
|
|
|
42
42
|
"@napi-rs/cli": "^3.6.2",
|
|
43
43
|
"@types/node": "^24"
|
|
44
44
|
},
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"@jongleberry/vurst-html-linux-arm64-gnu": "0.2.2",
|
|
48
|
-
"@jongleberry/vurst-html-linux-x64-gnu": "0.2.2"
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"https-proxy-agent": "^7.0.6"
|
|
49
47
|
},
|
|
50
48
|
"scripts": {
|
|
51
49
|
"build": "napi build --platform --release",
|
|
52
50
|
"build:debug": "napi build --platform",
|
|
53
|
-
"test": "node --test __test__/*.test.mjs"
|
|
51
|
+
"test": "node --test __test__/*.test.mjs",
|
|
52
|
+
"postinstall": "node scripts/install.js"
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { createHash, randomBytes } = require("node:crypto");
|
|
5
|
+
const { createReadStream, createWriteStream, existsSync } = require("node:fs");
|
|
6
|
+
const {
|
|
7
|
+
copyFile,
|
|
8
|
+
mkdir,
|
|
9
|
+
readFile,
|
|
10
|
+
rename,
|
|
11
|
+
rm,
|
|
12
|
+
writeFile,
|
|
13
|
+
} = require("node:fs/promises");
|
|
14
|
+
const http = require("node:http");
|
|
15
|
+
const https = require("node:https");
|
|
16
|
+
const { basename, dirname, join } = require("node:path");
|
|
17
|
+
const { pipeline } = require("node:stream/promises");
|
|
18
|
+
const { fileURLToPath } = require("node:url");
|
|
19
|
+
const { HttpsProxyAgent } = require("https-proxy-agent");
|
|
20
|
+
|
|
21
|
+
const REPOSITORY = "jonathanong/vurst";
|
|
22
|
+
const DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
23
|
+
const MAX_REDIRECTS = 5;
|
|
24
|
+
const MAX_ATTEMPTS = 6;
|
|
25
|
+
const RETRY_BASE_MS = 1_000;
|
|
26
|
+
const RETRYABLE_CODES = new Set([
|
|
27
|
+
"EAI_AGAIN",
|
|
28
|
+
"ECONNREFUSED",
|
|
29
|
+
"ECONNRESET",
|
|
30
|
+
"ENETUNREACH",
|
|
31
|
+
"EPIPE",
|
|
32
|
+
"ERR_STREAM_PREMATURE_CLOSE",
|
|
33
|
+
"ETIMEDOUT",
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const TARGETS = {
|
|
37
|
+
"darwin-arm64": {
|
|
38
|
+
triple: "aarch64-apple-darwin",
|
|
39
|
+
napiSuffix: "darwin-arm64",
|
|
40
|
+
onnxDirectory: "darwin-arm64",
|
|
41
|
+
onnxLibrary: "libonnxruntime.dylib",
|
|
42
|
+
onnxAssetExtension: ".dylib",
|
|
43
|
+
},
|
|
44
|
+
"linux-x64": {
|
|
45
|
+
triple: "x86_64-unknown-linux-gnu",
|
|
46
|
+
napiSuffix: "linux-x64-gnu",
|
|
47
|
+
onnxDirectory: "linux-x64",
|
|
48
|
+
onnxLibrary: "libonnxruntime.so",
|
|
49
|
+
onnxAssetExtension: ".so",
|
|
50
|
+
},
|
|
51
|
+
"linux-arm64": {
|
|
52
|
+
triple: "aarch64-unknown-linux-gnu",
|
|
53
|
+
napiSuffix: "linux-arm64-gnu",
|
|
54
|
+
onnxDirectory: "linux-arm64",
|
|
55
|
+
onnxLibrary: "libonnxruntime.so",
|
|
56
|
+
onnxAssetExtension: ".so",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
class HttpError extends Error {
|
|
61
|
+
constructor(url, statusCode) {
|
|
62
|
+
super(`Download failed for ${url}: HTTP ${statusCode}`);
|
|
63
|
+
this.statusCode = statusCode;
|
|
64
|
+
this.retryable =
|
|
65
|
+
statusCode === 408 ||
|
|
66
|
+
statusCode === 429 ||
|
|
67
|
+
(statusCode >= 500 && statusCode < 600);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function detectMusl(report = process.report) {
|
|
72
|
+
try {
|
|
73
|
+
const current =
|
|
74
|
+
typeof report?.getReport === "function" ? report.getReport() : null;
|
|
75
|
+
if (current?.header?.glibcVersionRuntime) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (
|
|
79
|
+
current?.sharedObjects?.some(
|
|
80
|
+
(path) => path.includes("libc.musl-") || path.includes("ld-musl-"),
|
|
81
|
+
)
|
|
82
|
+
) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Fall through to the ldd probe.
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
return require("node:child_process")
|
|
91
|
+
.execFileSync("ldd", ["--version"], { encoding: "utf8" })
|
|
92
|
+
.includes("musl");
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function platformTarget(
|
|
99
|
+
platform = process.platform,
|
|
100
|
+
arch = process.arch,
|
|
101
|
+
musl = detectMusl(),
|
|
102
|
+
) {
|
|
103
|
+
if (platform === "darwin" && arch === "arm64") {
|
|
104
|
+
return TARGETS["darwin-arm64"];
|
|
105
|
+
}
|
|
106
|
+
if (platform === "linux" && arch === "x64" && !musl) {
|
|
107
|
+
return TARGETS["linux-x64"];
|
|
108
|
+
}
|
|
109
|
+
if (platform === "linux" && arch === "arm64" && !musl) {
|
|
110
|
+
return TARGETS["linux-arm64"];
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function packageKind(packageName) {
|
|
116
|
+
const match = /^@jongleberry\/vurst-(ai|html|markdown)$/.exec(packageName);
|
|
117
|
+
if (!match) {
|
|
118
|
+
throw new Error(`Unsupported vurst native package: ${packageName}`);
|
|
119
|
+
}
|
|
120
|
+
return match[1];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function releaseBaseUrl(version) {
|
|
124
|
+
return (
|
|
125
|
+
process.env.VURST_RELEASE_BASE_URL ||
|
|
126
|
+
`https://github.com/${REPOSITORY}/releases/download/v${version}`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function normalizeBaseUrl(value) {
|
|
131
|
+
const url = String(value);
|
|
132
|
+
return url.endsWith("://") ? url : url.replace(/\/+$/, "");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function validateBaseUrl(value) {
|
|
136
|
+
let url;
|
|
137
|
+
try {
|
|
138
|
+
url = new URL(value);
|
|
139
|
+
} catch {
|
|
140
|
+
throw new Error(`Invalid release base URL: ${value}`);
|
|
141
|
+
}
|
|
142
|
+
if (url.username || url.password) {
|
|
143
|
+
throw new Error("Release download URLs must not contain credentials");
|
|
144
|
+
}
|
|
145
|
+
if (url.protocol === "file:") {
|
|
146
|
+
if (!String(value).toLowerCase().startsWith("file:///")) {
|
|
147
|
+
throw new Error("File release URLs must use canonical file:/// form");
|
|
148
|
+
}
|
|
149
|
+
return url;
|
|
150
|
+
}
|
|
151
|
+
const local = url.hostname === "127.0.0.1" || url.hostname === "localhost";
|
|
152
|
+
if (url.protocol !== "https:" && !(local && url.protocol === "http:")) {
|
|
153
|
+
throw new Error("Release download URLs must use HTTPS");
|
|
154
|
+
}
|
|
155
|
+
return url;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function validateDownloadUrl(value, baseUrl) {
|
|
159
|
+
const url = validateBaseUrl(value);
|
|
160
|
+
if (url.protocol === "file:") {
|
|
161
|
+
if (baseUrl.protocol !== "file:") {
|
|
162
|
+
throw new Error(`Untrusted release redirect: ${value}`);
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const sameOrigin = url.origin === baseUrl.origin;
|
|
167
|
+
const githubAssetHost =
|
|
168
|
+
baseUrl.hostname === "github.com" &&
|
|
169
|
+
(url.hostname === "githubusercontent.com" ||
|
|
170
|
+
url.hostname.endsWith(".githubusercontent.com"));
|
|
171
|
+
if (!sameOrigin && !githubAssetHost) {
|
|
172
|
+
throw new Error(`Untrusted release redirect: ${value}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function shouldBypassProxy(url, environment = process.env) {
|
|
177
|
+
const noProxy =
|
|
178
|
+
environment.npm_config_noproxy ||
|
|
179
|
+
environment.NO_PROXY ||
|
|
180
|
+
environment.no_proxy;
|
|
181
|
+
if (!noProxy) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
const hostname = url.hostname.toLowerCase();
|
|
185
|
+
const port = url.port || (url.protocol === "https:" ? "443" : "80");
|
|
186
|
+
return noProxy.split(",").some((rawEntry) => {
|
|
187
|
+
const entry = rawEntry.trim().toLowerCase();
|
|
188
|
+
if (!entry) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
if (entry === "*") {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
const separator = entry.lastIndexOf(":");
|
|
195
|
+
const hasPort = separator > 0 && /^\d+$/.test(entry.slice(separator + 1));
|
|
196
|
+
const entryPort = hasPort ? entry.slice(separator + 1) : null;
|
|
197
|
+
const entryHost = (hasPort ? entry.slice(0, separator) : entry).replace(
|
|
198
|
+
/^\./,
|
|
199
|
+
"",
|
|
200
|
+
);
|
|
201
|
+
if (entryPort && entryPort !== port) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
return hostname === entryHost || hostname.endsWith(`.${entryHost}`);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function proxyUrlFor(value, environment = process.env) {
|
|
209
|
+
const url = new URL(value);
|
|
210
|
+
if (url.protocol === "file:" || shouldBypassProxy(url, environment)) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
if (url.protocol === "https:") {
|
|
214
|
+
return (
|
|
215
|
+
environment.npm_config_https_proxy ||
|
|
216
|
+
environment.HTTPS_PROXY ||
|
|
217
|
+
environment.https_proxy ||
|
|
218
|
+
environment.npm_config_proxy ||
|
|
219
|
+
environment.HTTP_PROXY ||
|
|
220
|
+
environment.http_proxy ||
|
|
221
|
+
environment.ALL_PROXY ||
|
|
222
|
+
environment.all_proxy ||
|
|
223
|
+
null
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
return (
|
|
227
|
+
environment.npm_config_proxy ||
|
|
228
|
+
environment.HTTP_PROXY ||
|
|
229
|
+
environment.http_proxy ||
|
|
230
|
+
environment.ALL_PROXY ||
|
|
231
|
+
environment.all_proxy ||
|
|
232
|
+
null
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function request(url, handleResponse, baseUrl, redirects = 0) {
|
|
237
|
+
validateDownloadUrl(url, baseUrl);
|
|
238
|
+
return new Promise((resolve, reject) => {
|
|
239
|
+
const client = url.startsWith("http://") ? http : https;
|
|
240
|
+
const proxyUrl = proxyUrlFor(url);
|
|
241
|
+
const requestOptions = proxyUrl
|
|
242
|
+
? { agent: new HttpsProxyAgent(proxyUrl) }
|
|
243
|
+
: undefined;
|
|
244
|
+
const req = client.get(url, requestOptions, (response) => {
|
|
245
|
+
if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
|
|
246
|
+
response.resume();
|
|
247
|
+
if (redirects >= MAX_REDIRECTS) {
|
|
248
|
+
reject(new Error(`Too many redirects while downloading ${url}`));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (!response.headers.location) {
|
|
252
|
+
reject(new Error(`Redirect missing Location header for ${url}`));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const redirected = new URL(response.headers.location, url).toString();
|
|
256
|
+
request(redirected, handleResponse, baseUrl, redirects + 1).then(
|
|
257
|
+
resolve,
|
|
258
|
+
reject,
|
|
259
|
+
);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (response.statusCode !== 200) {
|
|
263
|
+
response.resume();
|
|
264
|
+
reject(new HttpError(url, response.statusCode));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
Promise.resolve(handleResponse(response)).then(resolve, reject);
|
|
268
|
+
});
|
|
269
|
+
req.setTimeout(DOWNLOAD_TIMEOUT_MS, () => {
|
|
270
|
+
const error = new Error(`Download timed out after ${DOWNLOAD_TIMEOUT_MS}ms`);
|
|
271
|
+
error.retryable = true;
|
|
272
|
+
req.destroy(error);
|
|
273
|
+
});
|
|
274
|
+
req.on("error", reject);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function retryable(error) {
|
|
279
|
+
return error?.retryable === true || RETRYABLE_CODES.has(error?.code);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function withRetry(operation) {
|
|
283
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
284
|
+
try {
|
|
285
|
+
return await operation();
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (attempt >= MAX_ATTEMPTS || !retryable(error)) {
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
const maximum = Math.min(RETRY_BASE_MS * 2 ** (attempt - 1), 30_000);
|
|
291
|
+
const delay = Math.floor(Math.random() * maximum);
|
|
292
|
+
console.warn(
|
|
293
|
+
`vurst: retrying release download after ${error.message} ` +
|
|
294
|
+
`(attempt ${attempt + 1}/${MAX_ATTEMPTS})`,
|
|
295
|
+
);
|
|
296
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function download(url, destination, baseUrl) {
|
|
302
|
+
validateDownloadUrl(url, baseUrl);
|
|
303
|
+
if (new URL(url).protocol === "file:") {
|
|
304
|
+
await copyFile(fileURLToPath(url), destination);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
await withRetry(() =>
|
|
308
|
+
request(
|
|
309
|
+
url,
|
|
310
|
+
(response) => pipeline(response, createWriteStream(destination)),
|
|
311
|
+
baseUrl,
|
|
312
|
+
),
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function fetchText(url, baseUrl) {
|
|
317
|
+
validateDownloadUrl(url, baseUrl);
|
|
318
|
+
if (new URL(url).protocol === "file:") {
|
|
319
|
+
return readFile(fileURLToPath(url), "utf8");
|
|
320
|
+
}
|
|
321
|
+
return withRetry(async () => {
|
|
322
|
+
const chunks = [];
|
|
323
|
+
let length = 0;
|
|
324
|
+
await request(
|
|
325
|
+
url,
|
|
326
|
+
async (response) => {
|
|
327
|
+
for await (const chunk of response) {
|
|
328
|
+
length += chunk.length;
|
|
329
|
+
if (length > 1024 * 1024) {
|
|
330
|
+
throw new Error("Checksum response exceeded 1 MiB");
|
|
331
|
+
}
|
|
332
|
+
chunks.push(chunk);
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
baseUrl,
|
|
336
|
+
);
|
|
337
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function parseChecksum(text, expectedAsset) {
|
|
342
|
+
for (const line of text.split(/\r?\n/)) {
|
|
343
|
+
const [hash, file] = line.trim().split(/\s+/, 2);
|
|
344
|
+
const normalizedFile = file?.replace(/^\*/, "");
|
|
345
|
+
if (
|
|
346
|
+
/^[a-fA-F0-9]{64}$/.test(hash || "") &&
|
|
347
|
+
(!file ||
|
|
348
|
+
normalizedFile === expectedAsset ||
|
|
349
|
+
basename(normalizedFile) === expectedAsset)
|
|
350
|
+
) {
|
|
351
|
+
return hash.toLowerCase();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
throw new Error(`No SHA-256 checksum found for ${expectedAsset}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function sha256(path) {
|
|
358
|
+
const hash = createHash("sha256");
|
|
359
|
+
for await (const chunk of createReadStream(path)) {
|
|
360
|
+
hash.update(chunk);
|
|
361
|
+
}
|
|
362
|
+
return hash.digest("hex");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function requiredAssets(kind, version, target, packageRoot) {
|
|
366
|
+
const binaryName = `vurst-${kind}`;
|
|
367
|
+
const assets = [
|
|
368
|
+
{
|
|
369
|
+
name: `${binaryName}-v${version}-${target.triple}.node`,
|
|
370
|
+
destination: join(packageRoot, `${binaryName}.${target.napiSuffix}.node`),
|
|
371
|
+
},
|
|
372
|
+
];
|
|
373
|
+
if (kind === "ai") {
|
|
374
|
+
assets.push({
|
|
375
|
+
name:
|
|
376
|
+
`vurst-ai-onnxruntime-v${version}-${target.triple}` +
|
|
377
|
+
target.onnxAssetExtension,
|
|
378
|
+
destination: join(
|
|
379
|
+
packageRoot,
|
|
380
|
+
"onnxruntime",
|
|
381
|
+
target.onnxDirectory,
|
|
382
|
+
target.onnxLibrary,
|
|
383
|
+
),
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return assets;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function installedVersion(marker) {
|
|
390
|
+
try {
|
|
391
|
+
return (await readFile(marker, "utf8")).trim();
|
|
392
|
+
} catch (error) {
|
|
393
|
+
if (error.code === "ENOENT") {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function installNative(options = {}) {
|
|
401
|
+
const packageRoot = options.packageRoot || join(__dirname, "..");
|
|
402
|
+
const pkg = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
|
|
403
|
+
const kind = packageKind(pkg.name);
|
|
404
|
+
if (process.env.VURST_SKIP_BINARY_DOWNLOAD === "1") {
|
|
405
|
+
console.log(`Skipping ${pkg.name} native download`);
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
const target =
|
|
409
|
+
options.target ||
|
|
410
|
+
platformTarget(options.platform, options.arch, options.musl);
|
|
411
|
+
if (!target) {
|
|
412
|
+
throw new Error(
|
|
413
|
+
`Unsupported platform ${options.platform || process.platform}/` +
|
|
414
|
+
`${options.arch || process.arch}; vurst provides macOS arm64 and ` +
|
|
415
|
+
"Linux x64/arm64 glibc binaries",
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const marker = join(packageRoot, ".vurst-native-version");
|
|
420
|
+
const assets = requiredAssets(kind, pkg.version, target, packageRoot);
|
|
421
|
+
if (
|
|
422
|
+
(await installedVersion(marker)) === pkg.version &&
|
|
423
|
+
assets.every(({ destination }) => existsSync(destination))
|
|
424
|
+
) {
|
|
425
|
+
console.log(`${pkg.name} native assets already match v${pkg.version}`);
|
|
426
|
+
return assets.map(({ destination }) => destination);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const base = normalizeBaseUrl(options.baseUrl || releaseBaseUrl(pkg.version));
|
|
430
|
+
const parsedBase = validateBaseUrl(base);
|
|
431
|
+
const temporary = [];
|
|
432
|
+
try {
|
|
433
|
+
for (const asset of assets) {
|
|
434
|
+
await mkdir(dirname(asset.destination), { recursive: true });
|
|
435
|
+
const temp = `${asset.destination}.tmp-${randomBytes(8).toString("hex")}`;
|
|
436
|
+
temporary.push(temp);
|
|
437
|
+
console.log(`Downloading ${asset.name}`);
|
|
438
|
+
await download(`${base}/${asset.name}`, temp, parsedBase);
|
|
439
|
+
const checksumText = await fetchText(
|
|
440
|
+
`${base}/${asset.name}.sha256`,
|
|
441
|
+
parsedBase,
|
|
442
|
+
);
|
|
443
|
+
const expected = parseChecksum(checksumText, asset.name);
|
|
444
|
+
const actual = await sha256(temp);
|
|
445
|
+
if (actual !== expected) {
|
|
446
|
+
throw new Error(
|
|
447
|
+
`Checksum mismatch for ${asset.name}: expected ${expected}, got ${actual}`,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
for (let index = 0; index < assets.length; index += 1) {
|
|
453
|
+
await rename(temporary[index], assets[index].destination);
|
|
454
|
+
}
|
|
455
|
+
const markerTemp = `${marker}.tmp-${randomBytes(8).toString("hex")}`;
|
|
456
|
+
temporary.push(markerTemp);
|
|
457
|
+
await writeFile(markerTemp, `${pkg.version}\n`);
|
|
458
|
+
await rename(markerTemp, marker);
|
|
459
|
+
return assets.map(({ destination }) => destination);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
await Promise.all(temporary.map((path) => rm(path, { force: true })));
|
|
462
|
+
throw new Error(`Failed to install ${pkg.name} native assets: ${error.message}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async function main() {
|
|
467
|
+
try {
|
|
468
|
+
await installNative();
|
|
469
|
+
} catch (error) {
|
|
470
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
471
|
+
process.exitCode = 1;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (require.main === module) {
|
|
476
|
+
void main();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
module.exports = {
|
|
480
|
+
TARGETS,
|
|
481
|
+
installNative,
|
|
482
|
+
installedVersion,
|
|
483
|
+
packageKind,
|
|
484
|
+
parseChecksum,
|
|
485
|
+
platformTarget,
|
|
486
|
+
proxyUrlFor,
|
|
487
|
+
requiredAssets,
|
|
488
|
+
sha256,
|
|
489
|
+
shouldBypassProxy,
|
|
490
|
+
validateBaseUrl,
|
|
491
|
+
};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|