build(plugin-sdk): verify dist facade exports

This commit is contained in:
Peter Steinberger
2026-03-29 14:32:04 +01:00
parent 2c9bc0bb78
commit e6116769b4
3 changed files with 66 additions and 31 deletions

View File

@@ -1006,10 +1006,10 @@
"android:test:integration": "OPENCLAW_LIVE_TEST=1 OPENCLAW_LIVE_ANDROID_NODE=1 vitest run --config vitest.live.config.ts src/gateway/android-node.capabilities.live.test.ts",
"android:test:third-party": "cd apps/android && ./gradlew :app:testThirdPartyDebugUnitTest",
"audit:seams": "node scripts/audit-seams.mjs",
"build": "pnpm canvas:a2ui:bundle && node scripts/tsdown-build.mjs && node scripts/runtime-postbuild.mjs && node scripts/build-stamp.mjs && pnpm build:plugin-sdk:dts && node --import tsx scripts/write-plugin-sdk-entry-dts.ts && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/copy-export-html-templates.ts && node --import tsx scripts/write-build-info.ts && node --import tsx scripts/write-cli-startup-metadata.ts && node --import tsx scripts/write-cli-compat.ts",
"build": "pnpm canvas:a2ui:bundle && node scripts/tsdown-build.mjs && node scripts/runtime-postbuild.mjs && node scripts/build-stamp.mjs && pnpm build:plugin-sdk:dts && node --import tsx scripts/write-plugin-sdk-entry-dts.ts && node scripts/check-plugin-sdk-exports.mjs && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/copy-export-html-templates.ts && node --import tsx scripts/write-build-info.ts && node --import tsx scripts/write-cli-startup-metadata.ts && node --import tsx scripts/write-cli-compat.ts",
"build:docker": "node scripts/tsdown-build.mjs && node scripts/runtime-postbuild.mjs && node scripts/build-stamp.mjs && node --import tsx scripts/canvas-a2ui-copy.ts && node --import tsx scripts/copy-hook-metadata.ts && node --import tsx scripts/copy-export-html-templates.ts && node --import tsx scripts/write-build-info.ts && node --import tsx scripts/write-cli-startup-metadata.ts && node --import tsx scripts/write-cli-compat.ts",
"build:plugin-sdk:dts": "tsc -p tsconfig.plugin-sdk.dts.json",
"build:strict-smoke": "pnpm canvas:a2ui:bundle && node scripts/tsdown-build.mjs && node scripts/runtime-postbuild.mjs && node scripts/build-stamp.mjs && pnpm build:plugin-sdk:dts",
"build:strict-smoke": "pnpm canvas:a2ui:bundle && node scripts/tsdown-build.mjs && node scripts/runtime-postbuild.mjs && node scripts/build-stamp.mjs && pnpm build:plugin-sdk:dts && node --import tsx scripts/write-plugin-sdk-entry-dts.ts && node scripts/check-plugin-sdk-exports.mjs",
"canon:check": "node scripts/canon.mjs check",
"canon:check:json": "node scripts/canon.mjs check --json",
"canon:enforce": "node scripts/canon.mjs enforce --json",

View File

@@ -1,11 +1,11 @@
#!/usr/bin/env node
/**
* Verifies that critical plugin-sdk exports are present in the compiled dist output.
* Regression guard for #27569 where isDangerousNameMatchingEnabled was missing
* from the compiled output, breaking channel extension plugins at runtime.
* Verifies that the root plugin-sdk runtime surface and generated facade types
* are present in the compiled dist output.
*
* Run after `pnpm build` to catch missing exports before release.
* Run after `pnpm build` to catch missing root exports or leaked repo-only type
* aliases before release.
*/
import { readFileSync, existsSync } from "node:fs";
@@ -15,6 +15,15 @@ import { pluginSdkSubpaths } from "./lib/plugin-sdk-entries.mjs";
const __dirname = dirname(fileURLToPath(import.meta.url));
const distFile = resolve(__dirname, "..", "dist", "plugin-sdk", "index.js");
const generatedFacadeTypeMapDts = resolve(
__dirname,
"..",
"dist",
"plugin-sdk",
"src",
"generated",
"plugin-sdk-facade-type-map.generated.d.ts",
);
if (!existsSync(distFile)) {
console.error("ERROR: dist/plugin-sdk/index.js not found. Run `pnpm build` first.");
@@ -44,32 +53,13 @@ const exportSet = new Set(exportedNames);
const requiredRuntimeShimEntries = ["compat.js", "root-alias.cjs"];
// Critical functions that channel extension plugins import from openclaw/plugin-sdk.
// If any of these are missing, plugins will fail at runtime with:
// TypeError: (0 , _pluginSdk.<name>) is not a function
// The root plugin-sdk entry intentionally stays tiny. Keep this list aligned
// with src/plugin-sdk/index.ts runtime exports.
const requiredExports = [
"isDangerousNameMatchingEnabled",
"createAccountListHelpers",
"buildAgentMediaPayload",
"createReplyPrefixOptions",
"createTypingCallbacks",
"logInboundDrop",
"logTypingFailure",
"buildPendingHistoryContextFromMap",
"clearHistoryEntriesIfEnabled",
"recordPendingHistoryEntryIfEnabled",
"resolveControlCommandGate",
"resolveDmGroupAccessWithLists",
"resolveAllowlistProviderRuntimeGroupPolicy",
"resolveDefaultGroupPolicy",
"resolveChannelMediaMaxBytes",
"warnMissingProviderGroupPolicyFallbackOnce",
"emptyPluginConfigSchema",
"onDiagnosticEvent",
"normalizePluginHttpPath",
"registerPluginHttpRoute",
"DEFAULT_ACCOUNT_ID",
"DEFAULT_GROUP_HISTORY_LIMIT",
"registerContextEngine",
"delegateCompactionToRuntime",
];
let missing = 0;
@@ -101,12 +91,29 @@ for (const entry of requiredRuntimeShimEntries) {
}
}
if (!existsSync(generatedFacadeTypeMapDts)) {
console.error(
"MISSING GENERATED FACADE TYPE MAP DTS: dist/plugin-sdk/src/generated/plugin-sdk-facade-type-map.generated.d.ts",
);
missing += 1;
} else {
const facadeTypeMapContent = readFileSync(generatedFacadeTypeMapDts, "utf-8");
if (facadeTypeMapContent.includes("@openclaw/")) {
console.error(
"INVALID GENERATED FACADE TYPE MAP DTS: dist/plugin-sdk/src/generated/plugin-sdk-facade-type-map.generated.d.ts leaks @openclaw/* imports",
);
missing += 1;
}
}
if (missing > 0) {
console.error(
`\nERROR: ${missing} required plugin-sdk artifact(s) missing (named exports or subpath files).`,
);
console.error("This will break channel extension plugins at runtime.");
console.error("Check src/plugin-sdk/index.ts, subpath entries, and rebuild.");
console.error("This will break published plugin-sdk artifacts.");
console.error(
"Check src/plugin-sdk/index.ts, generated d.ts rewrites, subpath entries, and rebuild.",
);
process.exit(1);
}

View File

@@ -56,6 +56,32 @@ const TYPE_SHIMS: Partial<Record<string, string>> = {
].join("\n"),
};
const GENERATED_FACADE_TYPE_MAP_SOURCE = path.join(
process.cwd(),
"dist/plugin-sdk/src/generated/plugin-sdk-facade-type-map.generated.d.ts",
);
const GENERATED_FACADE_TYPE_MAP_DIST_PREFIX = "../../../extensions/";
function rewriteFacadeTypeMapSpecifier(specifier: string): string {
if (!specifier.startsWith("@openclaw/")) {
return specifier;
}
return `${GENERATED_FACADE_TYPE_MAP_DIST_PREFIX}${specifier.slice("@openclaw/".length)}`;
}
function rewriteGeneratedFacadeTypeMapDts(): void {
if (!fs.existsSync(GENERATED_FACADE_TYPE_MAP_SOURCE)) {
return;
}
const source = fs.readFileSync(GENERATED_FACADE_TYPE_MAP_SOURCE, "utf8");
const rewritten = source.replace(/@openclaw\/([a-z0-9-]+\/[^")\s]+)/g, (_match, suffix: string) =>
rewriteFacadeTypeMapSpecifier(`@openclaw/${suffix}`),
);
if (rewritten !== source) {
fs.writeFileSync(GENERATED_FACADE_TYPE_MAP_SOURCE, rewritten, "utf8");
}
}
// `tsc` emits declarations under `dist/plugin-sdk/src/plugin-sdk/*` because the source lives
// at `src/plugin-sdk/*` and `rootDir` is `.` (repo root, to support cross-src/extensions refs).
//
@@ -78,3 +104,5 @@ for (const entry of pluginSdkEntrypoints) {
fs.mkdirSync(path.dirname(runtimeOut), { recursive: true });
fs.writeFileSync(runtimeOut, runtimeShim, "utf8");
}
rewriteGeneratedFacadeTypeMapDts();