mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
The native toolchain manifest's lib_diagnostics path is relative to the platform package's dist/bin. installNativeCli copies the manifest into the scriptc package's bin and relocates each asset path, but lib_diagnostics was not in the list, so it pointed at a missing file under the scriptc package and npm installs did not use the prebuilt lib diagnostics. It is now relocated with the other assets.
214 lines
8.1 KiB
TypeScript
214 lines
8.1 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",
|
|
lib_diagnostics: "../lib/lib-diagnostics.json",
|
|
};
|
|
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.lib_diagnostics)).toBe(
|
|
join(platform, "lib-diagnostics.json"),
|
|
);
|
|
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);
|