mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-02 08:35:07 +08:00
* feat: ship the self-hosted native scriptc CLI - Run the shared compiler and CLI natively, including library builds and compile-time evaluation. - Install native platform packages and ship relocatable standalone distributions. - Preserve program semantics and reuse validated build artifacts for fast rebuilds. * fix: complete native CLI integration and validation - Preserve library diagnostics and run recursive compiler work on the main stack. - Restore packaged helper permissions and exercise native npm and Wasm library builds. - Recover interrupted Sandbox status probes within the original command deadline.
132 lines
7.6 KiB
TypeScript
132 lines
7.6 KiB
TypeScript
import { execFileSync } from "node:child_process";
|
|
import { copyFileSync, mkdtempSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { afterEach, expect, test } from "vitest";
|
|
import { installNativeCli, nativeCliPackage } from "../scripts/install-native.mjs";
|
|
import { prepareNativeCommand } from "../scripts/prepare-native.mjs";
|
|
|
|
const roots: string[] = [];
|
|
afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); });
|
|
|
|
function fixture() {
|
|
const root = mkdtempSync(join(process.platform === "win32" ? tmpdir() : "/tmp", "scriptc-install-"));
|
|
roots.push(root);
|
|
const directory = join(root, "installation");
|
|
const packageName = "@scriptc/cli-darwin-arm64";
|
|
const platform = join(directory, "node_modules", packageName);
|
|
const bin = join(platform, "dist/bin");
|
|
mkdirSync(bin, { recursive: true });
|
|
writeFileSync(join(directory, "package.json"), JSON.stringify({ name: "scriptc", version: "1.2.3" }));
|
|
writeFileSync(join(platform, "package.json"), JSON.stringify({ name: packageName, version: "1.2.3" }));
|
|
const binary = join(bin, process.platform === "win32" ? "scriptc.exe" : "scriptc");
|
|
writeFileSync(binary, "native compiler payload");
|
|
const toolchain = {
|
|
schema: "scriptc.native-toolchain.v1", compiler_version: "1.2.3", target: "macos-arm64",
|
|
ts7: "../lib/typescript/lib/tsc", llvm_package: "../lib/llvm", runtime_pack: "../lib/runtime",
|
|
runtime_packs: [{ target: "macos-arm64", path: "../lib/runtime" }],
|
|
linker: "clang", linker_args: [], dsymutil: "dsymutil", comptime: "../lib/comptime",
|
|
wasi_node_runner: "../lib/wasi/cli/wasi-runner.js",
|
|
};
|
|
writeFileSync(binary + ".json", JSON.stringify(toolchain));
|
|
const helpers = [toolchain.ts7, toolchain.comptime, join(toolchain.llvm_package, "bin/scriptc-llvm-codegen")]
|
|
.map((path) => resolve(bin, path));
|
|
for (const path of helpers) {
|
|
mkdirSync(dirname(path), { recursive: true });
|
|
writeFileSync(path, "native helper payload", { mode: 0o644 });
|
|
}
|
|
return { root, directory, packageName, platform, binary, toolchain, helpers };
|
|
}
|
|
|
|
test("selects only supported native host packages, including Linux libc", () => {
|
|
expect(nativeCliPackage("darwin", "arm64", null)).toBe("@scriptc/cli-darwin-arm64");
|
|
expect(nativeCliPackage("win32", "x64", null)).toBe("@scriptc/cli-win32-x64-msvc");
|
|
expect(nativeCliPackage("linux", "x64", "glibc")).toBe("@scriptc/cli-linux-x64-gnu");
|
|
expect(nativeCliPackage("linux", "arm64", "musl")).toBe("@scriptc/cli-linux-arm64-musl");
|
|
expect(() => nativeCliPackage("linux", "x64", null)).toThrow("identify");
|
|
expect(() => nativeCliPackage("win32", "arm64", null)).toThrow("no native command");
|
|
});
|
|
|
|
test("installs a direct executable and relocatable references to platform assets", () => {
|
|
const f = fixture();
|
|
const command = installNativeCli(f.directory, f.packageName);
|
|
expect(readFileSync(command, "utf8")).toBe("native compiler payload");
|
|
if (process.platform !== "win32") expect(statSync(command).mode & 0o111).toBe(0o111);
|
|
if (process.platform !== "win32") {
|
|
for (const helper of f.helpers) expect(statSync(helper).mode & 0o111).toBe(0o111);
|
|
}
|
|
const moved = join(f.root, "relocated");
|
|
renameSync(f.directory, moved);
|
|
const installed = join(moved, "bin/scriptc.exe");
|
|
const manifest = JSON.parse(readFileSync(installed + ".json", "utf8"));
|
|
const platform = join(moved, "node_modules", f.packageName, "dist/lib");
|
|
expect(resolve(dirname(installed), manifest.ts7)).toBe(join(platform, "typescript/lib/tsc"));
|
|
expect(resolve(dirname(installed), manifest.wasi_node_runner)).toBe(join(platform, "wasi/cli/wasi-runner.js"));
|
|
expect(resolve(dirname(installed), manifest.runtime_packs[0].path)).toBe(join(platform, "runtime"));
|
|
expect(manifest.linker).toBe("clang");
|
|
expect(JSON.stringify(manifest)).not.toContain(f.directory);
|
|
});
|
|
|
|
test("missing and mismatched packages fail without replacing an installed command", () => {
|
|
const f = fixture();
|
|
const command = installNativeCli(f.directory, f.packageName);
|
|
writeFileSync(join(f.platform, "package.json"), JSON.stringify({ name: f.packageName, version: "1.2.4" }));
|
|
expect(() => installNativeCli(f.directory, f.packageName)).toThrow("found 1.2.4");
|
|
expect(() => installNativeCli(f.directory, "@scriptc/cli-missing")).toThrow("optional dependencies");
|
|
expect(readFileSync(command, "utf8")).toBe("native compiler payload");
|
|
});
|
|
|
|
test("npm links the installed native executable without an interpreter", () => {
|
|
const f = fixture();
|
|
// Keep the package payload small while exercising npm's native shim.
|
|
// Production command coverage builds the real compiler in the bootstrap gate.
|
|
const windows = process.platform === "win32";
|
|
if (windows) copyFileSync(process.execPath, f.binary);
|
|
else {
|
|
const source = join(f.root, "payload.c");
|
|
writeFileSync(source, '#include <stdio.h>\nint main(void) { puts("native compiler"); return 0; }\n');
|
|
execFileSync("clang", [source, "-o", f.binary]);
|
|
}
|
|
for (const helper of f.helpers) writeFileSync(helper, "#!/bin/sh\nprintf 'native helper\\n'\n");
|
|
const packageName = "scriptc-native-install-test";
|
|
const scripts = join(f.directory, "scripts");
|
|
mkdirSync(scripts);
|
|
copyFileSync(join(import.meta.dirname, "../scripts/install-native.mjs"), join(scripts, "install-native.mjs"));
|
|
writeFileSync(join(scripts, "fixture-install.mjs"),
|
|
`import { installNativeCli } from './install-native.mjs'; installNativeCli(process.cwd(), ${JSON.stringify(f.packageName)});\n`);
|
|
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
const npmEnv = { ...process.env, NODE_PATH: "", npm_config_cache: join(f.root, "npm-cache"), npm_config_update_notifier: "false" };
|
|
const pack = (directory: string): string => {
|
|
const result = execFileSync(npm, ["pack", "--json", "--ignore-scripts"], {
|
|
cwd: directory, env: npmEnv, encoding: "utf8", shell: process.platform === "win32",
|
|
});
|
|
return join(directory, (JSON.parse(result) as { filename: string }[])[0]!.filename);
|
|
};
|
|
const platformTarball = pack(f.platform);
|
|
writeFileSync(join(f.directory, "package.json"), JSON.stringify({
|
|
name: packageName, version: "1.2.3", type: "module",
|
|
bin: { [packageName]: "bin/scriptc.exe" }, files: ["bin", "scripts"],
|
|
scripts: { postinstall: "node scripts/fixture-install.mjs" },
|
|
optionalDependencies: { [f.packageName]: "file:" + platformTarball },
|
|
}));
|
|
prepareNativeCommand(f.directory);
|
|
const tarball = pack(f.directory);
|
|
const installed = join(f.root, "npm-install");
|
|
mkdirSync(installed);
|
|
execFileSync(npm, ["install", "--offline", "--no-audit", "--no-fund", tarball], {
|
|
cwd: installed, env: npmEnv, shell: process.platform === "win32", stdio: "pipe",
|
|
});
|
|
const command = join(installed, "node_modules/.bin", packageName + (process.platform === "win32" ? ".cmd" : ""));
|
|
const result = execFileSync(command, windows ? ["--version"] : ["native compiler"], {
|
|
env: { ...process.env, PATH: "" }, encoding: "utf8", shell: process.platform === "win32",
|
|
});
|
|
expect(result.trim()).toBe(windows ? process.version : "native compiler");
|
|
if (process.platform !== "win32") {
|
|
const bin = join(installed, "node_modules", packageName, "bin");
|
|
const manifest = JSON.parse(readFileSync(join(bin, "scriptc.exe.json"), "utf8"));
|
|
for (const helper of [manifest.ts7, manifest.comptime, join(manifest.llvm_package, "bin/scriptc-llvm-codegen")]) {
|
|
expect(execFileSync(resolve(bin, helper), [], { env: { ...process.env, PATH: "" }, encoding: "utf8" }).trim()).toBe("native helper");
|
|
}
|
|
}
|
|
}, 60_000);
|