Files
scriptc/packages/cli/test/bootstrap.test.ts
Chris Tate 2805c3ddb4 perf: lazy-load the compiler from the CLI (#189)
* perf: lazy-load the compiler from the CLI

- Serve help and version from a lightweight bootstrap and route exact executable cache hits before loading the compiler graph.
- Validate routed hits against compiler, frontend, toolchain, and native dependency proofs with portable cache publication.

* fix: repair lazy executable cache routes
2026-08-21 10:29:34 -05:00

88 lines
3.8 KiB
TypeScript

import { execFile } from "node:child_process";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { promisify } from "node:util";
import { expect, test } from "vitest";
const execFileAsync = promisify(execFile);
const repoRoot = join(import.meta.dirname, "../../..");
const bootstrap = join(repoRoot, "packages/cli/dist/bootstrap.js");
test("bootstrap serves version and help without loading the compiler graph", async () => {
const preloadDir = await mkdtemp(join(tmpdir(), "scriptc-bootstrap-preload-"));
const preload = join(preloadDir, "preload.mjs");
try {
await writeFile(preload, [
"import { registerHooks } from 'node:module';",
"registerHooks({ load(url, context, nextLoad) {",
" if (url.includes('/packages/compiler/dist/index.js')) throw new Error('compiler graph loaded');",
" return nextLoad(url, context);",
"}});",
"",
].join("\n"));
const version = await execFileAsync(process.execPath, ["--import", preload, bootstrap, "--version"]);
expect(version.stdout.trim()).toMatch(/^\d+\.\d+\.\d+$/);
const help = await execFileAsync(process.execPath, ["--import", preload, bootstrap, "--help"]);
expect(help.stdout).toContain("scriptc build <file.ts|.js>");
} finally {
await rm(preloadDir, { recursive: true, force: true }).catch(() => undefined);
}
});
test("bootstrap exact builds use the routed cache and source edits fall through", async () => {
const dir = await mkdtemp(join(tmpdir(), "scriptc-bootstrap-cache-"));
const cacheRoot = join(dir, "cache");
const entry = join(dir, "main.ts");
const outPath = join(dir, process.platform === "win32" ? "program.exe" : "program");
const preload = join(dir, "reject-full-compiler.mjs");
const env = { ...process.env, SCRIPTC_CACHE_DIR: cacheRoot, SCRIPTC_TIMING: "1" };
const build = (rejectFullCompiler = false): Promise<{ stderr: string }> =>
execFileAsync(process.execPath, [
...(rejectFullCompiler ? ["--import", preload] : []),
bootstrap,
"build",
entry,
"-o",
outPath,
], {
env,
maxBuffer: 4 * 1024 * 1024,
});
try {
await mkdir(cacheRoot, { mode: 0o700 });
await Promise.all([
writeFile(entry, 'console.log("one");\n'),
writeFile(preload, [
"import { registerHooks } from 'node:module';",
"registerHooks({ load(url, context, nextLoad) {",
" if (url.includes('/packages/compiler/dist/index.js')) throw new Error('compiler graph loaded');",
" return nextLoad(url, context);",
"}});",
"",
].join("\n")),
]);
expect((await build()).stderr).toContain("scriptc lowering");
expect((await build()).stderr).not.toContain("scriptc lowering");
expect((await execFileAsync(outPath)).stdout).toBe("one\n");
// Route metadata can be evicted independently of the executable payload.
// One full-compiler fallback must repair it so the following invocation is
// once again able to run with the package root import forbidden.
await Promise.all([
rm(join(cacheRoot, "early-exe-route"), { recursive: true, force: true }),
rm(join(cacheRoot, "early-exe-implementation"), { recursive: true, force: true }),
]);
expect((await build()).stderr).not.toContain("scriptc lowering");
await expect(build(true)).resolves.toMatchObject({ stderr: "" });
await writeFile(entry, 'console.log("two");\n');
expect((await build()).stderr).toContain("scriptc lowering");
expect((await build()).stderr).not.toContain("scriptc lowering");
expect((await execFileAsync(outPath)).stdout).toBe("two\n");
expect(await readFile(join(dir, "main.ll"), "utf8")).toContain("two");
} finally {
await rm(dir, { recursive: true, force: true });
}
}, 120_000);