test: stabilize browser and provider ci shards

This commit is contained in:
Peter Steinberger
2026-04-06 03:20:09 +01:00
parent 332e7d9d7b
commit b1c98e8469
34 changed files with 345 additions and 542 deletions

View File

@@ -7,6 +7,7 @@ import {
withBundledPluginEnablementCompat,
withBundledPluginVitestCompat,
} from "./bundled-compat.js";
import { resolveBundledPluginRepoEntryPath } from "./bundled-plugin-metadata.js";
import { createCapturedPluginRegistration } from "./captured-registration.js";
import { discoverOpenClawPlugins } from "./discovery.js";
import type { PluginLoadOptions } from "./loader.js";
@@ -213,6 +214,7 @@ export function loadBundledCapabilityRuntimeRegistry(params: {
manifestRegistry.plugins.map((record) => [record.rootDir, record]),
);
const seenPluginIds = new Set<string>();
const repoRoot = process.cwd();
for (const candidate of discovery.candidates) {
const manifest = manifestByRoot.get(candidate.rootDir);
@@ -229,15 +231,22 @@ export function loadBundledCapabilityRuntimeRegistry(params: {
name: manifest.name,
description: manifest.description,
version: manifest.version,
source: candidate.source,
source:
env?.VITEST && params.pluginSdkResolution === "dist"
? (resolveBundledPluginRepoEntryPath({
rootDir: repoRoot,
pluginId: manifest.id,
preferBuilt: true,
}) ?? candidate.source)
: candidate.source,
rootDir: candidate.rootDir,
workspaceDir: candidate.workspaceDir,
});
const opened = openBoundaryFileSync({
absolutePath: candidate.source,
rootPath: candidate.rootDir,
boundaryLabel: "plugin root",
absolutePath: record.source,
rootPath: record.source === candidate.source ? candidate.rootDir : repoRoot,
boundaryLabel: record.source === candidate.source ? "plugin root" : "repo root",
rejectHardlinks: false,
skipLexicalRootCheck: true,
});

View File

@@ -5,6 +5,7 @@ import {
clearBundledPluginMetadataCache,
listBundledPluginMetadata,
resolveBundledPluginGeneratedPath,
resolveBundledPluginRepoEntryPath,
} from "./bundled-plugin-metadata.js";
import {
createGeneratedPluginTempRoot,
@@ -175,6 +176,45 @@ describe("bundled plugin metadata", () => {
expectGeneratedPathResolution(tempRoot, path.join("plugin", "index.js"));
});
it("resolves bundled repo entry paths from dist before workspace source", () => {
const tempRoot = createGeneratedPluginTempRoot("openclaw-bundled-plugin-repo-entry-");
const pluginRoot = path.join(tempRoot, "extensions", "alpha");
const distPluginRoot = path.join(tempRoot, "dist", "extensions", "alpha");
writeJson(path.join(pluginRoot, "package.json"), {
name: "@openclaw/alpha",
version: "0.0.1",
openclaw: {
extensions: ["./index.ts"],
},
});
writeJson(path.join(pluginRoot, "openclaw.plugin.json"), {
id: "alpha",
configSchema: { type: "object" },
});
fs.writeFileSync(path.join(pluginRoot, "index.ts"), "export const source = true;\n", "utf8");
expect(
resolveBundledPluginRepoEntryPath({
rootDir: tempRoot,
pluginId: "alpha",
preferBuilt: true,
}),
).toBe(path.join(pluginRoot, "index.ts"));
fs.mkdirSync(distPluginRoot, { recursive: true });
fs.writeFileSync(path.join(distPluginRoot, "index.js"), "export const built = true;\n", "utf8");
clearBundledPluginMetadataCache();
expect(
resolveBundledPluginRepoEntryPath({
rootDir: tempRoot,
pluginId: "alpha",
preferBuilt: true,
}),
).toBe(path.join(distPluginRoot, "index.js"));
});
it("merges runtime channel schema metadata with manifest-owned channel config fields", () => {
const tempRoot = createGeneratedPluginTempRoot("openclaw-bundled-plugin-channel-configs-");

View File

@@ -243,3 +243,37 @@ export function resolveBundledPluginGeneratedPath(
}
return null;
}
function normalizeRelativePluginEntryPath(entryPath: string): string {
return entryPath.replace(/^\.\//u, "");
}
export function resolveBundledPluginRepoEntryPath(params: {
rootDir: string;
pluginId: string;
preferBuilt?: boolean;
}): string | null {
const metadata = findBundledPluginMetadataById(params.pluginId, { rootDir: params.rootDir });
if (!metadata) {
return null;
}
const entryOrder = params.preferBuilt
? [metadata.source.built, metadata.source.source]
: [metadata.source.source, metadata.source.built];
const baseDirs = [
path.resolve(params.rootDir, "dist", "extensions", metadata.dirName),
path.resolve(params.rootDir, "extensions", metadata.dirName),
];
for (const baseDir of baseDirs) {
for (const entryPath of entryOrder) {
const candidate = path.resolve(baseDir, normalizeRelativePluginEntryPath(entryPath));
if (fs.existsSync(candidate)) {
return candidate;
}
}
}
return null;
}

View File

@@ -15,6 +15,7 @@ import type {
WebFetchProviderPlugin,
WebSearchProviderPlugin,
} from "../types.js";
import { BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS } from "./inventory/bundled-capability-metadata.js";
import {
loadVitestImageGenerationProviderContractRegistry,
loadVitestMediaUnderstandingProviderContractRegistry,
@@ -91,6 +92,21 @@ function uniqueStrings(values: readonly string[]): string[] {
}
function resolveBundledManifestContracts(): PluginRegistrationContractEntry[] {
if (process.env.VITEST) {
return BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.map((entry) => ({
pluginId: entry.pluginId,
providerIds: [...entry.providerIds],
speechProviderIds: [...entry.speechProviderIds],
realtimeTranscriptionProviderIds: [...entry.realtimeTranscriptionProviderIds],
realtimeVoiceProviderIds: [...entry.realtimeVoiceProviderIds],
mediaUnderstandingProviderIds: [...entry.mediaUnderstandingProviderIds],
imageGenerationProviderIds: [...entry.imageGenerationProviderIds],
videoGenerationProviderIds: [...entry.videoGenerationProviderIds],
webFetchProviderIds: [...entry.webFetchProviderIds],
webSearchProviderIds: [...entry.webSearchProviderIds],
toolNames: [...entry.toolNames],
}));
}
return loadPluginManifestRegistry({})
.plugins.filter(
(plugin) =>

View File

@@ -1,5 +1,8 @@
import { createJiti } from "jiti";
import { loadBundledCapabilityRuntimeRegistry } from "../bundled-capability-runtime.js";
import { resolveManifestContractPluginIds } from "../manifest-registry.js";
import { resolveBundledPluginRepoEntryPath } from "../bundled-plugin-metadata.js";
import { createCapturedPluginRegistration } from "../captured-registration.js";
import type { OpenClawPluginDefinition } from "../types.js";
import type {
ImageGenerationProviderPlugin,
MediaUnderstandingProviderPlugin,
@@ -9,6 +12,7 @@ import type {
SpeechProviderPlugin,
VideoGenerationProviderPlugin,
} from "../types.js";
import { BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS } from "./inventory/bundled-capability-metadata.js";
export type SpeechProviderContractEntry = {
pluginId: string;
@@ -54,6 +58,67 @@ type ManifestContractKey =
| "videoGenerationProviders"
| "musicGenerationProviders";
const VITEST_CONTRACT_PLUGIN_IDS = {
imageGenerationProviders: BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
(entry) => entry.imageGenerationProviderIds.length > 0,
).map((entry) => entry.pluginId),
speechProviders: BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
(entry) => entry.speechProviderIds.length > 0,
).map((entry) => entry.pluginId),
mediaUnderstandingProviders: BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
(entry) => entry.mediaUnderstandingProviderIds.length > 0,
).map((entry) => entry.pluginId),
realtimeVoiceProviders: BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
(entry) => entry.realtimeVoiceProviderIds.length > 0,
).map((entry) => entry.pluginId),
realtimeTranscriptionProviders: BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
(entry) => entry.realtimeTranscriptionProviderIds.length > 0,
).map((entry) => entry.pluginId),
videoGenerationProviders: BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.filter(
(entry) => entry.videoGenerationProviderIds.length > 0,
).map((entry) => entry.pluginId),
} satisfies Record<ManifestContractKey, string[]>;
function loadVitestVideoGenerationFallbackEntries(
pluginIds: readonly string[],
): VideoGenerationProviderContractEntry[] {
const jiti = createJiti(import.meta.url, {
interopDefault: true,
moduleCache: false,
fsCache: false,
});
const repoRoot = process.cwd();
return pluginIds.flatMap((pluginId) => {
const modulePath = resolveBundledPluginRepoEntryPath({
rootDir: repoRoot,
pluginId,
preferBuilt: true,
});
if (!modulePath) {
return [];
}
try {
const mod = jiti(modulePath) as
| OpenClawPluginDefinition
| { default?: OpenClawPluginDefinition };
const plugin =
(mod as { default?: OpenClawPluginDefinition }).default ??
(mod as OpenClawPluginDefinition);
if (typeof plugin?.register !== "function") {
return [];
}
const captured = createCapturedPluginRegistration();
void plugin.register(captured.api);
return captured.videoGenerationProviders.map((provider) => ({
pluginId,
provider,
}));
} catch {
return [];
}
});
}
function loadVitestCapabilityContractEntries<T>(params: {
contract: ManifestContractKey;
pickEntries: (registry: ReturnType<typeof loadBundledCapabilityRuntimeRegistry>) => Array<{
@@ -61,19 +126,30 @@ function loadVitestCapabilityContractEntries<T>(params: {
provider: T;
}>;
}): Array<{ pluginId: string; provider: T }> {
const pluginIds = resolveManifestContractPluginIds({
contract: params.contract,
origin: "bundled",
});
const pluginIds = VITEST_CONTRACT_PLUGIN_IDS[params.contract];
if (pluginIds.length === 0) {
return [];
}
return params.pickEntries(
const bulkEntries = params.pickEntries(
loadBundledCapabilityRuntimeRegistry({
pluginIds,
pluginSdkResolution: "dist",
}),
);
const coveredPluginIds = new Set(bulkEntries.map((entry) => entry.pluginId));
if (coveredPluginIds.size === pluginIds.length) {
return bulkEntries;
}
return pluginIds.flatMap((pluginId) =>
params
.pickEntries(
loadBundledCapabilityRuntimeRegistry({
pluginIds: [pluginId],
pluginSdkResolution: "dist",
}),
)
.filter((entry) => entry.pluginId === pluginId),
);
}
export function loadVitestSpeechProviderContractRegistry(): SpeechProviderContractEntry[] {
@@ -132,7 +208,7 @@ export function loadVitestImageGenerationProviderContractRegistry(): ImageGenera
}
export function loadVitestVideoGenerationProviderContractRegistry(): VideoGenerationProviderContractEntry[] {
return loadVitestCapabilityContractEntries({
const entries = loadVitestCapabilityContractEntries({
contract: "videoGenerationProviders",
pickEntries: (registry) =>
registry.videoGenerationProviders.map((entry) => ({
@@ -140,6 +216,14 @@ export function loadVitestVideoGenerationProviderContractRegistry(): VideoGenera
provider: entry.provider,
})),
});
const coveredPluginIds = new Set(entries.map((entry) => entry.pluginId));
const missingPluginIds = VITEST_CONTRACT_PLUGIN_IDS.videoGenerationProviders.filter(
(pluginId) => !coveredPluginIds.has(pluginId),
);
if (missingPluginIds.length === 0) {
return entries;
}
return [...entries, ...loadVitestVideoGenerationFallbackEntries(missingPluginIds)];
}
export function loadVitestMusicGenerationProviderContractRegistry(): MusicGenerationProviderContractEntry[] {