mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-21 05:50:55 +08:00
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks. - Preserve compiler import boundaries and protected fixtures while cleaning up lint findings. - Standardize source formatting and validate import, fixture, and embedded-source protections.
118 lines
4.3 KiB
JavaScript
118 lines
4.3 KiB
JavaScript
import { spawn } from "node:child_process";
|
|
import { existsSync, rmSync } from "node:fs";
|
|
import { dirname, resolve } from "node:path";
|
|
import { setTimeout } from "node:timers/promises";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
export function nativeBootstrapPlan({ phase = "all", sanitize = false } = {}) {
|
|
if (!["all", "commands", "rebuild"].includes(phase)) {
|
|
throw new Error("SCRIPTC_BOOTSTRAP_PHASE must be all, commands, or rebuild");
|
|
}
|
|
const phases = phase === "all" ? ["commands", "rebuild"] : [phase];
|
|
return { phases, packageChecks: !sanitize && phases.includes("commands") };
|
|
}
|
|
|
|
export async function runNativeBootstrapChecks(plan, checks) {
|
|
const results = await Promise.allSettled(
|
|
plan.phases.map((phase) => Promise.resolve().then(() => checks[phase]())),
|
|
);
|
|
const failures = results
|
|
.filter((result) => result.status === "rejected")
|
|
.map((result) => result.reason);
|
|
if (failures.length) throw new AggregateError(failures, "native bootstrap phases failed");
|
|
}
|
|
|
|
export async function runBootstrapAndPackageChecks({
|
|
bootstrap,
|
|
packageReady,
|
|
packageChecks,
|
|
poll = () => setTimeout(100),
|
|
}) {
|
|
let finished = false;
|
|
const building = Promise.resolve()
|
|
.then(bootstrap)
|
|
.finally(() => {
|
|
finished = true;
|
|
});
|
|
const packaging = async () => {
|
|
while (!packageReady()) {
|
|
if (finished) throw new Error("bootstrap ended before its native package was ready");
|
|
await poll();
|
|
}
|
|
await packageChecks();
|
|
};
|
|
// Keep the package checks alive after a bootstrap failure and vice versa,
|
|
// then report both results once no child can still read the shared package.
|
|
const results = await Promise.allSettled([building, packaging()]);
|
|
const failures = results
|
|
.filter((result) => result.status === "rejected")
|
|
.map((result) => result.reason);
|
|
if (failures.length) throw new AggregateError(failures, "native bootstrap checks failed");
|
|
}
|
|
|
|
async function command(executable, args) {
|
|
await new Promise((resolve, reject) => {
|
|
const child = spawn(executable, args, { stdio: "inherit", env: process.env });
|
|
child.once("error", reject);
|
|
child.once("exit", (code, signal) =>
|
|
code === 0 ? resolve() : reject(new Error(`${executable} exited with ${signal ?? code}`)),
|
|
);
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
const plan = nativeBootstrapPlan({
|
|
phase: process.env.SCRIPTC_BOOTSTRAP_PHASE,
|
|
sanitize: process.env.SCRIPTC_SAN === "1",
|
|
});
|
|
const bootstrap = () =>
|
|
command("pnpm", ["test", "tests/harness/self-hosting-native-driver.test.ts"]);
|
|
if (!plan.packageChecks) return bootstrap();
|
|
const packageDirectory = process.env.SCRIPTC_BOOTSTRAP_PACKAGE_DIR;
|
|
if (!packageDirectory) throw new Error("SCRIPTC_BOOTSTRAP_PACKAGE_DIR is required");
|
|
const ready = packageDirectory + ".ready";
|
|
rmSync(ready, { force: true });
|
|
await runBootstrapAndPackageChecks({
|
|
bootstrap,
|
|
packageReady: () => existsSync(ready),
|
|
packageChecks: async () => {
|
|
const verify = async () => {
|
|
await command("pnpm", ["test", "packages/runtime/test/glibc-random.test.ts"]);
|
|
await command(process.execPath, [
|
|
"scripts/verify-native-cli.mjs",
|
|
packageDirectory,
|
|
"--run",
|
|
]);
|
|
await command(process.execPath, ["scripts/smoke-native-install.mjs", packageDirectory]);
|
|
};
|
|
const amazon = () =>
|
|
command("docker", [
|
|
"run",
|
|
"--rm",
|
|
"--volume",
|
|
`${process.cwd()}:/work:ro`,
|
|
"--volume",
|
|
`${packageDirectory}:/native:ro`,
|
|
"--volume",
|
|
`${dirname(dirname(process.execPath))}:/node:ro`,
|
|
"--env",
|
|
"PATH=/node/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin",
|
|
"--workdir",
|
|
"/work",
|
|
"amazonlinux:2023",
|
|
"sh",
|
|
"-c",
|
|
'set -eu\ndnf install --assumeyes clang tar gzip\ntest "$(getconf GNU_LIBC_VERSION)" = "glibc 2.34"\nnode scripts/smoke-native-install.mjs /native',
|
|
]);
|
|
const results = await Promise.allSettled([verify(), amazon()]);
|
|
const failures = results
|
|
.filter((result) => result.status === "rejected")
|
|
.map((result) => result.reason);
|
|
if (failures.length) throw new AggregateError(failures, "native package checks failed");
|
|
},
|
|
});
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href)
|
|
await main();
|