mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:10:52 +00:00
QA: fix matrix runner staging and host registration
This commit is contained in:
@@ -652,13 +652,14 @@ describe("qa bundled plugin dir", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("creates a scoped bundled plugin tree for the allowed plugins only", async () => {
|
||||
it("creates a scoped bundled plugin tree for allowed plugins plus always-allowed runtime facades", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-scope-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "qa-channel"), { recursive: true });
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "memory-core"), { recursive: true });
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "speech-core"), { recursive: true });
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "unused-plugin"), { recursive: true });
|
||||
await writeFile(path.join(repoRoot, "dist", "shared-chunk-abc123.js"), "export {};\n", "utf8");
|
||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-target-"));
|
||||
@@ -672,7 +673,11 @@ describe("qa bundled plugin dir", () => {
|
||||
allowedPluginIds: ["qa-channel", "memory-core"],
|
||||
});
|
||||
|
||||
expect((await readdir(bundledPluginsDir)).toSorted()).toEqual(["memory-core", "qa-channel"]);
|
||||
expect((await readdir(bundledPluginsDir)).toSorted()).toEqual([
|
||||
"memory-core",
|
||||
"qa-channel",
|
||||
"speech-core",
|
||||
]);
|
||||
expect(bundledPluginsDir).toBe(
|
||||
path.join(
|
||||
repoRoot,
|
||||
@@ -688,6 +693,7 @@ describe("qa bundled plugin dir", () => {
|
||||
);
|
||||
expect((await lstat(path.join(bundledPluginsDir, "qa-channel"))).isDirectory()).toBe(true);
|
||||
expect((await lstat(path.join(bundledPluginsDir, "memory-core"))).isDirectory()).toBe(true);
|
||||
expect((await lstat(path.join(bundledPluginsDir, "speech-core"))).isDirectory()).toBe(true);
|
||||
await expect(
|
||||
lstat(
|
||||
path.join(
|
||||
@@ -854,4 +860,37 @@ describe("qa bundled plugin dir", () => {
|
||||
}),
|
||||
).resolves.toBe("2026.4.8");
|
||||
});
|
||||
|
||||
it("includes always-allowed runtime facade plugins when raising the QA runtime host version", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-runtime-version-runtime-facade-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
await writeFile(
|
||||
path.join(repoRoot, "package.json"),
|
||||
JSON.stringify({ version: "2026.4.7-1" }),
|
||||
"utf8",
|
||||
);
|
||||
const bundledRoot = path.join(repoRoot, "extensions");
|
||||
await mkdir(path.join(bundledRoot, "qa-channel"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(bundledRoot, "qa-channel", "package.json"),
|
||||
JSON.stringify({ openclaw: { install: { minHostVersion: ">=2026.4.8" } } }),
|
||||
"utf8",
|
||||
);
|
||||
await mkdir(path.join(bundledRoot, "speech-core"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(bundledRoot, "speech-core", "package.json"),
|
||||
JSON.stringify({ openclaw: { install: { minHostVersion: ">=2026.4.9" } } }),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
await expect(
|
||||
__testing.resolveQaRuntimeHostVersion({
|
||||
repoRoot,
|
||||
bundledPluginsSourceRoot: bundledRoot,
|
||||
allowedPluginIds: ["qa-channel"],
|
||||
}),
|
||||
).resolves.toBe("2026.4.9");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -78,6 +78,14 @@ const QA_MOCK_BLOCKED_ENV_KEY_PATTERNS = Object.freeze([
|
||||
|
||||
const QA_LIVE_PROVIDER_CONFIG_PATH_ENV = "OPENCLAW_QA_LIVE_PROVIDER_CONFIG_PATH";
|
||||
const QA_LIVE_ANTHROPIC_SETUP_TOKEN_ENV = "OPENCLAW_QA_LIVE_ANTHROPIC_SETUP_TOKEN";
|
||||
// Keep this in sync with the facade runtime's always-allowed bundled surfaces.
|
||||
// QA child staging must include these runtime helpers even when they are not in
|
||||
// cfg.plugins.allow, otherwise lazy facade loads can fail inside the child.
|
||||
const QA_ALWAYS_STAGE_RUNTIME_PLUGIN_IDS = Object.freeze([
|
||||
"image-generation-core",
|
||||
"media-understanding-core",
|
||||
"speech-core",
|
||||
]);
|
||||
const QA_LIVE_SETUP_TOKEN_VALUE_ENV = "OPENCLAW_LIVE_SETUP_TOKEN_VALUE";
|
||||
const QA_LIVE_ANTHROPIC_SETUP_TOKEN_PROFILE_ENV = "OPENCLAW_QA_LIVE_ANTHROPIC_SETUP_TOKEN_PROFILE";
|
||||
const QA_LIVE_ANTHROPIC_SETUP_TOKEN_PROFILE_ID = "anthropic:qa-setup-token";
|
||||
@@ -765,8 +773,12 @@ async function resolveQaRuntimeHostVersion(params: {
|
||||
const rootPackageRaw = await fs.readFile(path.join(params.repoRoot, "package.json"), "utf8");
|
||||
const rootPackage = JSON.parse(rootPackageRaw) as { version?: string };
|
||||
let selected = parseStableSemverFloor(rootPackage.version);
|
||||
const stagedPluginIds = collectQaBundledPluginIds({
|
||||
sourceRoot: params.bundledPluginsSourceRoot,
|
||||
allowedPluginIds: params.allowedPluginIds,
|
||||
});
|
||||
|
||||
for (const pluginId of params.allowedPluginIds) {
|
||||
for (const pluginId of stagedPluginIds) {
|
||||
const packagePath = path.join(params.bundledPluginsSourceRoot, pluginId, "package.json");
|
||||
if (!existsSync(packagePath)) {
|
||||
continue;
|
||||
@@ -788,12 +800,29 @@ async function resolveQaRuntimeHostVersion(params: {
|
||||
return selected?.label;
|
||||
}
|
||||
|
||||
function collectQaBundledPluginIds(params: {
|
||||
sourceRoot: string;
|
||||
allowedPluginIds: readonly string[];
|
||||
}) {
|
||||
const pluginIds = new Set(params.allowedPluginIds);
|
||||
for (const pluginId of QA_ALWAYS_STAGE_RUNTIME_PLUGIN_IDS) {
|
||||
if (existsSync(path.join(params.sourceRoot, pluginId))) {
|
||||
pluginIds.add(pluginId);
|
||||
}
|
||||
}
|
||||
return [...pluginIds];
|
||||
}
|
||||
|
||||
async function createQaBundledPluginsDir(params: {
|
||||
repoRoot: string;
|
||||
tempRoot: string;
|
||||
allowedPluginIds: readonly string[];
|
||||
}) {
|
||||
const sourceRoot = resolveQaBundledPluginsSourceRoot(params.repoRoot);
|
||||
const stagedPluginIds = collectQaBundledPluginIds({
|
||||
sourceRoot,
|
||||
allowedPluginIds: params.allowedPluginIds,
|
||||
});
|
||||
const sourceTreeRoot = path.dirname(sourceRoot);
|
||||
if (
|
||||
sourceTreeRoot === path.join(params.repoRoot, "dist") ||
|
||||
@@ -814,7 +843,7 @@ async function createQaBundledPluginsDir(params: {
|
||||
const targetPath = path.join(stagedTreeRoot, entry.name);
|
||||
if (entry.name === "extensions") {
|
||||
await fs.mkdir(targetPath, { recursive: true });
|
||||
for (const pluginId of params.allowedPluginIds) {
|
||||
for (const pluginId of stagedPluginIds) {
|
||||
const sourceDir = path.join(sourceRoot, pluginId);
|
||||
if (!existsSync(sourceDir)) {
|
||||
throw new Error(`qa bundled plugin not found: ${pluginId} (${sourceDir})`);
|
||||
@@ -834,7 +863,7 @@ async function createQaBundledPluginsDir(params: {
|
||||
|
||||
const bundledPluginsDir = path.join(params.tempRoot, "bundled-plugins");
|
||||
await fs.mkdir(bundledPluginsDir, { recursive: true });
|
||||
for (const pluginId of params.allowedPluginIds) {
|
||||
for (const pluginId of stagedPluginIds) {
|
||||
const sourceDir = path.join(sourceRoot, pluginId);
|
||||
if (!existsSync(sourceDir)) {
|
||||
throw new Error(`qa bundled plugin not found: ${pluginId} (${sourceDir})`);
|
||||
|
||||
@@ -2,7 +2,10 @@ 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";
|
||||
import {
|
||||
loadBundledPluginPublicSurfaceModuleSync,
|
||||
tryLoadActivatedBundledPluginPublicSurfaceModuleSync,
|
||||
} from "./facade-runtime.js";
|
||||
|
||||
export type QaRunnerCliRegistration = {
|
||||
commandName: string;
|
||||
@@ -98,6 +101,19 @@ function buildKnownQaRunnerCatalog(): readonly QaRunnerCliContribution[] {
|
||||
});
|
||||
}
|
||||
|
||||
function loadQaRunnerRuntimeSurface(plugin: PluginManifestRecord): QaRunnerRuntimeSurface | null {
|
||||
if (plugin.origin === "bundled") {
|
||||
return loadBundledPluginPublicSurfaceModuleSync<QaRunnerRuntimeSurface>({
|
||||
dirName: plugin.id,
|
||||
artifactBasename: "runtime-api.js",
|
||||
});
|
||||
}
|
||||
return tryLoadActivatedBundledPluginPublicSurfaceModuleSync<QaRunnerRuntimeSurface>({
|
||||
dirName: plugin.id,
|
||||
artifactBasename: "runtime-api.js",
|
||||
});
|
||||
}
|
||||
|
||||
export function listQaRunnerCliContributions(): readonly QaRunnerCliContribution[] {
|
||||
const contributions = new Map<string, QaRunnerCliContribution>();
|
||||
|
||||
@@ -106,11 +122,7 @@ export function listQaRunnerCliContributions(): readonly QaRunnerCliContribution
|
||||
}
|
||||
|
||||
for (const plugin of listDeclaredQaRunnerPlugins()) {
|
||||
const runtimeSurface =
|
||||
tryLoadActivatedBundledPluginPublicSurfaceModuleSync<QaRunnerRuntimeSurface>({
|
||||
dirName: plugin.id,
|
||||
artifactBasename: "runtime-api.js",
|
||||
});
|
||||
const runtimeSurface = loadQaRunnerRuntimeSurface(plugin);
|
||||
const runtimeRegistrationByCommandName = runtimeSurface
|
||||
? indexRuntimeRegistrations(plugin.id, runtimeSurface)
|
||||
: null;
|
||||
|
||||
Reference in New Issue
Block a user