mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
* perf(test): run memory plugin PR tests on Bun * fix(ci): keep static release planning independent of test runtimes
205 lines
6.2 KiB
TypeScript
205 lines
6.2 KiB
TypeScript
// Defines the plugin prerelease validation surface and matching test lanes.
|
|
import { BUNDLED_PLUGIN_INSTALL_UNINSTALL_SHARDS } from "./docker-e2e-scenarios.mts";
|
|
import type { ExtensionTestPlanGroup } from "./extension-test-plan.mts";
|
|
|
|
type PrereleaseSurfaceEntry = { surfaces: readonly string[] };
|
|
|
|
/** Required behavioral surfaces that plugin prerelease validation must cover. */
|
|
const PLUGIN_PRERELEASE_REQUIRED_SURFACES = Object.freeze([
|
|
"package-artifact",
|
|
"bundled-lifecycle",
|
|
"external-plugins",
|
|
"update-no-op",
|
|
"installed-plugin-deps",
|
|
"doctor-fix",
|
|
"config-round-trip",
|
|
"gateway-bootstrap",
|
|
"sdk-compatibility",
|
|
"external-install-boundary",
|
|
"status-diagnostics",
|
|
"npm-registry-plugin",
|
|
"clawhub-registry-plugin",
|
|
"resource-guardrails",
|
|
"plugin-gateway-rpc",
|
|
"live-ish-availability",
|
|
]);
|
|
|
|
const pluginPrereleaseDockerLanes = Object.freeze([
|
|
{
|
|
lane: "npm-onboard-channel-agent",
|
|
surfaces: ["package-artifact", "gateway-bootstrap", "status-diagnostics"],
|
|
},
|
|
{
|
|
lane: "npm-onboard-discord-candidate-channel-agent",
|
|
surfaces: [
|
|
"package-artifact",
|
|
"external-plugins",
|
|
"installed-plugin-deps",
|
|
"gateway-bootstrap",
|
|
"status-diagnostics",
|
|
],
|
|
},
|
|
{
|
|
lane: "npm-onboard-slack-candidate-channel-agent",
|
|
surfaces: ["package-artifact", "gateway-bootstrap", "status-diagnostics"],
|
|
},
|
|
{
|
|
lane: "doctor-switch",
|
|
surfaces: ["package-artifact", "doctor-fix"],
|
|
},
|
|
{
|
|
lane: "update-channel-switch",
|
|
surfaces: ["package-artifact", "installed-plugin-deps", "update-no-op"],
|
|
},
|
|
{
|
|
lane: "plugins-offline",
|
|
surfaces: ["external-plugins", "sdk-compatibility", "status-diagnostics"],
|
|
},
|
|
{
|
|
lane: "plugins",
|
|
surfaces: [
|
|
"external-plugins",
|
|
"sdk-compatibility",
|
|
"external-install-boundary",
|
|
"status-diagnostics",
|
|
],
|
|
},
|
|
{
|
|
lane: "kitchen-sink-plugin",
|
|
surfaces: [
|
|
"external-plugins",
|
|
"sdk-compatibility",
|
|
"external-install-boundary",
|
|
"status-diagnostics",
|
|
"npm-registry-plugin",
|
|
"clawhub-registry-plugin",
|
|
"resource-guardrails",
|
|
],
|
|
},
|
|
{
|
|
lane: "kitchen-sink-rpc",
|
|
surfaces: [
|
|
"external-plugins",
|
|
"sdk-compatibility",
|
|
"gateway-bootstrap",
|
|
"status-diagnostics",
|
|
"npm-registry-plugin",
|
|
"resource-guardrails",
|
|
"plugin-gateway-rpc",
|
|
],
|
|
},
|
|
{
|
|
lane: "plugin-update",
|
|
surfaces: ["package-artifact", "update-no-op"],
|
|
},
|
|
{
|
|
lane: "config-reload",
|
|
surfaces: ["config-round-trip", "gateway-bootstrap"],
|
|
},
|
|
{
|
|
lane: "gateway-network",
|
|
surfaces: ["gateway-bootstrap", "status-diagnostics"],
|
|
},
|
|
{
|
|
lane: "mcp-channels",
|
|
surfaces: ["gateway-bootstrap", "status-diagnostics"],
|
|
},
|
|
{
|
|
lane: "cron-mcp-cleanup",
|
|
surfaces: ["gateway-bootstrap", "status-diagnostics"],
|
|
},
|
|
...Array.from({ length: BUNDLED_PLUGIN_INSTALL_UNINSTALL_SHARDS }, (_, index) => ({
|
|
lane: `bundled-plugin-install-uninstall-${index}`,
|
|
surfaces: ["bundled-lifecycle", "package-artifact", "status-diagnostics"],
|
|
})),
|
|
]);
|
|
|
|
const staticChecks = Object.freeze([
|
|
{
|
|
check: "test:extensions:package-boundary:compile",
|
|
checkName: "checks-plugin-prerelease-package-boundary-compile",
|
|
command: "pnpm run test:extensions:package-boundary:compile",
|
|
surfaces: ["package-artifact", "sdk-compatibility"],
|
|
},
|
|
{
|
|
check: "test:extensions:package-boundary:canary",
|
|
checkName: "checks-plugin-prerelease-package-boundary-canary",
|
|
command: "pnpm run test:extensions:package-boundary:canary",
|
|
surfaces: ["package-artifact", "sdk-compatibility"],
|
|
},
|
|
{
|
|
check: "live-ish-availability",
|
|
checkName: "checks-plugin-prerelease-live-ish-availability",
|
|
command: "node --import tsx scripts/plugin-prerelease-liveish-matrix.mts",
|
|
surfaces: ["live-ish-availability"],
|
|
},
|
|
]);
|
|
|
|
function coveredSurfaces(entries: readonly PrereleaseSurfaceEntry[]): string[] {
|
|
return [
|
|
...new Set(
|
|
entries
|
|
.flatMap((entry) => entry.surfaces)
|
|
.filter((surface) => typeof surface === "string" && surface.length > 0),
|
|
),
|
|
].toSorted((a, b) => a.localeCompare(b));
|
|
}
|
|
|
|
/** Keep each release batch on Node and add only its qualified Bun groups. */
|
|
export async function resolvePluginPrereleaseExtensionRuntime({
|
|
planGroups,
|
|
fullReleaseValidation,
|
|
vitestArgs = [],
|
|
}: {
|
|
planGroups: readonly Pick<ExtensionTestPlanGroup, "config" | "roots">[];
|
|
fullReleaseValidation: boolean;
|
|
vitestArgs?: readonly string[];
|
|
}): Promise<{ test_runtime_policy: "dual" | "node"; requires_bun: boolean }> {
|
|
if (!fullReleaseValidation) {
|
|
return { test_runtime_policy: "node", requires_bun: false };
|
|
}
|
|
// Static release plans also run from bounded tooling copies without test inventories.
|
|
const { ciTestShardRequiresBun } = await import("./ci-test-runtime.mts");
|
|
const requiresBun = ciTestShardRequiresBun(
|
|
{
|
|
groups: planGroups.map(({ config, roots }) => ({
|
|
configs: [config],
|
|
includePatterns: roots.map((root) =>
|
|
/\.test\.tsx?$/u.test(root) ? root : `${root}/**/*.test.ts`,
|
|
),
|
|
vitestArgs,
|
|
})),
|
|
},
|
|
"dual",
|
|
);
|
|
return { test_runtime_policy: requiresBun ? "dual" : "node", requires_bun: requiresBun };
|
|
}
|
|
|
|
/** Build the plugin prerelease plan from Docker lanes and static checks. */
|
|
export function createPluginPrereleaseTestPlan() {
|
|
const dockerLanes = pluginPrereleaseDockerLanes.map((entry) => entry.lane);
|
|
const allEntries = [...pluginPrereleaseDockerLanes, ...staticChecks];
|
|
return {
|
|
dockerLanes,
|
|
staticChecks: staticChecks.map((entry) => ({
|
|
check: entry.check,
|
|
checkName: entry.checkName,
|
|
command: entry.command,
|
|
surfaces: entry.surfaces.slice(),
|
|
})),
|
|
surfaces: coveredSurfaces(allEntries),
|
|
};
|
|
}
|
|
|
|
/** Assert that a plugin prerelease plan covers every required surface. */
|
|
export function assertPluginPrereleaseTestPlanComplete(
|
|
plan: ReturnType<typeof createPluginPrereleaseTestPlan> = createPluginPrereleaseTestPlan(),
|
|
) {
|
|
const missing = PLUGIN_PRERELEASE_REQUIRED_SURFACES.filter(
|
|
(surface) => !plan.surfaces.includes(surface),
|
|
);
|
|
if (missing.length > 0) {
|
|
throw new Error(`Plugin prerelease test plan is missing surfaces: ${missing.join(", ")}`);
|
|
}
|
|
return plan;
|
|
}
|