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.
122 lines
4.5 KiB
TypeScript
122 lines
4.5 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import { createHash } from "node:crypto";
|
|
import { createRequire } from "node:module";
|
|
import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { release as osRelease, tmpdir } from "node:os";
|
|
import { dirname, join } from "node:path";
|
|
import { promisify } from "node:util";
|
|
import { afterEach, describe, expect, test } from "vitest";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const require = createRequire(import.meta.url);
|
|
const repoRoot = join(import.meta.dirname, "../../..");
|
|
const cliEntry = join(repoRoot, "packages/cli/src/main.ts");
|
|
const tsxLoader = join(dirname(require.resolve("tsx/package.json")), "dist/loader.mjs");
|
|
const supported =
|
|
process.platform === "darwin" &&
|
|
process.arch === "arm64" &&
|
|
Number.parseInt(osRelease().split(".", 1)[0] ?? "", 10) >= 24;
|
|
const dirs: string[] = [];
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
|
|
});
|
|
|
|
async function fixture() {
|
|
const dir = await mkdtemp(join(tmpdir(), "scriptc-cli-native-output-"));
|
|
dirs.push(dir);
|
|
const entry = join(dir, "hello.ts");
|
|
await writeFile(entry, 'console.log("native output");\n');
|
|
return { dir, entry };
|
|
}
|
|
|
|
function cli(args: string[], env: NodeJS.ProcessEnv = process.env) {
|
|
return execFileAsync(process.execPath, ["--import", tsxLoader, cliEntry, ...args], {
|
|
env,
|
|
maxBuffer: 4 * 1024 * 1024,
|
|
});
|
|
}
|
|
|
|
test("unsupported targets fail with SC3002 before creating an artifact", async () => {
|
|
const { dir, entry } = await fixture();
|
|
const output = join(dir, "hello.o");
|
|
await expect(
|
|
cli(["build", entry, "--emit=obj", "-o", output], {
|
|
...process.env,
|
|
SCRIPTC_TARGET: "scriptc-unsupported-target",
|
|
}),
|
|
).rejects.toMatchObject({
|
|
code: 1,
|
|
stderr: expect.stringContaining("SC3002"),
|
|
});
|
|
await expect(readFile(output)).rejects.toMatchObject({ code: "ENOENT" });
|
|
});
|
|
|
|
describe.runIf(supported)("macOS arm64 native outputs", () => {
|
|
test("default names and exact paths identify assembly and object artifacts", async () => {
|
|
const { dir, entry } = await fixture();
|
|
for (const [kind, name] of [
|
|
["asm", "hello.s"],
|
|
["obj", "hello.o"],
|
|
] as const) {
|
|
const result = await cli(["build", entry, `--emit=${kind}`]);
|
|
expect(result.stdout).toBe(`${join(dir, ".scriptc", name)}\n`);
|
|
}
|
|
const exact = join(dir, "exact.custom");
|
|
await cli(["build", entry, "--emit=obj", "-o", exact]);
|
|
expect((await readFile(exact)).subarray(0, 4)).toEqual(Buffer.from([0xcf, 0xfa, 0xed, 0xfe]));
|
|
});
|
|
|
|
test("asm and obj do not execute compiler, archiver, or linker traps", async () => {
|
|
const { dir, entry } = await fixture();
|
|
const traps = join(dir, "traps");
|
|
await execFileAsync("mkdir", [traps]);
|
|
const trapLog = join(dir, "trap.log");
|
|
for (const tool of ["clang", "cc", "gcc", "zig", "ar", "ld", "xcrun"]) {
|
|
const path = join(traps, tool);
|
|
await writeFile(path, `#!/bin/sh\nprintf '${tool}\\n' >> '${trapLog}'\nexit 97\n`);
|
|
await chmod(path, 0o755);
|
|
}
|
|
const env = {
|
|
...process.env,
|
|
PATH: `${traps}:${process.env["PATH"] ?? ""}`,
|
|
SCRIPTC_CACHE_DIR: join(dir, "cache"),
|
|
};
|
|
for (const kind of ["asm", "obj"] as const) {
|
|
await expect(
|
|
cli(["build", entry, `--emit=${kind}`, "-o", join(dir, `hello.${kind}`)], env),
|
|
).resolves.toMatchObject({ stderr: "" });
|
|
}
|
|
await expect(readFile(trapLog)).rejects.toMatchObject({ code: "ENOENT" });
|
|
});
|
|
|
|
test("identical object inputs have deterministic hashes", async () => {
|
|
const { dir, entry } = await fixture();
|
|
const first = join(dir, "first.o");
|
|
const second = join(dir, "second.o");
|
|
await cli(["build", entry, "--emit=obj", "-o", first], {
|
|
...process.env,
|
|
SCRIPTC_NO_CACHE: "1",
|
|
});
|
|
await cli(["build", entry, "--emit=obj", "-o", second], {
|
|
...process.env,
|
|
SCRIPTC_NO_CACHE: "1",
|
|
});
|
|
const digest = (path: string) =>
|
|
readFile(path).then((bytes) => createHash("sha256").update(bytes).digest("hex"));
|
|
expect(await digest(first)).toBe(await digest(second));
|
|
});
|
|
|
|
test("sanitize refuses by name without publishing an object", async () => {
|
|
const { dir, entry } = await fixture();
|
|
const output = join(dir, "san.o");
|
|
await expect(
|
|
cli(["build", entry, "--emit=obj", "--sanitize", "-o", output]),
|
|
).rejects.toMatchObject({
|
|
code: 1,
|
|
stderr: expect.stringMatching(/SC3002[\s\S]*AddressSanitizer/),
|
|
});
|
|
await expect(readFile(output)).rejects.toMatchObject({ code: "ENOENT" });
|
|
});
|
|
});
|