Files
openclaw/src/plugin-sdk/qa-runner-runtime.ts
Gustavo Madeira Santana 82a2db71e8 refactor(qa): split Matrix QA into optional plugin (#66723)
Merged via squash.

Prepared head SHA: 27241bd089
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
2026-04-14 16:28:57 -04:00

162 lines
5.5 KiB
TypeScript

import type { Command } from "commander";
import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
import { listBundledQaRunnerCatalog } from "../plugins/qa-runner-catalog.js";
import { tryLoadActivatedBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js";
export type QaRunnerCliRegistration = {
commandName: string;
register(qa: Command): void;
};
type QaRunnerRuntimeSurface = {
qaRunnerCliRegistrations?: readonly QaRunnerCliRegistration[];
};
export type QaRunnerCliContribution =
| {
pluginId: string;
commandName: string;
description?: string;
status: "available";
registration: QaRunnerCliRegistration;
}
| {
pluginId: string;
commandName: string;
description?: string;
status: "blocked";
}
| {
pluginId: string;
commandName: string;
description?: string;
status: "missing";
npmSpec: string;
};
function listDeclaredQaRunnerPlugins(): Array<
PluginManifestRecord & {
qaRunners: NonNullable<PluginManifestRecord["qaRunners"]>;
}
> {
return loadPluginManifestRegistry({ cache: true })
.plugins.filter(
(
plugin,
): plugin is PluginManifestRecord & {
qaRunners: NonNullable<PluginManifestRecord["qaRunners"]>;
} => Array.isArray(plugin.qaRunners) && plugin.qaRunners.length > 0,
)
.toSorted((left, right) => {
const idCompare = left.id.localeCompare(right.id);
if (idCompare !== 0) {
return idCompare;
}
return left.rootDir.localeCompare(right.rootDir);
});
}
function indexRuntimeRegistrations(
pluginId: string,
surface: QaRunnerRuntimeSurface,
): ReadonlyMap<string, QaRunnerCliRegistration> {
const registrations = surface.qaRunnerCliRegistrations ?? [];
const registrationByCommandName = new Map<string, QaRunnerCliRegistration>();
for (const registration of registrations) {
if (!registration?.commandName || typeof registration.register !== "function") {
throw new Error(`QA runner plugin "${pluginId}" exported an invalid CLI registration`);
}
if (registrationByCommandName.has(registration.commandName)) {
throw new Error(
`QA runner plugin "${pluginId}" exported duplicate CLI registration "${registration.commandName}"`,
);
}
registrationByCommandName.set(registration.commandName, registration);
}
return registrationByCommandName;
}
function buildKnownQaRunnerCatalog(): readonly QaRunnerCliContribution[] {
const knownRunners = listBundledQaRunnerCatalog();
const seenCommandNames = new Map<string, string>();
return knownRunners.map((runner) => {
const previousOwner = seenCommandNames.get(runner.commandName);
if (previousOwner) {
throw new Error(
`QA runner command "${runner.commandName}" declared by both "${previousOwner}" and "${runner.pluginId}"`,
);
}
seenCommandNames.set(runner.commandName, runner.pluginId);
return {
pluginId: runner.pluginId,
commandName: runner.commandName,
...(runner.description ? { description: runner.description } : {}),
status: "missing" as const,
npmSpec: runner.npmSpec,
};
});
}
export function listQaRunnerCliContributions(): readonly QaRunnerCliContribution[] {
const contributions = new Map<string, QaRunnerCliContribution>();
for (const runner of buildKnownQaRunnerCatalog()) {
contributions.set(runner.commandName, runner);
}
for (const plugin of listDeclaredQaRunnerPlugins()) {
const runtimeSurface =
tryLoadActivatedBundledPluginPublicSurfaceModuleSync<QaRunnerRuntimeSurface>({
dirName: plugin.id,
artifactBasename: "runtime-api.js",
});
const runtimeRegistrationByCommandName = runtimeSurface
? indexRuntimeRegistrations(plugin.id, runtimeSurface)
: null;
const declaredCommandNames = new Set(plugin.qaRunners.map((runner) => runner.commandName));
for (const runner of plugin.qaRunners) {
const previous = contributions.get(runner.commandName);
if (previous && previous.pluginId !== plugin.id) {
throw new Error(
`QA runner command "${runner.commandName}" declared by both "${previous.pluginId}" and "${plugin.id}"`,
);
}
const registration = runtimeRegistrationByCommandName?.get(runner.commandName);
if (!runtimeSurface) {
contributions.set(runner.commandName, {
pluginId: plugin.id,
commandName: runner.commandName,
...(runner.description ? { description: runner.description } : {}),
status: "blocked",
});
continue;
}
if (!registration) {
throw new Error(
`QA runner plugin "${plugin.id}" declared "${runner.commandName}" in openclaw.plugin.json but did not export a matching CLI registration`,
);
}
contributions.set(runner.commandName, {
pluginId: plugin.id,
commandName: runner.commandName,
...(runner.description ? { description: runner.description } : {}),
status: "available",
registration,
});
}
for (const commandName of runtimeRegistrationByCommandName?.keys() ?? []) {
if (!declaredCommandNames.has(commandName)) {
throw new Error(
`QA runner plugin "${plugin.id}" exported "${commandName}" from runtime-api.js but did not declare it in openclaw.plugin.json`,
);
}
}
}
return [...contributions.values()];
}