openclaw/scripts/lib/native-declaration-subprocess.mts
Dallin Romney 17a8d22268
fix(plugin-sdk): emit revision declarations with their own compiler (#181908)
* fix(plugin-sdk): render shipped surfaces with revision tooling

Co-authored-by: RomneyDa <6581799+RomneyDa@users.noreply.github.com>

* fix(plugin-sdk): drop stale renderer argument

Co-authored-by: RomneyDa <6581799+RomneyDa@users.noreply.github.com>

* fix(release): keep SDK renderer trusted

Co-authored-by: RomneyDa <6581799+RomneyDa@users.noreply.github.com>

* fix(release): render SDK revisions with Node under Bun

Co-authored-by: RomneyDa <6581799+RomneyDa@users.noreply.github.com>

* fix(plugin-sdk): emit revision declarations with their own compiler

* fix(build): declare the emitter's compiler API edge as a declaration input

---------

Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: RomneyDa <6581799+RomneyDa@users.noreply.github.com>
2026-10-23 23:36:11 -07:00

144 lines
5.2 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { deserialize, serialize } from "node:v8";
import {
resolveRuntimeWorkerArgv,
resolveRuntimeWorkerUrl,
} from "../../src/infra/runtime-worker-url.js";
import { hasUnjoinedWork, runManagedCommand } from "./managed-child-process.mts";
import { emitNativeDeclarations, type NativeDeclaration } from "./native-declaration-emitter.mts";
import { isRecord } from "./record-shared.mjs";
type DeclarationRequest = Pick<
Parameters<typeof emitNativeDeclarations>[0],
"cwd" | "configFile" | "roots" | "compilerOptions" | "diagnostics" | "compilerRoot"
>;
function readRequest(file: string): DeclarationRequest {
const value: unknown = JSON.parse(fs.readFileSync(file, "utf8"));
if (
!isRecord(value) ||
typeof value.cwd !== "string" ||
typeof value.configFile !== "string" ||
!Array.isArray(value.roots) ||
!value.roots.every((root): root is string => typeof root === "string") ||
(value.compilerOptions !== undefined && !isRecord(value.compilerOptions)) ||
(value.diagnostics !== undefined &&
value.diagnostics !== "declarations" &&
value.diagnostics !== "all") ||
(value.compilerRoot !== undefined && typeof value.compilerRoot !== "string")
) {
throw new Error("Invalid native declaration subprocess request");
}
return {
cwd: value.cwd,
configFile: value.configFile,
roots: value.roots,
compilerOptions: value.compilerOptions,
diagnostics: value.diagnostics,
compilerRoot: value.compilerRoot,
};
}
function isDeclaration(value: unknown): value is NativeDeclaration {
if (!isRecord(value) || typeof value.code !== "string" || !isRecord(value.map)) {
return false;
}
const map = value.map;
return (
map.version === 3 &&
typeof map.file === "string" &&
typeof map.mappings === "string" &&
Array.isArray(map.sources) &&
map.sources.every((source) => typeof source === "string") &&
Array.isArray(map.names) &&
map.names.every((name) => typeof name === "string")
);
}
function readResult(file: string): Awaited<ReturnType<typeof emitNativeDeclarations>> {
const value: unknown = deserialize(fs.readFileSync(file));
if (
!isRecord(value) ||
!Array.isArray(value.inputs) ||
!value.inputs.every((input): input is string => typeof input === "string") ||
!(value.declarations instanceof Map)
) {
throw new Error("Invalid native declaration subprocess result");
}
const declarations = new Map<string, NativeDeclaration>();
for (const [source, declaration] of value.declarations) {
if (typeof source !== "string" || !isDeclaration(declaration)) {
throw new Error("Invalid native declaration subprocess output");
}
declarations.set(source, declaration);
}
return { inputs: value.inputs, declarations };
}
/** Join native emission before synchronous semantic work can prevent its shutdown. */
export async function emitNativeDeclarationsInSubprocess(options: DeclarationRequest) {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-native-declarations-"));
const request = path.join(directory, "request.json");
let joined = true;
try {
fs.writeFileSync(request, JSON.stringify(options));
const worker = resolveRuntimeWorkerUrl({
currentModuleUrl: import.meta.url,
sourceWorkerName: "native-declaration-subprocess",
distWorkerPath: "legacy-finalizer/scripts/lib/native-declaration-subprocess.js",
});
const heapArgs = process.execArgv.flatMap((arg, index, args) =>
/^--max[-_]old[-_]space[-_]size=/.test(arg)
? [arg]
: /^--max[-_]old[-_]space[-_]size$/.test(arg)
? [arg, args[index + 1]!]
: [],
);
let errorOutput = "";
const code = await runManagedCommand({
bin: process.execPath,
args: [...heapArgs, ...resolveRuntimeWorkerArgv(worker), request],
cwd: options.cwd,
shell: false,
stdio: ["ignore", "ignore", "pipe"],
requireProcessTreeExit: process.platform !== "win32",
onReady(child) {
child.stderr!.setEncoding("utf8");
child.stderr!.on("data", (chunk: string) => {
errorOutput += chunk.slice(0, Math.max(0, 64 * 1024 - errorOutput.length));
});
},
});
if (code !== 0) {
throw new Error(
errorOutput.trim() || `Native declaration subprocess exited with code ${code}`,
);
}
return readResult(path.join(directory, "result.bin"));
} catch (error) {
joined = !hasUnjoinedWork(error);
throw error;
} finally {
if (joined) {
fs.rmSync(directory, { recursive: true, force: true });
}
}
}
if (import.meta.main) {
const request = process.argv[2];
if (!request || process.argv.length !== 3) {
throw new Error("Expected one native declaration request path");
}
try {
const result = await emitNativeDeclarations(readRequest(request));
fs.writeFileSync(path.join(path.dirname(request), "result.bin"), serialize(result));
} catch (error) {
// API.close() only ends the compiler's stdin. An uncaught failure would exit
// before that shutdown completes; draining the event loop joins the compiler.
console.error(error);
process.exitCode = 1;
}
}