Files
openclaw/test/scripts/check-built-plugin-control-plane-modules.test.ts
T
Peter Steinberger da4a656cdb improve: doctor migration checks no longer load every bundled plugin runtime (#120678)
* perf(plugins): declare doctor contract surfaces

* perf(doctor): slim migration import closures

* perf(plugins): narrow doctor declaration record surface and wire owner-test lane

Registry records carry only the doctorContract declaration instead of the whole
parsed manifest, and check:changed now selects the src/plugins-owned declaration
honesty and closure-guard tests for extension module/manifest changes so
cross-lane drift cannot pass PR classification.

* fix(doctor): keep control-plane dist imports require-safe

Keep doctor and channel control-plane chunks off exec-class dependencies, and enforce native require(esm) loading during postbuild.

* chore(plugin-sdk): regenerate API baseline

* chore(plugin-sdk): sync export ordering

* fix(plugins): satisfy doctor contract CI boundaries

* perf(doctor): make qqbot doctor closure dependency-light

qqbot was the last plugin above 5s in doctor state-migration enumeration
(~8s under tsx/jiti). The cost was not the state-key builder (already a
leaf): its doctor closure value-imported the runtime-doctor SDK barrel,
whose plugin-state-store/state-db re-exports pull kysely (~330 modules),
plus security-runtime for one fileExists (~200 modules), all resolved
per-module by jiti during enumeration.

Split the migration-define helpers and light re-exports into a new
private-local plugin-sdk/runtime-doctor-migrations subpath; runtime-doctor
re-exports it so its public surface is byte-identical (API baseline hash
unchanged). qqbot's doctor-contract and state-migrations now import only
the light subpath, swapping fileExists for the equivalent async
legacyStateFileExists already in the closure.

qqbot enumeration: ~8.0s/531 modules -> ~0.25s/18 modules.

* chore(plugin-sdk): drop private-local subpath from API baseline

runtime-doctor-migrations is private-local-only; the baseline tracks public
modules, and the earlier line was generated before the classification.

* fix(plugins): register runtime-doctor-migrations boundary paths

The private-local subpath list feeds the extension package boundary map;
the shared paths config and xai's derived overrides must carry the same
entry or the boundary contract test fails.
2026-08-08 13:29:18 -07:00

112 lines
3.8 KiB
TypeScript

// Built plugin control-plane module checks cover native require(esm) acceptance.
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
listBuiltPluginControlPlaneModules,
probeBuiltPluginControlPlaneModules,
verifyBuiltPluginControlPlaneModules,
} from "../../scripts/check-built-plugin-control-plane-modules.mjs";
const roots: string[] = [];
function makeRoot(): 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');
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("lists exact contract files and channel legacy setup references", () => {
const rootDir = makeRoot();
write(rootDir, "dist/extensions/demo/doctor-contract-api.js", "export const ok = true;\n");
write(rootDir, "dist/extensions/demo/contract-api.js", "export const ok = true;\n");
write(
rootDir,
"dist/extensions/demo/provider-contract-api.js",
"export const ignored = true;\n",
);
write(
rootDir,
"dist/extensions/demo/setup-entry.js",
[
"const setup = {",
' legacyStateMigrations: { specifier: "./legacy-state-migrations-api.js" },',
' legacySessionSurface: { specifier: "./legacy-session-surface-api.js" },',
"};",
"export default setup;",
].join("\n"),
);
write(rootDir, "dist/extensions/demo/legacy-state-migrations-api.js", "export {};\n");
write(rootDir, "dist/extensions/demo/legacy-session-surface-api.js", "export {};\n");
expect(listBuiltPluginControlPlaneModules({ rootDir })).toEqual([
{
pluginId: "demo",
kind: "contract",
relativePath: "dist/extensions/demo/contract-api.js",
},
{
pluginId: "demo",
kind: "doctor-contract",
relativePath: "dist/extensions/demo/doctor-contract-api.js",
},
{
pluginId: "demo",
kind: "channel-legacy-session-surface",
relativePath: "dist/extensions/demo/legacy-session-surface-api.js",
},
{
pluginId: "demo",
kind: "channel-legacy-state-migrations",
relativePath: "dist/extensions/demo/legacy-state-migrations-api.js",
},
]);
});
it("accepts synchronously requireable ESM artifacts", () => {
const rootDir = makeRoot();
write(rootDir, "dist/extensions/demo/doctor-contract-api.js", "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,
);
});
});