mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-30 07:14:39 +08:00
Each @scriptc/cli-<platform> package bundled its platform's LLVM helper and runtime pack, which are also published as @scriptc/llvm-<platform> and @scriptc/runtime-<platform>. The CLI packages now depend on those packages at the same version (workspace:*, which pnpm pack writes as the exact version) and leave them out: cli-linux-x64-gnu goes from 115 MB to 60 MB packed. - build-native-cli.mts --platform-packages, which each CLI package's build:native passes, writes a manifest without llvm_package, runtime_pack and runtime_packs. - loadNativeToolchain resolves a missing helper or host runtime pack as Node resolves the package from the compiler's directory: nested in the platform package, beside it (hoisted, or pnpm's virtual store) or higher up. It requires the compiler's version, and an absent or mismatched package fails with an error that names it. - installNativeCli records where the package manager put both packages in the installed manifest, with the same checks. - verify-native-cli.mjs requires the exact dependencies of a platform package and checks the resolved packages' versions. - package-native-cli.mjs bundles the helper and runtime pack into the standalone archives, which stay self-contained. - smoke-native-install.mjs installs the helper and runtime pack tarballs with the platform package; the bootstrap's exported package drops its bundled copies first. - release.yml publishes the CLI packages only once every helper and runtime pack package resolves on npm, and scriptc only once the CLI packages resolve, each wait at most 25 minutes. A rerun skips the uploads npm already accepted and waits again.
138 lines
6.7 KiB
JavaScript
138 lines
6.7 KiB
JavaScript
import { execFileSync } from "node:child_process";
|
|
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
import { dirname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
|
|
import { verifyLinuxAbi } from "./verify-linux-abi.mjs";
|
|
|
|
const root = realpathSync(resolve(process.argv[2] ?? "."));
|
|
const identity = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
const target = identity.name.replace("@scriptc/cli-", "");
|
|
const windows = target.startsWith("win32-");
|
|
const hostPath = (value) => (windows ? value.replaceAll("\\", "/") : value);
|
|
const absolutePath = (value) => isAbsolute(value) || (windows && win32.parse(value).root !== "");
|
|
const distribution = join(root, "dist");
|
|
const binary = join(distribution, "bin", target.startsWith("win32-") ? "scriptc.exe" : "scriptc");
|
|
const toolchain = JSON.parse(readFileSync(binary + ".json", "utf8"));
|
|
if (
|
|
toolchain.schema !== "scriptc.native-toolchain.v1" ||
|
|
toolchain.compiler_version !== identity.version
|
|
) {
|
|
throw new Error("native CLI manifest version does not match the package");
|
|
}
|
|
if (statSync(binary).size < 1024) throw new Error("native compiler payload is missing");
|
|
if (!target.startsWith("win32-") && !(statSync(binary).mode & 0o111))
|
|
throw new Error("native compiler is not executable");
|
|
const within = (path) => {
|
|
const child = relative(distribution, realpathSync(path));
|
|
if (child === ".." || child.startsWith(".." + sep) || isAbsolute(child))
|
|
throw new Error(`distribution references external asset: ${path}`);
|
|
};
|
|
// An npm platform package depends on its LLVM helper and runtime pack
|
|
// packages at its own version instead of bundling them.
|
|
const platformPackages = toolchain.llvm_package === undefined;
|
|
const dependencies = identity.dependencies ?? {};
|
|
const dependency = (prefix) => {
|
|
const name = Object.keys(dependencies).find((key) => key.startsWith(prefix));
|
|
if (name === undefined || ![identity.version, "workspace:*"].includes(dependencies[name]))
|
|
throw new Error(
|
|
`${identity.name} must depend on its ${prefix}* package at ${identity.version}`,
|
|
);
|
|
return dirname(createRequire(join(root, "package.json")).resolve(`${name}/package.json`));
|
|
};
|
|
if (platformPackages && (toolchain.runtime_pack !== undefined || toolchain.runtime_packs))
|
|
throw new Error("a platform package's runtime pack is its dependency");
|
|
const helperRoot = platformPackages
|
|
? dependency("@scriptc/llvm-")
|
|
: resolve(dirname(binary), hostPath(toolchain.llvm_package));
|
|
const runtimeRoot = platformPackages
|
|
? dependency("@scriptc/runtime-")
|
|
: resolve(dirname(binary), hostPath(toolchain.runtime_pack));
|
|
for (const name of [
|
|
"ts7",
|
|
"tsc_ts",
|
|
...(platformPackages ? [] : ["llvm_package", "runtime_pack"]),
|
|
"runtime_sources",
|
|
"declarations",
|
|
"comptime",
|
|
"wasi_node_runner",
|
|
]) {
|
|
const value = toolchain[name];
|
|
if (typeof value !== "string" || absolutePath(value))
|
|
throw new Error(`native toolchain needs a relative ${name}`);
|
|
within(resolve(dirname(binary), hostPath(value)));
|
|
}
|
|
// Optional: a compiler this host could not run while building ships without it.
|
|
if (toolchain.lib_diagnostics !== undefined) {
|
|
const value = toolchain.lib_diagnostics;
|
|
if (typeof value !== "string" || absolutePath(value))
|
|
throw new Error("native toolchain needs a relative lib_diagnostics");
|
|
within(resolve(dirname(binary), hostPath(value)));
|
|
}
|
|
const pack = JSON.parse(readFileSync(join(runtimeRoot, "runtime-pack.json"), "utf8"));
|
|
if (pack.version !== identity.version || pack.target.name !== toolchain.target)
|
|
throw new Error("native compiler runtime identity mismatch");
|
|
if (JSON.parse(readFileSync(join(helperRoot, "package.json"), "utf8")).version !== identity.version)
|
|
throw new Error("native compiler LLVM helper version mismatch");
|
|
const seen = new Set();
|
|
for (const runtime of toolchain.runtime_packs ?? []) {
|
|
if (seen.has(runtime.target)) throw new Error(`duplicate runtime target: ${runtime.target}`);
|
|
seen.add(runtime.target);
|
|
if (typeof runtime.path !== "string" || absolutePath(runtime.path))
|
|
throw new Error("native runtime pack paths must be relative");
|
|
const directory = resolve(dirname(binary), hostPath(runtime.path));
|
|
within(directory);
|
|
const manifest = JSON.parse(readFileSync(join(directory, "runtime-pack.json"), "utf8"));
|
|
const packageIdentity = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
|
|
if (
|
|
manifest.schema !== "scriptc.runtime-pack.v1" ||
|
|
manifest.target.name !== runtime.target ||
|
|
manifest.version !== identity.version ||
|
|
packageIdentity.version !== identity.version ||
|
|
packageIdentity.name !== manifest.package
|
|
)
|
|
throw new Error(`native runtime pack identity mismatch: ${runtime.target}`);
|
|
}
|
|
for (const name of ["LICENSE", "THIRD_PARTY_NOTICES"]) {
|
|
if (!existsSync(join(distribution, name))) throw new Error(`native compiler is missing ${name}`);
|
|
}
|
|
for (const directory of [distribution, join(distribution, "lib/runtime-sources/vendor")]) {
|
|
if (readdirSync(directory).some((name) => ["node_modules", ".cache", "seed"].includes(name)))
|
|
throw new Error(`development files present in ${directory}`);
|
|
}
|
|
if (process.argv.includes("--run")) {
|
|
if (target.startsWith("linux-")) {
|
|
for (const executable of [
|
|
binary,
|
|
resolve(dirname(binary), toolchain.ts7),
|
|
resolve(dirname(binary), toolchain.comptime),
|
|
]) {
|
|
verifyLinuxAbi(executable, { staticOnly: target.endsWith("-musl") });
|
|
}
|
|
verifyLinuxAbi(join(helperRoot, "bin/scriptc-llvm-codegen"), { staticOnly: true });
|
|
}
|
|
const version = execFileSync(binary, ["--version"], {
|
|
encoding: "utf8",
|
|
env: { ...process.env, PATH: "" },
|
|
}).trim();
|
|
if (version !== identity.version)
|
|
throw new Error(`native command reported ${version}, expected ${identity.version}`);
|
|
// tsc-ts, the default checker, runs on the distribution's own platform.
|
|
const tscTs = resolve(dirname(binary), hostPath(toolchain.tsc_ts));
|
|
if (windows && !tscTs.endsWith("tsc-ts.exe"))
|
|
throw new Error(`tsc_ts is not tsc-ts.exe: ${tscTs}`);
|
|
const tscTsVersion = execFileSync(tscTs, ["--version"], {
|
|
encoding: "utf8",
|
|
env: { ...process.env, PATH: "" },
|
|
}).trim();
|
|
if (!tscTsVersion.startsWith("Version "))
|
|
throw new Error(`the distribution's tsc-ts reported ${JSON.stringify(tscTsVersion)}`);
|
|
// The release strips tsc-ts and the comptime evaluator; arm64 macOS runs
|
|
// only executables whose linker-generated code signature matches their
|
|
// contents.
|
|
if (process.platform === "darwin" && process.arch === "arm64") {
|
|
for (const executable of [binary, tscTs, resolve(dirname(binary), toolchain.comptime)])
|
|
execFileSync("codesign", ["--verify", "--strict", executable], { stdio: "inherit" });
|
|
}
|
|
}
|
|
console.log(`${identity.name}@${identity.version}: native distribution verified`);
|