mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-22 09:58:16 +00:00
test(plugin-sdk): ratchet surface budget checks
This commit is contained in:
3
.github/workflows/workflow-sanity.yml
vendored
3
.github/workflows/workflow-sanity.yml
vendored
@@ -251,3 +251,6 @@ jobs:
|
||||
|
||||
- name: Check plugin SDK API baseline drift
|
||||
run: pnpm plugin-sdk:api:check
|
||||
|
||||
- name: Check plugin SDK surface budget
|
||||
run: pnpm plugin-sdk:surface:check
|
||||
|
||||
@@ -37,21 +37,145 @@ function readBudgetEnv(name, fallback) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function readEntrypointBudgetEnv(name, fallback) {
|
||||
const raw = process.env[name];
|
||||
if (raw === undefined) {
|
||||
return fallback;
|
||||
}
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch {
|
||||
throw new Error(`${name} must be a JSON object of entrypoint integer budgets`);
|
||||
}
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
throw new Error(`${name} must be a JSON object of entrypoint integer budgets`);
|
||||
}
|
||||
|
||||
const overrides = {};
|
||||
for (const [entrypoint, value] of Object.entries(parsed)) {
|
||||
if (!Number.isSafeInteger(value) || value < 0) {
|
||||
throw new Error(`${name}.${entrypoint} must be a safe non-negative integer`);
|
||||
}
|
||||
overrides[entrypoint] = value;
|
||||
}
|
||||
return Object.freeze({ ...fallback, ...overrides });
|
||||
}
|
||||
|
||||
const defaultPublicDeprecatedExportsByEntrypointBudget = Object.freeze({
|
||||
core: 2,
|
||||
lmstudio: 1,
|
||||
"provider-setup": 1,
|
||||
"self-hosted-provider-setup": 14,
|
||||
routing: 1,
|
||||
runtime: 3,
|
||||
"runtime-logger": 3,
|
||||
"runtime-secret-resolution": 5,
|
||||
"setup-adapter-runtime": 1,
|
||||
"channel-streaming": 47,
|
||||
"approval-reply-runtime": 1,
|
||||
"config-runtime": 123,
|
||||
"config-contracts": 1,
|
||||
"config-types": 415,
|
||||
"config-schema": 3,
|
||||
"reply-dedupe": 1,
|
||||
"inbound-reply-dispatch": 33,
|
||||
"channel-reply-pipeline": 12,
|
||||
"channel-reply-options-runtime": 2,
|
||||
"channel-runtime": 144,
|
||||
"interactive-runtime": 13,
|
||||
"outbound-send-deps": 4,
|
||||
"outbound-runtime": 16,
|
||||
"file-access-runtime": 2,
|
||||
"infra-runtime": 570,
|
||||
"ssrf-policy": 1,
|
||||
"ssrf-runtime": 1,
|
||||
"media-runtime": 2,
|
||||
"text-runtime": 188,
|
||||
"agent-runtime": 7,
|
||||
"plugin-runtime": 13,
|
||||
"channel-secret-runtime": 23,
|
||||
"secret-file-runtime": 1,
|
||||
"security-runtime": 7,
|
||||
"agent-harness": 7,
|
||||
"agent-harness-runtime": 7,
|
||||
types: 6,
|
||||
"agent-config-primitives": 2,
|
||||
"command-auth": 81,
|
||||
compat: 152,
|
||||
"direct-dm": 9,
|
||||
"direct-dm-access": 5,
|
||||
discord: 48,
|
||||
mattermost: 7,
|
||||
matrix: 1,
|
||||
"channel-config-schema-legacy": 22,
|
||||
"channel-actions": 2,
|
||||
"channel-envelope": 3,
|
||||
"channel-inbound": 21,
|
||||
"channel-inbound-roots": 1,
|
||||
"channel-logging": 4,
|
||||
"channel-location": 4,
|
||||
"channel-mention-gating": 7,
|
||||
"channel-lifecycle": 23,
|
||||
"channel-ingress": 8,
|
||||
"channel-message": 228,
|
||||
"channel-message-runtime": 225,
|
||||
"channel-pairing-paths": 1,
|
||||
"channel-policy": 8,
|
||||
"channel-route": 5,
|
||||
"session-store-runtime": 1,
|
||||
"group-access": 13,
|
||||
"media-generation-runtime-shared": 3,
|
||||
"music-generation-core": 20,
|
||||
"reply-history": 8,
|
||||
"messaging-targets": 12,
|
||||
"memory-core": 45,
|
||||
"memory-core-engine-runtime": 15,
|
||||
"memory-core-host-multimodal": 3,
|
||||
"memory-core-host-query": 2,
|
||||
"memory-core-host-events": 11,
|
||||
"memory-core-host-status": 1,
|
||||
"memory-core-host-runtime-core": 1,
|
||||
"memory-host-core": 1,
|
||||
"memory-host-files": 7,
|
||||
"memory-host-status": 72,
|
||||
"provider-auth": 20,
|
||||
"provider-oauth-runtime": 2,
|
||||
"provider-auth-login": 3,
|
||||
"provider-model-shared": 29,
|
||||
"provider-stream-family": 40,
|
||||
"provider-stream-shared": 28,
|
||||
"provider-stream": 40,
|
||||
"provider-web-search": 1,
|
||||
"provider-zai-endpoint": 3,
|
||||
"telegram-account": 3,
|
||||
"telegram-command-config": 7,
|
||||
"webhook-ingress": 2,
|
||||
"webhook-path": 2,
|
||||
zalouser: 5,
|
||||
zod: 282,
|
||||
});
|
||||
|
||||
let budgets;
|
||||
let publicDeprecatedExportsByEntrypointBudget;
|
||||
try {
|
||||
budgets = {
|
||||
publicEntrypoints: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_ENTRYPOINTS", 308),
|
||||
publicExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_EXPORTS", 9920),
|
||||
publicFunctionExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_FUNCTION_EXPORTS", 5031),
|
||||
publicEntrypoints: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_ENTRYPOINTS", 319),
|
||||
publicExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_EXPORTS", 10269),
|
||||
publicFunctionExports: readBudgetEnv("OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_FUNCTION_EXPORTS", 5159),
|
||||
publicDeprecatedExports: readBudgetEnv(
|
||||
"OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_DEPRECATED_EXPORTS",
|
||||
3143,
|
||||
3230,
|
||||
),
|
||||
publicWildcardReexports: readBudgetEnv(
|
||||
"OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_WILDCARD_REEXPORTS",
|
||||
215,
|
||||
),
|
||||
};
|
||||
publicDeprecatedExportsByEntrypointBudget = readEntrypointBudgetEnv(
|
||||
"OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_DEPRECATED_EXPORTS_BY_ENTRYPOINT",
|
||||
defaultPublicDeprecatedExportsByEntrypointBudget,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
@@ -194,6 +318,19 @@ function formatStats(label, stats) {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function collectDeprecatedEntrypointBudgetFailures(byEntrypoint) {
|
||||
const failures = [];
|
||||
for (const [entrypoint, stats] of byEntrypoint) {
|
||||
const budget = publicDeprecatedExportsByEntrypointBudget[entrypoint] ?? 0;
|
||||
if (stats.deprecatedExports > budget) {
|
||||
failures.push(
|
||||
`public deprecated exports in ${entrypoint} ${stats.deprecatedExports} > ${budget}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
const allStats = collectExportStats(pluginSdkEntrypoints);
|
||||
const publicStats = collectExportStats(publicPluginSdkEntrypoints);
|
||||
const localOnlyStats = collectExportStats(privateLocalOnlyPluginSdkEntrypoints);
|
||||
@@ -246,6 +383,7 @@ if (publicStats.totals.deprecatedExports > budgets.publicDeprecatedExports) {
|
||||
`public deprecated exports ${publicStats.totals.deprecatedExports} > ${budgets.publicDeprecatedExports}`,
|
||||
);
|
||||
}
|
||||
failures.push(...collectDeprecatedEntrypointBudgetFailures(publicStats.byEntrypoint));
|
||||
if (publicWildcards.count > budgets.publicWildcardReexports) {
|
||||
failures.push(
|
||||
`public wildcard reexports ${publicWildcards.count} > ${budgets.publicWildcardReexports}`,
|
||||
|
||||
@@ -29,6 +29,7 @@ const checkCommands = [
|
||||
{ name: "config docs baseline", args: ["config:docs:check"] },
|
||||
{ name: "plugin SDK exports", args: ["plugin-sdk:check-exports"] },
|
||||
{ name: "plugin SDK API baseline", args: ["plugin-sdk:api:check"] },
|
||||
{ name: "plugin SDK surface budget", args: ["plugin-sdk:surface:check"] },
|
||||
];
|
||||
|
||||
if (fix) {
|
||||
|
||||
@@ -78,7 +78,13 @@ export type { OutboundSendDeps } from "../infra/outbound/send-deps.js";
|
||||
export { sanitizeForPlainText } from "../infra/outbound/sanitize-text.js";
|
||||
export { logAckFailure, logTypingFailure } from "../channels/logging.js";
|
||||
export * from "../channels/streaming.js";
|
||||
export * from "../channels/progress-draft-compositor.js";
|
||||
export {
|
||||
createChannelProgressDraftCompositor,
|
||||
type ChannelProgressDraftCompositor,
|
||||
type ChannelProgressDraftCompositorLine,
|
||||
type ChannelProgressDraftMode,
|
||||
type ChannelProgressDraftUpdateOptions,
|
||||
} from "../channels/progress-draft-compositor.js";
|
||||
export {
|
||||
classifyDurableSendRecoveryState,
|
||||
createChannelMessageAdapterFromOutbound,
|
||||
|
||||
@@ -131,6 +131,21 @@ describe("ci workflow guards", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("runs plugin SDK API and surface drift checks in workflow sanity", () => {
|
||||
const workflow = readWorkflowSanityWorkflow();
|
||||
const steps = workflow.jobs["generated-doc-baselines"].steps;
|
||||
const stepNames = steps.map((step) => step.name);
|
||||
|
||||
expect(stepNames).toContain("Check plugin SDK API baseline drift");
|
||||
expect(stepNames).toContain("Check plugin SDK surface budget");
|
||||
expect(stepNames.indexOf("Check plugin SDK API baseline drift")).toBeLessThan(
|
||||
stepNames.indexOf("Check plugin SDK surface budget"),
|
||||
);
|
||||
expect(steps.find((step) => step.name === "Check plugin SDK surface budget").run).toBe(
|
||||
"pnpm plugin-sdk:surface:check",
|
||||
);
|
||||
});
|
||||
|
||||
it("bounds platform checkout fetches without GNU timeout", () => {
|
||||
const workflow = readCiWorkflow();
|
||||
|
||||
|
||||
@@ -39,4 +39,22 @@ describe("plugin SDK surface report", () => {
|
||||
);
|
||||
expect(result.stderr).not.toContain("at ");
|
||||
});
|
||||
|
||||
it("accepts exact deprecated export budget overrides by public entrypoint", () => {
|
||||
const result = runSurfaceReport({
|
||||
OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_DEPRECATED_EXPORTS_BY_ENTRYPOINT: JSON.stringify({ core: 2 }),
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stderr).toBe("");
|
||||
});
|
||||
|
||||
it("rejects deprecated export growth by public entrypoint", () => {
|
||||
const result = runSurfaceReport({
|
||||
OPENCLAW_PLUGIN_SDK_MAX_PUBLIC_DEPRECATED_EXPORTS_BY_ENTRYPOINT: JSON.stringify({ core: 1 }),
|
||||
});
|
||||
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain("public deprecated exports in core 2 > 1");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user