openclaw/test/scripts/check-built-plugin-control-plane-modules.test.ts

299 lines
11 KiB
TypeScript

// Built plugin control-plane module checks cover native require(esm) acceptance.
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
collectBuiltDoctorContractClosureViolations,
listBuiltPluginControlPlaneModules,
probeBuiltPluginControlPlaneModules,
verifyBuiltPluginControlPlaneModules,
} from "../../scripts/check-built-plugin-control-plane-modules.mts";
import {
resolveRuntimeWorkerArgv,
resolveRuntimeWorkerUrl,
} from "../../src/infra/runtime-worker-url.js";
import { resolveTestNodeExecPath } from "../../src/test-utils/node-process.js";
import { toolingMtsEntrypoints } from "./tooling-mts-runtime.test-support.mts";
const roots: string[] = [];
const testNodeExecPath = resolveTestNodeExecPath();
function makeRoot(extension = ".js"): string {
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-control-plane-"));
roots.push(rootDir);
fs.writeFileSync(path.join(rootDir, "package.json"), '{"type":"module"}\n');
if (extension === ".cjs") {
write(rootDir, "extensions/demo/openclaw.plugin.json", '{"id":"demo"}');
write(rootDir, "extensions/demo/index.ts", "export {};\n");
write(
rootDir,
"extensions/demo/package.json",
JSON.stringify({
openclaw: {
extensions: ["./index.ts"],
build: { bundledDist: false, runtimeFormat: "cjs" },
release: { publishToNpm: true },
},
}),
);
}
return rootDir;
}
function write(rootDir: string, relativePath: string, source: string): void {
const filePath = path.join(rootDir, relativePath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, source);
}
afterEach(() => {
vi.restoreAllMocks();
for (const rootDir of roots.splice(0)) {
fs.rmSync(rootDir, { recursive: true, force: true });
}
});
describe("built plugin control-plane module loads", () => {
it("keeps the native compiler unstarted for checker imports and runtime inventory checks", () => {
const rootDir = makeRoot();
const checkerUrl = resolveRuntimeWorkerUrl(toolingMtsEntrypoints.controlPlane);
const result = spawnSync(
testNodeExecPath,
[
...resolveRuntimeWorkerArgv(checkerUrl, testNodeExecPath).slice(0, -1),
"--input-type=module",
"--eval",
`import assert from "node:assert/strict";
import childProcess from "node:child_process";
import { syncBuiltinESMExports } from "node:module";
const spawn = childProcess.spawn;
let compilerStarts = 0;
childProcess.spawn = function(executable, args, ...rest) {
if (args?.includes("--api")) compilerStarts++;
return Reflect.apply(spawn, this, [executable, args, ...rest]);
};
syncBuiltinESMExports();
assert.equal(compilerStarts, 0, "native compiler started before the checker");
await import(${JSON.stringify(checkerUrl.href)});
assert.equal(compilerStarts, 0, "native compiler started by the checker import");
const { listCoreRuntimePostBuildOutputs } = await import(${JSON.stringify(resolveRuntimeWorkerUrl(toolingMtsEntrypoints.runtimePostbuild).href)});
listCoreRuntimePostBuildOutputs({ rootDir: ${JSON.stringify(rootDir)} });
assert.equal(compilerStarts, 0, "native compiler started by runtime postbuild inventory checks");
await import(${JSON.stringify(resolveRuntimeWorkerUrl(toolingMtsEntrypoints.runNode).href)});
assert.equal(compilerStarts, 0, "native compiler started by the development runner import");
const { API } = await import("typescript/unstable/sync");
const api = new API();
try {
api.parseConfigFile("tsconfig.json");
assert.equal(compilerStarts, 1, "the compiler process observation must detect an actual start");
} finally {
api.close();
}
`,
],
{
cwd: process.cwd(),
env: { ...process.env, NODE_OPTIONS: undefined },
encoding: "utf8",
timeout: 30_000,
},
);
expect(result.error, result.stderr).toBeUndefined();
expect(result.status, result.stderr).toBe(0);
});
it.each([".js", ".cjs"])(
"lists exact %s contracts and channel legacy setup references",
(extension) => {
const rootDir = makeRoot(extension);
write(
rootDir,
`dist/extensions/demo/doctor-contract-api${extension}`,
"export const ok = true;\n",
);
write(rootDir, `dist/extensions/demo/contract-api${extension}`, "export const ok = true;\n");
write(
rootDir,
`dist/extensions/demo/provider-contract-api${extension}`,
"export const ignored = true;\n",
);
write(
rootDir,
`dist/extensions/demo/setup-entry${extension}`,
[
"const setup = {",
` legacyStateMigrations: { specifier: "./legacy-state-migrations-api${extension}" },`,
` legacySessionSurface: { specifier: "./legacy-session-surface-api${extension}" },`,
"};",
"export default setup;",
].join("\n"),
);
write(
rootDir,
`dist/extensions/demo/legacy-state-migrations-api${extension}`,
"export {};\n",
);
write(rootDir, `dist/extensions/demo/legacy-session-surface-api${extension}`, "export {};\n");
expect(listBuiltPluginControlPlaneModules({ rootDir })).toEqual([
{
pluginId: "demo",
kind: "contract",
relativePath: `dist/extensions/demo/contract-api${extension}`,
},
{
pluginId: "demo",
kind: "doctor-contract",
relativePath: `dist/extensions/demo/doctor-contract-api${extension}`,
},
{
pluginId: "demo",
kind: "channel-legacy-session-surface",
relativePath: `dist/extensions/demo/legacy-session-surface-api${extension}`,
},
{
pluginId: "demo",
kind: "channel-legacy-state-migrations",
relativePath: `dist/extensions/demo/legacy-state-migrations-api${extension}`,
},
]);
},
);
it.each([".js", ".cjs"])("accepts synchronously requireable %s artifacts", (extension) => {
const rootDir = makeRoot(extension);
write(
rootDir,
`dist/extensions/demo/doctor-contract-api${extension}`,
extension === ".cjs" ? "exports.ok = true;\n" : "export const ok = true;\n",
);
expect(() => verifyBuiltPluginControlPlaneModules({ rootDir })).not.toThrow();
});
it("reports plugin, kind, path, and native require error", () => {
const rootDir = makeRoot();
write(
rootDir,
"dist/extensions/demo/doctor-contract-api.js",
"await Promise.resolve();\nexport const ok = true;\n",
);
expect(() => verifyBuiltPluginControlPlaneModules({ rootDir })).toThrow(
/demo \(doctor-contract\) dist\/extensions\/demo\/doctor-contract-api\.js:.*ERR_REQUIRE_ASYNC_MODULE/s,
);
});
it("bounds a stalled native require child", () => {
const rootDir = makeRoot();
write(rootDir, "dist/extensions/demo/doctor-contract-api.js", "while (true) {}\n");
const modules = listBuiltPluginControlPlaneModules({ rootDir });
expect(() => probeBuiltPluginControlPlaneModules(modules, { rootDir, timeoutMs: 100 })).toThrow(
/timed out|ETIMEDOUT/u,
);
});
});
describe("built doctor contract closures", () => {
it.each([".js", ".cjs"])(
"checks shared and cyclic %s chunks once while reporting each doctor contract",
(extension) => {
const rootDir = makeRoot(extension);
write(
rootDir,
`dist/extensions/demo/doctor-contract-api${extension}`,
extension === ".cjs"
? 'module.exports = require("../../token-chunk.cjs");'
: 'export * from "../../token-chunk.js";',
);
write(
rootDir,
`dist/token-chunk${extension}`,
extension === ".cjs"
? 'module.exports = require("./exec-chunk.cjs");'
: 'export * from "./exec-chunk.js";',
);
write(
rootDir,
`dist/extensions/other/doctor-contract-api${extension}`,
extension === ".cjs"
? 'module.exports = require("../../token-chunk.cjs");'
: 'export * from "../../token-chunk.js";',
);
write(
rootDir,
`dist/exec-chunk${extension}`,
extension === ".cjs"
? 'require("./token-chunk.cjs"); const exec = require("execa"); exports.rule = exec;'
: 'import "./token-chunk.js"; import "execa"; export const rule = 1;',
);
// Both formats use the same closure check; select the synthetic second plugin explicitly.
const modules = ["demo", "other"].map((pluginId) => ({
pluginId,
kind: "doctor-contract",
relativePath: `dist/extensions/${pluginId}/doctor-contract-api${extension}`,
}));
const readFile = vi.spyOn(fs, "readFileSync");
expect(collectBuiltDoctorContractClosureViolations(modules, { rootDir })).toEqual([
{
pluginId: "demo",
kind: "doctor-contract",
relativePath: `dist/extensions/demo/doctor-contract-api${extension}`,
dependency: "execa",
importerPath: `dist/exec-chunk${extension}`,
},
{
pluginId: "other",
kind: "doctor-contract",
relativePath: `dist/extensions/other/doctor-contract-api${extension}`,
dependency: "execa",
importerPath: `dist/exec-chunk${extension}`,
},
]);
for (const chunk of ["token-chunk", "exec-chunk"]) {
expect(
readFile.mock.calls.filter(
([file]) => file === path.join(rootDir, `dist/${chunk}${extension}`),
),
).toHaveLength(1);
}
write(rootDir, `dist/exec-chunk${extension}`, "");
expect(collectBuiltDoctorContractClosureViolations(modules, { rootDir })).toEqual([]);
},
);
it.each([".js", ".cjs"])(
"ignores lazy %s edges and non-doctor contract surfaces",
(extension) => {
const rootDir = makeRoot(extension);
// A dynamic import is never paid at enumeration time, and the general contract
// surface may legitimately spawn commands (matrix probes its SDK packages).
write(
rootDir,
`dist/extensions/demo/doctor-contract-api${extension}`,
extension === ".cjs"
? 'exports.load = () => require("execa");'
: 'export const load = () => import("execa");',
);
write(
rootDir,
`dist/extensions/demo/contract-api${extension}`,
extension === ".cjs"
? 'require("execa"); exports.a = 1;'
: 'import "execa"; export const a = 1;',
);
expect(
collectBuiltDoctorContractClosureViolations(
listBuiltPluginControlPlaneModules({ rootDir }),
{
rootDir,
},
),
).toEqual([]);
},
);
});