feat: bootstrap portable Node runtimes
CI / Client installer (macos-latest) (push) Canceled after 0s
CI / Client installer (ubuntu-latest) (push) Canceled after 0s
CI / Client installer (windows-latest) (push) Canceled after 0s
CI / test (22) (push) Canceled after 0s
CI / test (24) (push) Canceled after 0s
CI / release-smoke (push) Canceled after 0s
CI / Client installer (macos-latest) (push) Canceled after 0s
CI / Client installer (ubuntu-latest) (push) Canceled after 0s
CI / Client installer (windows-latest) (push) Canceled after 0s
CI / test (22) (push) Canceled after 0s
CI / test (24) (push) Canceled after 0s
CI / release-smoke (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env node
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const catalog = JSON.parse(readFileSync(new URL("../distribution/node-runtime.json", import.meta.url), "utf8"));
|
||||
const expected = new Map(Object.values(catalog.platforms).map((value) => [value.file, value.sha256]));
|
||||
const results = await Promise.all(catalog.sourceBases.map(checkSource));
|
||||
for (const result of results) {
|
||||
console.log(`${result.ok ? "OK" : "FAIL"} ${result.host}: ${result.message}`);
|
||||
}
|
||||
const official = results.find((result) => result.host === "nodejs.org");
|
||||
const domestic = results.filter((result) => result.host !== "nodejs.org");
|
||||
if (!official?.ok) throw new Error("Node 官方精确版本源不可用或校验清单不匹配");
|
||||
if (!domestic.some((result) => result.ok)) throw new Error("没有可用且校验一致的国内 Node 下载源");
|
||||
|
||||
async function checkSource(sourceBase) {
|
||||
const base = new URL(sourceBase);
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 20_000);
|
||||
timeout.unref();
|
||||
try {
|
||||
const checksumResponse = await fetch(new URL("SHASUMS256.txt", base), { signal: controller.signal });
|
||||
if (!checksumResponse.ok) return failure(base, `SHASUMS256.txt HTTP ${checksumResponse.status}`);
|
||||
const checksums = parseChecksums(await checksumResponse.text());
|
||||
for (const [file, sha256] of expected) {
|
||||
if (checksums.get(file) !== sha256) return failure(base, `${file} 的官方 SHA-256 不匹配或缺失`);
|
||||
}
|
||||
const heads = await Promise.all([...expected.keys()].map(async (file) => {
|
||||
const response = await fetch(new URL(file, base), { method: "HEAD", signal: controller.signal });
|
||||
return { file, status: response.status, ok: response.ok };
|
||||
}));
|
||||
const unavailable = heads.filter((item) => !item.ok);
|
||||
if (unavailable.length) return failure(base, `${unavailable[0].file} HTTP ${unavailable[0].status}`);
|
||||
return { host: base.host, ok: true, message: `${expected.size} 个三平台制品可用且哈希一致` };
|
||||
} catch (error) {
|
||||
return failure(base, error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
function parseChecksums(body) {
|
||||
const values = new Map();
|
||||
for (const line of body.split(/\r?\n/)) {
|
||||
const match = /^([a-f0-9]{64})\s+\*?(.+)$/.exec(line.trim());
|
||||
if (match) values.set(match[2], match[1]);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function failure(base, message) { return { host: base.host, ok: false, message }; }
|
||||
Reference in New Issue
Block a user