mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Add the @scriptc/libnode-<platform> packs (linux-x64-gnu, linux-arm64-gnu, darwin-arm64, darwin-x64): the pinned Node.js 24.22.0 built as a stripped shared library with full ICU and its startup snapshot, the scriptc bridge compiled against that release's headers, Node's LICENSE and a manifest with the Node version, NODE_MODULE_VERSION, bridge ABI and file hashes. packages/libnode-common holds the pin, the build (fetch and verify the source tarball, configure --shared, build only the libnode target, strip, stage with headers in a cache keyed by release, flags, platform and toolchain; the bridge rebuilds in seconds against the cached headers), a verifier, and a smoke test: a C program that loads a pack the way the runtime does, starts Node from the snapshot and evaluates expressions synchronously, asynchronously and throwing. The libnode-packs workflow builds the packs on native runners, Linux in the manylinux_2_28 images for a glibc 2.28 floor, caches each libnode build, smoke-tests and uploads every pack. It never runs in the ordinary build or test path. The compiler gains the pack lookup (SCRIPTC_LIBNODE, then the installed package), validation, the nodeIslands.libnode placements and the license notices for the Node island loader to use. The bridge source and its C ABI header are the NI-1 bridge agent's, included here so the packs build; the packs stay private until the in-process island runtime ships. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
91 lines
3.5 KiB
TypeScript
91 lines
3.5 KiB
TypeScript
import { readFileSync, readdirSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { describe, expect, test } from "vitest";
|
|
import {
|
|
LIBNODE_PLATFORMS,
|
|
bridgeAbi,
|
|
bridgeHash,
|
|
libnodeCacheKey,
|
|
packFileNames,
|
|
readPin,
|
|
repoRoot,
|
|
} from "../scripts/pin.mjs";
|
|
import { expectedTranscript } from "../scripts/smoke.mjs";
|
|
|
|
const readJson = (path: string) => JSON.parse(readFileSync(join(repoRoot, path), "utf8"));
|
|
|
|
describe("the libnode pin", () => {
|
|
test("is the repository's pinned Node release", () => {
|
|
const pin = readPin();
|
|
expect(pin.nodeVersion).toBe(readJson("internal/compatibility/node-v24.json").version);
|
|
expect(pin.nodeVersion).toBe(readFileSync(join(repoRoot, ".node-version"), "utf8").trim());
|
|
expect(pin.tarballSha256).toMatch(/^[0-9a-f]{64}$/);
|
|
});
|
|
|
|
test("builds the shared library with full ICU (design §8 question 2)", () => {
|
|
const pin = readPin();
|
|
expect(pin.configureFlags).toContain("--shared");
|
|
expect(pin.configureFlags.some((flag: string) => flag.startsWith("--with-intl"))).toBe(false);
|
|
expect(pin.icu).toBe("full");
|
|
});
|
|
});
|
|
|
|
describe("the platform packages", () => {
|
|
const dirs = readdirSync(join(repoRoot, "packages")).filter(
|
|
(name) => name.startsWith("libnode-") && name !== "libnode-common",
|
|
);
|
|
|
|
test("match the platform table", () => {
|
|
expect(dirs.map((dir) => dir.slice("libnode-".length)).sort()).toEqual(
|
|
Object.keys(LIBNODE_PLATFORMS).sort(),
|
|
);
|
|
});
|
|
|
|
test.each(Object.entries(LIBNODE_PLATFORMS))("%s ships its libraries", (platform, info) => {
|
|
const manifest = readJson(`packages/libnode-${platform}/package.json`);
|
|
const names = packFileNames(platform, readPin().moduleVersion);
|
|
expect(manifest.name).toBe(`@scriptc/libnode-${platform}`);
|
|
expect(manifest.version).toBe(readJson("packages/cli/package.json").version);
|
|
expect(manifest.os).toEqual([info.os]);
|
|
expect(manifest.cpu).toEqual([info.arch]);
|
|
expect(manifest.files).toEqual([
|
|
names.libnode,
|
|
names.bridge,
|
|
"manifest.json",
|
|
"LICENSE",
|
|
"SCRIPTC_LICENSE",
|
|
"THIRD_PARTY_NOTICES",
|
|
]);
|
|
// Never built by `pnpm -r build`.
|
|
expect(manifest.scripts.build).toBe("true");
|
|
expect(manifest.scripts["build:native"]).toBe("node scripts/build.mjs");
|
|
});
|
|
});
|
|
|
|
describe("cache identity", () => {
|
|
test("the libnode key depends on platform and toolchain, not on the bridge", () => {
|
|
const a = libnodeCacheKey("linux-x64-gnu", "manylinux_2_28-gcc-toolset-14");
|
|
expect(a).toMatch(/^[0-9a-f]{64}$/);
|
|
expect(libnodeCacheKey("linux-x64-gnu", "manylinux_2_28-gcc-toolset-14")).toBe(a);
|
|
expect(libnodeCacheKey("linux-arm64-gnu", "manylinux_2_28-gcc-toolset-14")).not.toBe(a);
|
|
expect(libnodeCacheKey("linux-x64-gnu", "other")).not.toBe(a);
|
|
expect(bridgeHash()).toMatch(/^[0-9a-f]{64}$/);
|
|
});
|
|
|
|
test("unknown platforms are refused", () => {
|
|
expect(() => libnodeCacheKey("linux-x64-musl", "x")).toThrow(
|
|
"unknown libnode pack platform linux-x64-musl",
|
|
);
|
|
});
|
|
});
|
|
|
|
test("the smoke transcript pins the Node version, the snapshot and full ICU", () => {
|
|
const lines = expectedTranscript().split("\n");
|
|
expect(lines[0]).toBe(
|
|
`bridge ABI ${bridgeAbi()}, Node ${readPin().nodeVersion}, NODE_MODULE_VERSION ${readPin().moduleVersion}`,
|
|
);
|
|
expect(lines).toContain("process.config.variables.node_use_node_snapshot => Rtrue");
|
|
expect(lines).toContain(`new Intl.NumberFormat('de-DE').format(1234567.891) => R"1.234.567,891"`);
|
|
expect(lines.at(-2)).toBe("ok");
|
|
});
|