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 "); } 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);