mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-16 05:00:59 +08:00
* feat(runtime): ship precompiled macOS artifacts - Add a versioned macOS arm64 runtime pack with deterministic feature selection and artifact verification. - Link LLVM helper objects against release-built runtime and vendor inputs without compiling user-machine C. - Wire package publishing, cache validation, documentation, and full-gate coverage for the new path. * fix(runtime): harden precompiled artifact handling * fix(runtime): make precompiled artifacts reproducible * fix(runtime): reject opaque linkers from executable cache * fix(runtime): normalize precompiled archive metadata * fix(runtime): harden precompiled artifact caching * fix(runtime): close executable cache link race * fix(runtime): stage verified pack artifacts * fix(runtime): bracket helper cache inputs * fix(runtime): preserve executable link identity * fix(runtime): trace selected linker dependencies
54 lines
2.3 KiB
JavaScript
54 lines
2.3 KiB
JavaScript
import { execFile } from "node:child_process";
|
|
import { readFile, writeFile } from "node:fs/promises";
|
|
import { promisify } from "node:util";
|
|
|
|
const run = promisify(execFile);
|
|
const GLOBAL_HEADER = "!<arch>\n";
|
|
const MEMBER_HEADER_SIZE = 60;
|
|
|
|
function replaceField(bytes, offset, width, value) {
|
|
bytes.write(value.padEnd(width, " "), offset, width, "ascii");
|
|
}
|
|
|
|
async function normalizeMetadata(output) {
|
|
const bytes = await readFile(output);
|
|
if (bytes.subarray(0, GLOBAL_HEADER.length).toString("ascii") !== GLOBAL_HEADER) {
|
|
throw new Error(`archiver produced an invalid archive: ${output}`);
|
|
}
|
|
let offset = GLOBAL_HEADER.length;
|
|
while (offset < bytes.length) {
|
|
const headerEnd = offset + MEMBER_HEADER_SIZE;
|
|
if (
|
|
headerEnd > bytes.length ||
|
|
bytes.subarray(offset + 58, headerEnd).toString("ascii") !== "`\n"
|
|
) throw new Error(`archiver produced a malformed member header: ${output}`);
|
|
const sizeText = bytes.subarray(offset + 48, offset + 58).toString("ascii").trim();
|
|
if (!/^\d+$/.test(sizeText)) {
|
|
throw new Error(`archiver produced a malformed member size: ${output}`);
|
|
}
|
|
const size = Number(sizeText);
|
|
if (!Number.isSafeInteger(size) || headerEnd + size > bytes.length) {
|
|
throw new Error(`archiver produced an invalid member size: ${output}`);
|
|
}
|
|
// ar stores these values in fixed-width ASCII fields. They do not affect
|
|
// member offsets or the symbol table, so normalizing them after indexing
|
|
// works with Apple/BSD ar as well as archivers that implement a D mode.
|
|
replaceField(bytes, offset + 16, 12, "0"); // timestamp
|
|
replaceField(bytes, offset + 28, 6, "0"); // uid
|
|
replaceField(bytes, offset + 34, 6, "0"); // gid
|
|
replaceField(bytes, offset + 40, 8, "100644"); // mode
|
|
offset = headerEnd + size + (size % 2);
|
|
}
|
|
if (offset !== bytes.length) throw new Error(`archiver produced a truncated archive: ${output}`);
|
|
await writeFile(output, bytes);
|
|
}
|
|
|
|
export async function createDeterministicArchive(archiver, output, objects) {
|
|
await run(archiver, ["rcs", output, ...objects], {
|
|
// Ask Apple/BSD ar to omit timestamps up front, then normalize every
|
|
// variable member-header field below for cross-account reproducibility.
|
|
env: { ...process.env, ZERO_AR_DATE: "1" },
|
|
});
|
|
await normalizeMetadata(output);
|
|
}
|