Files
scriptc/scripts/sandbox-image.mjs
T
Chris Tate 177d025452 Harden sandbox gate across host platforms
- Retain representative Darwin kqueue coverage in the default gate.
- Schedule native runtime tests by host and make Linux clang invocations portable.
- Resolve canonical OCI manifest digests for direct and indexed images.
2026-07-28 17:48:17 -05:00

91 lines
3.0 KiB
JavaScript

#!/usr/bin/env node
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { spawn } from "node:child_process";
import { sandboxImageConfig } from "./sandbox-config.mjs";
import { linuxAmd64ManifestDigest } from "./oci-manifest.mjs";
const root = fileURLToPath(new URL("../", import.meta.url));
const { project, reference: image, repository, tag, team } = sandboxImageConfig();
const nodeVersion = (await readFile(new URL("../.node-version", import.meta.url), "utf8")).trim();
function run(command, args, { capture = false } = {}) {
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
cwd: root,
env: { ...process.env, NO_UPDATE_NOTIFIER: "1" },
stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit",
});
let stdout = "";
let stderr = "";
if (capture) {
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => (stdout += chunk));
child.stderr.on("data", (chunk) => (stderr += chunk));
}
child.on("error", reject);
child.on("exit", (code, signal) => {
if (code === 0) resolve(stdout);
else reject(new Error(`${command} exited ${signal ?? code}${stderr ? `\n${stderr.trim()}` : ""}`));
});
});
}
console.log(`Authenticating Docker for ${team}/${project}...`);
await run("vercel", ["vcr", "login", "docker", "--scope", team, "--project", project]);
console.log(`Building and pushing ${image} for linux/amd64...`);
await run("docker", [
"buildx",
"build",
"--platform",
"linux/amd64",
"--build-arg",
`NODE_VERSION=${nodeVersion}`,
"--file",
"Dockerfile.sandbox",
"--output",
`type=image,name=${image},push=true,oci-mediatypes=true,compression=zstd,compression-level=3,force-compression=true`,
".",
]);
const inspection = JSON.parse(
await run(
"docker",
["buildx", "imagetools", "inspect", image, "--format", "{{json .}}"],
{ capture: true },
),
);
let amd64Digest;
try {
amd64Digest = linuxAmd64ManifestDigest(inspection);
} catch (error) {
throw new Error(`could not find the linux/amd64 manifest for ${image}`, {
cause: error,
});
}
console.log("Waiting for VCR to prepare the image for Sandbox...");
const deadline = Date.now() + 5 * 60_000;
while (Date.now() < deadline) {
const listing = JSON.parse(
await run(
"vercel",
["vcr", "image", "ls", repository, "--format", "json", "--scope", team, "--project", project],
{ capture: true },
),
);
const manifest = listing.images?.find((candidate) => candidate.manifestDigest === amd64Digest);
if (manifest?.status === "ready") {
console.log(`${repository}:${tag} is ready for Vercel Sandbox.`);
process.exit(0);
}
if (manifest?.status === "unoptimized") {
throw new Error(`${repository}:${tag} is unoptimized; Vercel Sandbox requires linux/amd64`);
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
throw new Error(`VCR did not prepare ${repository}:${tag} within 5 minutes`);