fix(test): align plugin gauntlet with built runtime

This commit is contained in:
Vincent Koc
2026-05-03 13:17:14 -07:00
parent b726214cf3
commit 53cc52981b
4 changed files with 82 additions and 6 deletions

View File

@@ -1,4 +1,4 @@
import { existsSync } from "node:fs";
import { existsSync, readdirSync, readFileSync } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
@@ -76,16 +76,20 @@ export function resolveQaBundledPluginSourceDir(params: { repoRoot: string; plug
path.join(params.repoRoot, "extensions", params.pluginId),
];
const existingCandidates = candidates.filter((candidate) => existsSync(candidate));
if (existingCandidates.length === 0) {
const manifestCandidates = findQaBundledPluginDirsByManifestId(params);
const allCandidates = [...existingCandidates, ...manifestCandidates].filter(
(candidate, index, all) => all.indexOf(candidate) === index,
);
if (allCandidates.length === 0) {
return null;
}
const cliMetadataCandidate = existingCandidates.find((candidate) =>
const cliMetadataCandidate = allCandidates.find((candidate) =>
QA_CLI_METADATA_ENTRY_BASENAMES.some((basename) => existsSync(path.join(candidate, basename))),
);
if (cliMetadataCandidate) {
return cliMetadataCandidate;
}
return existingCandidates[0] ?? null;
return allCandidates[0] ?? null;
}
function resolveQaBundledPluginScanRoots(repoRoot: string) {
@@ -96,6 +100,37 @@ function resolveQaBundledPluginScanRoots(repoRoot: string) {
].filter((candidate, index, all) => existsSync(candidate) && all.indexOf(candidate) === index);
}
function readQaBundledManifestId(manifestPath: string): string | null {
try {
const parsed = JSON.parse(readFileSync(manifestPath, "utf8")) as { id?: unknown };
return typeof parsed.id === "string" ? parsed.id.trim() || null : null;
} catch {
return null;
}
}
function findQaBundledPluginDirsByManifestId(params: {
repoRoot: string;
pluginId: string;
}): string[] {
const candidates: string[] = [];
for (const sourceRoot of resolveQaBundledPluginScanRoots(params.repoRoot)) {
for (const entry of readdirSync(sourceRoot, { withFileTypes: true }).toSorted((left, right) =>
left.name.localeCompare(right.name),
)) {
if (!entry.isDirectory()) {
continue;
}
const candidate = path.join(sourceRoot, entry.name);
const manifestId = readQaBundledManifestId(path.join(candidate, "openclaw.plugin.json"));
if (manifestId === params.pluginId) {
candidates.push(candidate);
}
}
}
return candidates;
}
export async function resolveQaOwnerPluginIdsForProviderIds(params: {
repoRoot: string;
providerIds: readonly string[];

View File

@@ -791,6 +791,33 @@ describe("qa bundled plugin dir", () => {
).toBe(path.join(repoRoot, "extensions", "qa-channel"));
});
it("resolves bundled plugins by manifest id when the directory name differs", async () => {
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-manifest-id-root-"));
cleanups.push(async () => {
await rm(repoRoot, { recursive: true, force: true });
});
await mkdir(path.join(repoRoot, "dist", "extensions", "kimi-coding"), {
recursive: true,
});
await writeFile(
path.join(repoRoot, "dist", "extensions", "kimi-coding", "openclaw.plugin.json"),
JSON.stringify({ id: "kimi", providers: ["kimi"] }),
"utf8",
);
await writeFile(
path.join(repoRoot, "dist", "extensions", "kimi-coding", "package.json"),
"{}",
"utf8",
);
expect(
__testing.resolveQaBundledPluginSourceDir({
repoRoot,
pluginId: "kimi",
}),
).toBe(path.join(repoRoot, "dist", "extensions", "kimi-coding"));
});
it("uses a source bundled plugin when the built copy is missing CLI metadata", async () => {
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-cli-metadata-root-"));
cleanups.push(async () => {

View File

@@ -1,6 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import JSON5 from "json5";
import { collectBundledPluginBuildEntries } from "./bundled-plugin-build-entries.mjs";
const MANIFEST_NAMES = ["openclaw.plugin.json", "openclaw.plugin.json5"];
@@ -142,9 +143,13 @@ function buildPluginMatrixEntry(params) {
function discoverBundledPluginManifests(repoRoot) {
const extensionsDir = path.join(repoRoot, "extensions");
const buildEntryDirs = new Set(
collectBundledPluginBuildEntries({ cwd: repoRoot }).map((entry) => entry.id),
);
const entries = fs
.readdirSync(extensionsDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.filter((entry) => buildEntryDirs.has(entry.name))
.flatMap((entry) => {
const pluginDir = path.join(extensionsDir, entry.name);
const manifestName = MANIFEST_NAMES.find((name) => fs.existsSync(path.join(pluginDir, name)));

View File

@@ -53,8 +53,8 @@ describe("plugin gateway gauntlet helpers", () => {
);
await writeManifest(
"beta",
"openclaw.plugin.json5",
`{ id: "beta", commandAliases: ["dreaming"], onboardingScopes: ["memory"] }`,
"openclaw.plugin.json",
JSON.stringify({ id: "beta", commandAliases: ["dreaming"], onboardingScopes: ["memory"] }),
);
const matrix = discoverBundledPluginManifests(repoRoot);
@@ -77,6 +77,15 @@ describe("plugin gateway gauntlet helpers", () => {
]);
});
it("skips source-only plugin dirs that are excluded from the built runtime", async () => {
await writeManifest("qqbot", "openclaw.plugin.json", JSON.stringify({ id: "qqbot" }));
await writeManifest("telegram", "openclaw.plugin.json", JSON.stringify({ id: "telegram" }));
const matrix = discoverBundledPluginManifests(repoRoot);
expect(matrix.map((entry) => entry.id)).toEqual(["telegram"]);
});
it("selects plugin shards after explicit id filtering", () => {
const entries = ["a", "b", "c", "d"].map((id) => ({ id }));