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.
114 lines
6.5 KiB
JavaScript
114 lines
6.5 KiB
JavaScript
import { constants, copyFileSync, chmodSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
export function nativeCliPackage(platform, architecture, libc) {
|
|
const host = `${platform}-${architecture}`;
|
|
if (host === "darwin-arm64" || host === "darwin-x64") return `@scriptc/cli-${host}`;
|
|
if (host === "win32-x64") return "@scriptc/cli-win32-x64-msvc";
|
|
if (host === "linux-x64" || host === "linux-arm64") {
|
|
if (libc !== "glibc" && libc !== "musl") throw new Error("could not identify the Linux C library");
|
|
return `@scriptc/cli-${host}-${libc === "musl" ? "musl" : "gnu"}`;
|
|
}
|
|
throw new Error(`scriptc has no native command for ${host}`);
|
|
}
|
|
|
|
function hostLibc() {
|
|
if (process.platform !== "linux") return null;
|
|
const report = process.report.getReport();
|
|
if (report.header.glibcVersionRuntime) return "glibc";
|
|
if (report.sharedObjects.some((path) => /(?:^|\/)ld-musl-|libc\.musl-/.test(path))) return "musl";
|
|
// A statically linked Node has no loaded libc in its process report.
|
|
// Only select musl when its loader is present for this architecture.
|
|
const arch = process.arch === "arm64" ? "aarch64" : "x86_64";
|
|
return existsSync(`/lib/ld-musl-${arch}.so.1`) ? "musl" : null;
|
|
}
|
|
|
|
export function relocateToolchain(manifest, sourceDirectory, destinationDirectory) {
|
|
const relocated = { ...manifest };
|
|
const pathFrom = (value) => relative(destinationDirectory, resolve(sourceDirectory, value));
|
|
for (const name of ["ts7", "llvm_package", "runtime_pack", "runtime_sources", "declarations", "comptime", "wasi_node_runner"]) {
|
|
if (typeof relocated[name] === "string") relocated[name] = pathFrom(relocated[name]);
|
|
}
|
|
for (const name of ["linker", "dsymutil", "archiver", "relocatable_linker"]) {
|
|
const value = relocated[name];
|
|
if (typeof value === "string" && !isAbsolute(value) && /[/\\]/.test(value)) relocated[name] = pathFrom(value);
|
|
}
|
|
if (relocated.runtime_packs) relocated.runtime_packs = relocated.runtime_packs.map((pack) => ({ ...pack, path: pathFrom(pack.path) }));
|
|
return relocated;
|
|
}
|
|
|
|
export function installNativeCli(directory, packageName = nativeCliPackage(process.platform, process.arch, hostLibc())) {
|
|
directory = realpathSync(directory);
|
|
const require = createRequire(join(directory, "package.json"));
|
|
const packageManifest = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
|
|
const version = packageManifest.version;
|
|
let platformManifest;
|
|
try { platformManifest = require.resolve(`${packageName}/package.json`); }
|
|
catch { throw new Error(`scriptc requires ${packageName}@${version}; reinstall with optional dependencies enabled`); }
|
|
const identity = JSON.parse(readFileSync(platformManifest, "utf8"));
|
|
if (identity.name !== packageName || identity.version !== version) throw new Error(`scriptc requires ${packageName}@${version}, found ${identity.version}`);
|
|
const sourceDirectory = join(dirname(platformManifest), "dist", "bin");
|
|
const source = join(sourceDirectory, process.platform === "win32" ? "scriptc.exe" : "scriptc");
|
|
const original = JSON.parse(readFileSync(source + ".json", "utf8"));
|
|
if (original.schema !== "scriptc.native-toolchain.v1" || original.compiler_version !== version) throw new Error("native compiler toolchain version does not match this installation");
|
|
const bin = join(directory, "bin");
|
|
mkdirSync(bin, { recursive: true });
|
|
const manifest = relocateToolchain(original, sourceDirectory, bin);
|
|
const packs = new Map((manifest.runtime_packs ?? []).map((pack) => [pack.target, pack]));
|
|
for (const suffix of ["darwin-arm64", "darwin-x64", "linux-x64-gnu", "linux-arm64-gnu", "linux-x64-musl", "linux-arm64-musl",
|
|
"win32-x64-msvc", "wasm32-wasi", "ios-arm64", "ios-simulator-arm64", "android-arm64"]) {
|
|
const name = `@scriptc/runtime-${suffix}`;
|
|
if (packageManifest.optionalDependencies?.[name] === undefined) continue;
|
|
let path;
|
|
try { path = require.resolve(`${name}/package.json`); }
|
|
catch { continue; }
|
|
const packIdentity = JSON.parse(readFileSync(path, "utf8"));
|
|
if (packIdentity.name !== name || packIdentity.version !== version) throw new Error(`scriptc requires ${name}@${version}`);
|
|
const pack = JSON.parse(readFileSync(join(dirname(path), "runtime-pack.json"), "utf8"));
|
|
if (pack.schema !== "scriptc.runtime-pack.v1" || pack.package !== name || pack.version !== version) {
|
|
throw new Error(`invalid runtime pack installed for ${name}@${version}`);
|
|
}
|
|
if (!packs.has(pack.target.name)) packs.set(pack.target.name, { target: pack.target.name, path: relative(bin, dirname(path)) });
|
|
}
|
|
manifest.runtime_packs = [...packs.values()];
|
|
// npm normalizes modes for payloads outside package bin entries. These
|
|
// tools are launched directly by the compiler after installation.
|
|
if (process.platform !== "win32") {
|
|
for (const path of [manifest.ts7, manifest.comptime, join(manifest.llvm_package, "bin/scriptc-llvm-codegen")]) {
|
|
chmodSync(resolve(bin, path), 0o755);
|
|
}
|
|
}
|
|
// The common filename lets npm's Windows shim invoke a native executable.
|
|
// POSIX bin links also execute this file directly, without a JS launcher.
|
|
const destination = join(bin, "scriptc.exe");
|
|
const staged = destination + `.install-${process.pid}`;
|
|
try {
|
|
copyFileSync(source, staged, constants.COPYFILE_FICLONE);
|
|
chmodSync(staged, 0o755);
|
|
writeFileSync(staged + ".json", JSON.stringify(manifest, null, 2) + "\n");
|
|
renameSync(staged + ".json", destination + ".json");
|
|
renameSync(staged, destination);
|
|
} finally {
|
|
rmSync(staged, { force: true });
|
|
rmSync(staged + ".json", { force: true });
|
|
}
|
|
return destination;
|
|
}
|
|
|
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
try {
|
|
const directory = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
const manifest = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
|
|
// A source checkout builds the native distribution separately. pnpm pack
|
|
// replaces workspace ranges with release versions before publication.
|
|
const workspace = Object.values(manifest.optionalDependencies ?? {}).some((value) => String(value).startsWith("workspace:"));
|
|
if (!workspace) installNativeCli(directory);
|
|
}
|
|
catch (error) {
|
|
process.stderr.write(`scriptc: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|