86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { writeFile } from "node:fs/promises";
|
|
import type { SerializedRoute } from "@suaveplan/api-types/sandbox-bridge";
|
|
import {
|
|
WASM_COMPILER_CODES,
|
|
WasmCompilerError,
|
|
} from "../error-codes/error-codes.js";
|
|
|
|
/** Inputs for {@link writeManifest}. */
|
|
export interface WriteManifestOptions {
|
|
/** Absolute path the manifest will be written to. */
|
|
readonly manifestPath: string;
|
|
/** Routes captured by the route-extract probe. */
|
|
readonly routes: readonly SerializedRoute[];
|
|
/** Pre-Javy bundled JS source — hashed into `sourceDigest`. */
|
|
readonly bundledJs: string;
|
|
/** Upstream Javy version pinned by the wrapper that compiled. */
|
|
readonly javyVersion: string;
|
|
/** This compiler's package version. */
|
|
readonly compilerVersion: string;
|
|
/** RFC 3339 UTC timestamp; defaults to `new Date().toISOString()`. */
|
|
readonly compiledAt?: string;
|
|
/**
|
|
* Caller-supplied filesystem writer — defaults to
|
|
* `node:fs/promises#writeFile`. Tests inject a recorder.
|
|
*/
|
|
readonly fsWrite?: (path: string, data: string) => Promise<void>;
|
|
}
|
|
|
|
/** The full manifest record shape. */
|
|
export interface CompiledPluginManifest {
|
|
readonly routes: readonly SerializedRoute[];
|
|
readonly abiVersion: string;
|
|
readonly abiVariant: "wasi-stdio";
|
|
readonly javyVersion: string;
|
|
readonly compilerVersion: string;
|
|
readonly compiledAt: string;
|
|
readonly sourceDigest: string;
|
|
}
|
|
|
|
/** The current compiler ABI version — semver string. */
|
|
export const COMPILER_ABI_VERSION = "1.0.0" as const;
|
|
|
|
/**
|
|
* Emit the `<basename>.wasm.json` sidecar. Refuses empty `routes`
|
|
* with `WASM_COMPILER.MANIFEST_MISSING_ROUTES` — a routeless plugin
|
|
* cannot route a single request through the bridge, so shipping the
|
|
* artifact would be pointless.
|
|
*
|
|
* Returns the manifest record so the orchestrator can include it in
|
|
* the compile result without re-reading from disk.
|
|
*/
|
|
export async function writeManifest(
|
|
options: WriteManifestOptions
|
|
): Promise<CompiledPluginManifest> {
|
|
if (options.routes.length === 0) {
|
|
throw new WasmCompilerError(
|
|
WASM_COMPILER_CODES.MANIFEST_MISSING_ROUTES,
|
|
"error.wasm_compiler.manifest_missing_routes",
|
|
{
|
|
context: { manifestPath: options.manifestPath },
|
|
}
|
|
);
|
|
}
|
|
const sourceDigest = createHash("sha256")
|
|
.update(options.bundledJs)
|
|
.digest("hex");
|
|
const compiledAt = options.compiledAt ?? new Date().toISOString();
|
|
const manifest: CompiledPluginManifest = {
|
|
routes: options.routes,
|
|
abiVersion: COMPILER_ABI_VERSION,
|
|
abiVariant: "wasi-stdio",
|
|
javyVersion: options.javyVersion,
|
|
compilerVersion: options.compilerVersion,
|
|
compiledAt,
|
|
sourceDigest,
|
|
};
|
|
const writer = options.fsWrite ?? defaultWriter;
|
|
await writer(options.manifestPath, JSON.stringify(manifest, null, 2));
|
|
return manifest;
|
|
}
|
|
|
|
async function defaultWriter(path: string, data: string): Promise<void> {
|
|
await writeFile(path, data, "utf8");
|
|
}
|