mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-17 05:09:24 +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
103 lines
3.4 KiB
JavaScript
103 lines
3.4 KiB
JavaScript
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
|
|
const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
|
|
async function lockIsActive(lockPath) {
|
|
try {
|
|
const owner = JSON.parse(await readFile(`${lockPath}/owner.json`, "utf8"));
|
|
if (!Number.isInteger(owner.pid) || owner.pid <= 0) return true;
|
|
try {
|
|
process.kill(owner.pid, 0);
|
|
return true;
|
|
} catch (error) {
|
|
return error?.code === "EPERM";
|
|
}
|
|
} catch {
|
|
// mkdir() wins before owner.json is written. Treat a fresh owner-less
|
|
// directory as active, but recover one left behind by a crashed builder.
|
|
const info = await stat(lockPath).catch(() => null);
|
|
return info !== null && Date.now() - info.mtimeMs < 5_000;
|
|
}
|
|
}
|
|
|
|
export async function withBuildLock(
|
|
lockPath,
|
|
task,
|
|
{ retryMilliseconds = 50, timeoutMilliseconds = 10 * 60_000 } = {},
|
|
) {
|
|
const started = Date.now();
|
|
for (;;) {
|
|
try {
|
|
await mkdir(lockPath);
|
|
await writeFile(`${lockPath}/owner.json`, JSON.stringify({ pid: process.pid }));
|
|
break;
|
|
} catch (error) {
|
|
if (error?.code !== "EEXIST") throw error;
|
|
if (!(await lockIsActive(lockPath))) {
|
|
const abandoned = `${lockPath}.abandoned-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
try {
|
|
await rename(lockPath, abandoned);
|
|
await rm(abandoned, { recursive: true, force: true });
|
|
} catch (takeoverError) {
|
|
if (takeoverError?.code !== "ENOENT") throw takeoverError;
|
|
}
|
|
continue;
|
|
}
|
|
if (Date.now() - started >= timeoutMilliseconds) {
|
|
throw new Error(`timed out waiting for runtime-pack build lock: ${lockPath}`);
|
|
}
|
|
await wait(retryMilliseconds);
|
|
}
|
|
}
|
|
try {
|
|
return await task();
|
|
} finally {
|
|
await rm(lockPath, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
async function moveAside(path, backup) {
|
|
try {
|
|
await rename(path, backup);
|
|
return true;
|
|
} catch (error) {
|
|
if (error?.code === "ENOENT") return false;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function installRuntimePack({
|
|
outputRoot,
|
|
manifestPath,
|
|
stagedOutputRoot,
|
|
stagedManifestPath,
|
|
backupRoot,
|
|
backupManifestPath,
|
|
}) {
|
|
let outputBackedUp = false;
|
|
let manifestBackedUp = false;
|
|
let outputInstalled = false;
|
|
let manifestInstalled = false;
|
|
try {
|
|
outputBackedUp = await moveAside(outputRoot, backupRoot);
|
|
manifestBackedUp = await moveAside(manifestPath, backupManifestPath);
|
|
await rename(stagedOutputRoot, outputRoot);
|
|
outputInstalled = true;
|
|
await rename(stagedManifestPath, manifestPath);
|
|
manifestInstalled = true;
|
|
} catch (error) {
|
|
if (manifestInstalled) await rm(manifestPath, { force: true }).catch(() => undefined);
|
|
if (outputInstalled) await rm(outputRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
if (manifestBackedUp) await rename(backupManifestPath, manifestPath).catch(() => undefined);
|
|
if (outputBackedUp) await rename(backupRoot, outputRoot).catch(() => undefined);
|
|
throw error;
|
|
}
|
|
// Backup cleanup is outside the transactional install. Once both staged
|
|
// outputs are live, a cleanup failure must not remove them or attempt a
|
|
// rollback from a backup that may already have been deleted.
|
|
await Promise.all([
|
|
rm(backupRoot, { recursive: true, force: true }).catch(() => undefined),
|
|
rm(backupManifestPath, { force: true }).catch(() => undefined),
|
|
]);
|
|
}
|