refactor: remove plugin sdk facade generator

This commit is contained in:
Peter Steinberger
2026-04-05 09:23:42 +01:00
parent a4b767c89b
commit 8be017fae6
40 changed files with 97 additions and 1341 deletions

View File

@@ -11,7 +11,6 @@ import {
visitModuleSpecifiers,
writeLine,
} from "./lib/guard-inventory-utils.mjs";
import { GENERATED_PLUGIN_SDK_FACADES_SCRIPT } from "./lib/plugin-sdk-facades.mjs";
import {
collectTypeScriptFilesFromRoots,
resolveSourceRoots,
@@ -42,9 +41,6 @@ function scanPluginSdkExtensionFacadeSmells(sourceFile, filePath) {
if (!relativeFile.startsWith("src/plugin-sdk/")) {
return [];
}
if (sourceFile.text.includes(`Generated by ${GENERATED_PLUGIN_SDK_FACADES_SCRIPT}`)) {
return [];
}
const entries = [];

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env node
/**
* Verifies that the root plugin-sdk runtime surface and generated facade types
* are present in the compiled dist output.
* Verifies that the root plugin-sdk runtime surface is present in the compiled
* dist output.
*
* Run after `pnpm build` to catch missing root exports or leaked repo-only type
* aliases before release.
@@ -15,16 +15,6 @@ 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.");
process.exit(1);
@@ -91,21 +81,6 @@ 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).`,

View File

@@ -1,95 +0,0 @@
#!/usr/bin/env node
import path from "node:path";
import { pathToFileURL } from "node:url";
import { formatGeneratedModule } from "./lib/format-generated-module.mjs";
import { writeGeneratedOutput } from "./lib/generated-output-utils.mjs";
import {
GENERATED_PLUGIN_SDK_FACADES,
GENERATED_PLUGIN_SDK_FACADES_LABEL,
GENERATED_PLUGIN_SDK_FACADE_TYPES_OUTPUT,
buildPluginSdkFacadeTypeMapModule,
buildPluginSdkFacadeModule,
} from "./lib/plugin-sdk-facades.mjs";
function parseArgs(argv) {
const check = argv.includes("--check");
const write = argv.includes("--write");
if (check === write) {
throw new Error("Use exactly one of --check or --write.");
}
return { check };
}
export function generatePluginSdkFacades(params) {
const results = [];
const typeMapOutputPath = GENERATED_PLUGIN_SDK_FACADE_TYPES_OUTPUT;
const typeMapNext = formatGeneratedModule(
buildPluginSdkFacadeTypeMapModule(GENERATED_PLUGIN_SDK_FACADES),
{
repoRoot: params.repoRoot,
outputPath: typeMapOutputPath,
errorLabel: `${GENERATED_PLUGIN_SDK_FACADES_LABEL}:type-map`,
},
);
results.push(
writeGeneratedOutput({
repoRoot: params.repoRoot,
outputPath: typeMapOutputPath,
next: typeMapNext,
check: params.check,
}),
);
for (const entry of GENERATED_PLUGIN_SDK_FACADES) {
const outputPath = `src/plugin-sdk/${entry.subpath}.ts`;
const next = formatGeneratedModule(
buildPluginSdkFacadeModule(entry, { repoRoot: params.repoRoot }),
{
repoRoot: params.repoRoot,
outputPath,
errorLabel: `${GENERATED_PLUGIN_SDK_FACADES_LABEL}:${entry.subpath}`,
},
);
results.push(
writeGeneratedOutput({
repoRoot: params.repoRoot,
outputPath,
next,
check: params.check,
}),
);
}
return results;
}
async function main(argv = process.argv.slice(2)) {
const { check } = parseArgs(argv);
const repoRoot = process.cwd();
const results = generatePluginSdkFacades({ repoRoot, check });
const changed = results.filter((entry) => entry.changed);
if (changed.length === 0) {
console.log(`[${GENERATED_PLUGIN_SDK_FACADES_LABEL}] up to date`);
return;
}
if (check) {
for (const result of changed) {
console.error(
`[${GENERATED_PLUGIN_SDK_FACADES_LABEL}] stale generated output at ${path.relative(repoRoot, result.outputPath)}`,
);
}
process.exitCode = 1;
return;
}
for (const result of changed) {
console.log(
`[${GENERATED_PLUGIN_SDK_FACADES_LABEL}] wrote ${path.relative(repoRoot, result.outputPath)}`,
);
}
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
await main();
}

View File

@@ -73,7 +73,6 @@
"allowlist-config-edit",
"bluebubbles",
"bluebubbles-policy",
"browser",
"browser-config-support",
"browser-support",
"boolean-param",

View File

@@ -1,727 +0,0 @@
import fs from "node:fs";
import path from "node:path";
import ts from "typescript";
import { bundledPluginFile } from "./bundled-plugin-paths.mjs";
function pluginSource(dirName, artifactBasename = "api.js") {
return `@openclaw/${dirName}/${artifactBasename}`;
}
function runtimeApiSourcePath(dirName) {
return bundledPluginFile(dirName, "runtime-api.ts");
}
export const GENERATED_PLUGIN_SDK_FACADES = [
{
subpath: "anthropic-cli",
source: pluginSource("anthropic", "api.js"),
exports: ["CLAUDE_CLI_BACKEND_ID", "isClaudeCliProvider"],
},
{
subpath: "bluebubbles-policy",
source: pluginSource("bluebubbles", "api.js"),
exports: [
"isAllowedBlueBubblesSender",
"resolveBlueBubblesGroupRequireMention",
"resolveBlueBubblesGroupToolPolicy",
],
},
{
subpath: "browser",
source: pluginSource("browser", "runtime-api.js"),
loadPolicy: "activated",
exports: [
"browserHandlers",
"createBrowserPluginService",
"createBrowserTool",
"handleBrowserGatewayRequest",
"registerBrowserCli",
],
},
{
subpath: "feishu-conversation",
source: pluginSource("feishu", "api.js"),
exports: [
"buildFeishuConversationId",
"createFeishuThreadBindingManager",
"feishuSessionBindingAdapterChannels",
"feishuThreadBindingTesting",
"parseFeishuDirectConversationId",
"parseFeishuConversationId",
"parseFeishuTargetId",
],
},
{
subpath: "feishu-setup",
source: pluginSource("feishu", "api.js"),
exports: ["feishuSetupAdapter", "feishuSetupWizard"],
},
{
subpath: "github-copilot-login",
source: pluginSource("github-copilot", "api.js"),
exports: ["githubCopilotLoginCommand"],
},
{
subpath: "image-generation-runtime",
source: pluginSource("image-generation-core", "runtime-api.js"),
loadPolicy: "activated",
exports: [
"generateImage",
"listRuntimeImageGenerationProviders",
"GenerateImageParams",
"GenerateImageRuntimeResult",
],
typeExports: ["GenerateImageParams", "GenerateImageRuntimeResult"],
},
{
subpath: "irc-surface",
source: pluginSource("irc", "api.js"),
exports: [
"ircSetupAdapter",
"ircSetupWizard",
"listIrcAccountIds",
"resolveDefaultIrcAccountId",
"resolveIrcAccount",
],
},
{
subpath: "memory-core-engine-runtime",
source: pluginSource("memory-core", "runtime-api.js"),
loadPolicy: "activated",
exports: [
"auditShortTermPromotionArtifacts",
"BuiltinMemoryEmbeddingProviderDoctorMetadata",
"getBuiltinMemoryEmbeddingProviderDoctorMetadata",
"getMemorySearchManager",
"listBuiltinAutoSelectMemoryEmbeddingProviderDoctorMetadata",
"MemoryIndexManager",
"repairShortTermPromotionArtifacts",
],
typeExports: [
"BuiltinMemoryEmbeddingProviderDoctorMetadata",
"RepairShortTermPromotionArtifactsResult",
"ShortTermAuditSummary",
],
},
{
subpath: "mattermost-policy",
source: pluginSource("mattermost", "api.js"),
exports: ["isMattermostSenderAllowed"],
},
{
subpath: "litellm",
source: pluginSource("litellm", "api.js"),
exports: [
"applyLitellmConfig",
"applyLitellmProviderConfig",
"buildLitellmModelDefinition",
"LITELLM_BASE_URL",
"LITELLM_DEFAULT_MODEL_ID",
"LITELLM_DEFAULT_MODEL_REF",
],
},
{
subpath: "line-runtime",
source: pluginSource("line", "runtime-api.js"),
loadPolicy: "activated",
runtimeApiPreExportsPath: runtimeApiSourcePath("line"),
typeExports: [
"Action",
"CardAction",
"CreateRichMenuParams",
"FlexBox",
"FlexBubble",
"FlexButton",
"FlexCarousel",
"FlexComponent",
"FlexContainer",
"FlexImage",
"FlexText",
"LineChannelData",
"LineConfig",
"LineProbeResult",
"ListItem",
"ResolvedLineAccount",
"RichMenuArea",
"RichMenuAreaRequest",
"RichMenuRequest",
"RichMenuResponse",
"RichMenuSize",
],
},
{
subpath: "line-surface",
source: pluginSource("line", "runtime-api.js"),
// This surface is also used by passive reply normalization helpers.
// Keep it loadable without requiring the LINE plugin to be activated.
exports: [
"CardAction",
"createActionCard",
"createAgendaCard",
"createAppleTvRemoteCard",
"createDeviceControlCard",
"createEventCard",
"createImageCard",
"createInfoCard",
"createListCard",
"createMediaPlayerCard",
"createReceiptCard",
"LineChannelData",
"LineConfig",
"LineConfigSchema",
"LineProbeResult",
"listLineAccountIds",
"ListItem",
"normalizeAccountId",
"processLineMessage",
"ResolvedLineAccount",
"resolveDefaultLineAccountId",
"resolveExactLineGroupConfigKey",
"resolveLineAccount",
],
typeExports: [
"CardAction",
"LineChannelData",
"LineConfig",
"LineProbeResult",
"ListItem",
"ResolvedLineAccount",
],
},
{
subpath: "matrix-helper",
source: pluginSource("matrix", "api.js"),
exports: [
"findMatrixAccountEntry",
"getMatrixScopedEnvVarNames",
"requiresExplicitMatrixDefaultAccount",
"resolveConfiguredMatrixAccountIds",
"resolveMatrixAccountStorageRoot",
"resolveMatrixChannelConfig",
"resolveMatrixCredentialsDir",
"resolveMatrixCredentialsPath",
"resolveMatrixDefaultOrOnlyAccountId",
"resolveMatrixLegacyFlatStoragePaths",
],
},
{
subpath: "matrix-runtime-surface",
source: pluginSource("matrix", "runtime-api.js"),
loadPolicy: "activated",
exports: ["resolveMatrixAccountStringValues", "setMatrixRuntime"],
},
{
subpath: "matrix-surface",
source: pluginSource("matrix", "api.js"),
exports: [
"createMatrixThreadBindingManager",
"matrixSessionBindingAdapterChannels",
"resetMatrixThreadBindingsForTests",
],
},
{
subpath: "matrix-thread-bindings",
source: pluginSource("matrix", "api.js"),
exports: [
"setMatrixThreadBindingIdleTimeoutBySessionKey",
"setMatrixThreadBindingMaxAgeBySessionKey",
],
},
{
subpath: "openrouter",
source: pluginSource("openrouter", "api.js"),
exports: [
"applyOpenrouterConfig",
"applyOpenrouterProviderConfig",
"buildOpenrouterProvider",
"OPENROUTER_DEFAULT_MODEL_REF",
],
},
{
subpath: "vercel-ai-gateway",
source: pluginSource("vercel-ai-gateway", "api.js"),
exports: [
"buildVercelAiGatewayProvider",
"discoverVercelAiGatewayModels",
"getStaticVercelAiGatewayModelCatalog",
"VERCEL_AI_GATEWAY_BASE_URL",
"VERCEL_AI_GATEWAY_DEFAULT_CONTEXT_WINDOW",
"VERCEL_AI_GATEWAY_DEFAULT_COST",
"VERCEL_AI_GATEWAY_DEFAULT_MAX_TOKENS",
"VERCEL_AI_GATEWAY_DEFAULT_MODEL_ID",
"VERCEL_AI_GATEWAY_DEFAULT_MODEL_REF",
"VERCEL_AI_GATEWAY_PROVIDER_ID",
],
},
{
subpath: "xiaomi",
source: pluginSource("xiaomi", "api.js"),
exports: [
"applyXiaomiConfig",
"applyXiaomiProviderConfig",
"buildXiaomiProvider",
"XIAOMI_DEFAULT_MODEL_ID",
"XIAOMI_DEFAULT_MODEL_REF",
],
},
{
subpath: "zai",
source: pluginSource("zai", "api.js"),
exports: [
"applyZaiConfig",
"applyZaiProviderConfig",
"ZAI_CN_BASE_URL",
"ZAI_CODING_CN_BASE_URL",
"ZAI_CODING_GLOBAL_BASE_URL",
"ZAI_DEFAULT_MODEL_ID",
"ZAI_DEFAULT_MODEL_REF",
"ZAI_GLOBAL_BASE_URL",
],
},
{
subpath: "zalo-setup",
source: pluginSource("zalo", "api.js"),
exports: [
"evaluateZaloGroupAccess",
"resolveZaloRuntimeGroupPolicy",
"zaloSetupAdapter",
"zaloSetupWizard",
],
},
];
export const GENERATED_PLUGIN_SDK_FACADES_BY_SUBPATH = Object.fromEntries(
GENERATED_PLUGIN_SDK_FACADES.map((entry) => [entry.subpath, entry]),
);
function resolveFacadeLoadPolicy(entry, sourcePath) {
// Keep loader policy next to the facade entry itself so additions stay local
// and mixed-source facades can opt into per-source behavior later if needed.
const sourcePolicy = entry.sourceLoadPolicy?.[sourcePath];
if (sourcePolicy) {
return sourcePolicy;
}
return entry.loadPolicy ?? "plain";
}
export const GENERATED_PLUGIN_SDK_FACADES_LABEL = "plugin-sdk-facades";
export const GENERATED_PLUGIN_SDK_FACADES_SCRIPT = "scripts/generate-plugin-sdk-facades.mjs";
export const GENERATED_PLUGIN_SDK_FACADE_TYPES_OUTPUT =
"src/generated/plugin-sdk-facade-type-map.generated.ts";
function rewriteFacadeTypeImportSpecifier(sourcePath) {
return sourcePath;
}
const MODULE_RESOLUTION_OPTIONS = {
allowJs: true,
checkJs: false,
jsx: ts.JsxEmit.Preserve,
module: ts.ModuleKind.ESNext,
moduleResolution: ts.ModuleResolutionKind.Bundler,
skipLibCheck: true,
target: ts.ScriptTarget.ESNext,
};
const MODULE_RESOLUTION_HOST = ts.createCompilerHost(MODULE_RESOLUTION_OPTIONS, true);
const moduleResolutionContextCache = new Map();
const sourceExportKindsCache = new Map();
function listFacadeEntrySourcePaths(entry) {
return Array.from(new Set([entry.source, ...Object.values(entry.exportSources ?? {})]));
}
function buildFacadeSourceModuleKey(sourceIndex) {
return `source${sourceIndex + 1}`;
}
function isPrimitiveTypeLike(type) {
if (type.isUnion()) {
return type.types.every((member) => isPrimitiveTypeLike(member));
}
const primitiveFlags =
ts.TypeFlags.StringLike |
ts.TypeFlags.NumberLike |
ts.TypeFlags.BooleanLike |
ts.TypeFlags.BigIntLike |
ts.TypeFlags.ESSymbolLike |
ts.TypeFlags.Null |
ts.TypeFlags.Undefined |
ts.TypeFlags.Void;
return Boolean(type.flags & primitiveFlags);
}
function isArrayTypeLike(checker, type) {
if (type.isUnion()) {
return type.types.every((member) => isArrayTypeLike(checker, member));
}
return checker.isArrayType(type) || checker.isTupleType(type);
}
function normalizeFacadeSourceParts(sourcePath) {
const packageSourceMatch = /^@openclaw\/([^/]+)\/([^/]+)$/u.exec(sourcePath);
if (packageSourceMatch) {
return {
dirName: packageSourceMatch[1],
artifactBasename: packageSourceMatch[2],
};
}
const match = /^\.\.\/\.\.\/extensions\/([^/]+)\/([^/]+)$/u.exec(sourcePath);
if (!match) {
throw new Error(`Unsupported plugin-sdk facade source: ${sourcePath}`);
}
return {
dirName: match[1],
artifactBasename: match[2],
};
}
function collectRuntimeApiPreExports(repoRoot, runtimeApiPath) {
const absolutePath = path.join(repoRoot, runtimeApiPath);
const sourceText = fs.readFileSync(absolutePath, "utf8");
const sourceFile = ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true);
const exportNames = new Set();
for (const statement of sourceFile.statements) {
if (!ts.isExportDeclaration(statement)) {
continue;
}
const moduleSpecifier =
statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)
? statement.moduleSpecifier.text
: undefined;
if (!moduleSpecifier) {
continue;
}
if (statement.isTypeOnly) {
continue;
}
if (moduleSpecifier === "openclaw/plugin-sdk/line-runtime") {
break;
}
if (!moduleSpecifier.startsWith("./src/")) {
continue;
}
if (!statement.exportClause || !ts.isNamedExports(statement.exportClause)) {
continue;
}
for (const element of statement.exportClause.elements) {
if (!element.isTypeOnly) {
exportNames.add(element.name.text);
}
}
}
return Array.from(exportNames).toSorted((left, right) => left.localeCompare(right));
}
function resolveFacadeSourceTypescriptPath(repoRoot, sourcePath) {
const packageSourceMatch = /^@openclaw\/([^/]+)\/(.+)$/u.exec(sourcePath);
const absolutePath = packageSourceMatch
? path.resolve(repoRoot, "extensions", packageSourceMatch[1], packageSourceMatch[2])
: path.resolve(repoRoot, "src/plugin-sdk", sourcePath);
const candidates = [absolutePath.replace(/\.js$/, ".ts"), absolutePath.replace(/\.js$/, ".tsx")];
return candidates.find((candidate) => fs.existsSync(candidate));
}
function resolveFacadeModuleResolutionContext(repoRoot) {
const cacheKey = repoRoot || "__default__";
const cached = moduleResolutionContextCache.get(cacheKey);
if (cached) {
return cached;
}
let context = {
options: MODULE_RESOLUTION_OPTIONS,
host: MODULE_RESOLUTION_HOST,
};
if (repoRoot) {
const fileExists = (filePath) => ts.sys.fileExists(filePath);
const readFile = (filePath) => ts.sys.readFile(filePath);
const configPath = ts.findConfigFile(repoRoot, fileExists, "tsconfig.json");
if (configPath) {
const configFile = ts.readConfigFile(configPath, readFile);
if (!configFile.error) {
const parsedConfig = ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
path.dirname(configPath),
MODULE_RESOLUTION_OPTIONS,
configPath,
);
const options = {
...MODULE_RESOLUTION_OPTIONS,
...parsedConfig.options,
};
context = {
options,
host: ts.createCompilerHost(options, true),
};
}
}
}
moduleResolutionContextCache.set(cacheKey, context);
return context;
}
function resolveFacadeSourceExportKinds(repoRoot, sourcePath) {
const cacheKey = `${repoRoot}::${sourcePath}`;
const cached = sourceExportKindsCache.get(cacheKey);
if (cached) {
return cached;
}
const sourceTsPath = resolveFacadeSourceTypescriptPath(repoRoot, sourcePath);
if (!sourceTsPath) {
const empty = new Map();
sourceExportKindsCache.set(cacheKey, empty);
return empty;
}
const moduleResolutionContext = resolveFacadeModuleResolutionContext(repoRoot);
const program = ts.createProgram(
[sourceTsPath],
moduleResolutionContext.options,
moduleResolutionContext.host,
);
const sourceFile = program.getSourceFile(sourceTsPath);
if (!sourceFile) {
const empty = new Map();
sourceExportKindsCache.set(cacheKey, empty);
return empty;
}
const checker = program.getTypeChecker();
const moduleSymbol = checker.getSymbolAtLocation(sourceFile) ?? sourceFile.symbol;
const exportKinds = new Map();
if (moduleSymbol) {
for (const exported of checker.getExportsOfModule(moduleSymbol)) {
const symbol =
exported.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(exported) : exported;
const flags = symbol.flags;
const declaration =
symbol.valueDeclaration ?? exported.valueDeclaration ?? exported.declarations?.[0];
const typeAtLocation = declaration
? checker.getTypeOfSymbolAtLocation(symbol, declaration)
: checker.getDeclaredTypeOfSymbol(symbol);
exportKinds.set(exported.getName(), {
type: Boolean(flags & ts.SymbolFlags.Type),
value: Boolean(flags & ts.SymbolFlags.Value),
functionLike: Boolean(flags & (ts.SymbolFlags.Function | ts.SymbolFlags.Method)),
callable: typeAtLocation.getCallSignatures().length > 0,
arrayLike: isArrayTypeLike(checker, typeAtLocation),
primitiveLike: isPrimitiveTypeLike(typeAtLocation),
});
}
}
sourceExportKindsCache.set(cacheKey, exportKinds);
return exportKinds;
}
export function buildPluginSdkFacadeModule(entry, params = {}) {
const sourceExportKinds = params.repoRoot
? resolveFacadeSourceExportKinds(params.repoRoot, entry.source)
: new Map();
const explicitFunctionExports = new Set(entry.functionExports ?? []);
const directExportSources = entry.directExports ?? {};
const exportNames = entry.exportAll
? Array.from(sourceExportKinds.keys()).toSorted((left, right) => left.localeCompare(right))
: entry.runtimeApiPreExportsPath
? collectRuntimeApiPreExports(params.repoRoot, entry.runtimeApiPreExportsPath)
: entry.exports;
const explicitTypeExports = new Set(entry.typeExports ?? []);
const valueExports = [];
const typeExports = [];
const valueExportsBySource = new Map();
let needsLazyArrayHelper = false;
let needsLazyObjectHelper = false;
for (const exportName of exportNames ?? []) {
if (explicitTypeExports.has(exportName)) {
continue;
}
if (directExportSources[exportName]) {
valueExports.push(exportName);
continue;
}
const kind = sourceExportKinds.get(exportName);
if (kind?.type && !kind.value) {
typeExports.push(exportName);
continue;
}
valueExports.push(exportName);
if (kind?.arrayLike) {
needsLazyArrayHelper = true;
} else if (!kind?.functionLike && !kind?.callable && !kind?.primitiveLike) {
needsLazyObjectHelper = true;
}
const sourcePath = entry.exportSources?.[exportName] ?? entry.source;
const exportsForSource = valueExportsBySource.get(sourcePath) ?? [];
exportsForSource.push(exportName);
valueExportsBySource.set(sourcePath, exportsForSource);
}
for (const typeExport of entry.typeExports ?? []) {
if (!typeExports.includes(typeExport)) {
typeExports.push(typeExport);
}
}
const nonDirectValueExports = valueExports.filter(
(exportName) => !directExportSources[exportName],
);
const lines = [`// Generated by ${GENERATED_PLUGIN_SDK_FACADES_SCRIPT}. Do not edit manually.`];
if (nonDirectValueExports.length || typeExports.length) {
lines.push(
'import type { PluginSdkFacadeTypeMap } from "../generated/plugin-sdk-facade-type-map.generated.js";',
);
lines.push(`type FacadeEntry = PluginSdkFacadeTypeMap[${JSON.stringify(entry.subpath)}];`);
lines.push('type FacadeModule = FacadeEntry["module"];');
for (const [sourceIndex] of listFacadeEntrySourcePaths(entry).entries()) {
if (sourceIndex === 0) {
continue;
}
lines.push(
`type FacadeModule${sourceIndex + 1} = FacadeEntry["sourceModules"][${JSON.stringify(buildFacadeSourceModuleKey(sourceIndex))}]["module"];`,
);
}
}
const directExportsBySource = new Map();
for (const exportName of valueExports) {
const sourcePath = directExportSources[exportName];
if (!sourcePath) {
continue;
}
const exportsForSource = directExportsBySource.get(sourcePath) ?? [];
exportsForSource.push(exportName);
directExportsBySource.set(sourcePath, exportsForSource);
}
if (directExportsBySource.size > 0) {
for (const [sourcePath, exportNamesForSource] of [...directExportsBySource.entries()].toSorted(
([left], [right]) => left.localeCompare(right),
)) {
lines.push(
`export { ${exportNamesForSource.toSorted((left, right) => left.localeCompare(right)).join(", ")} } from ${JSON.stringify(sourcePath)};`,
);
}
}
if (nonDirectValueExports.length) {
const runtimeImports = new Set();
if (needsLazyArrayHelper) {
runtimeImports.add("createLazyFacadeArrayValue");
}
if (needsLazyObjectHelper) {
runtimeImports.add("createLazyFacadeObjectValue");
}
for (const sourcePath of listFacadeEntrySourcePaths(entry)) {
const loadPolicy = resolveFacadeLoadPolicy(entry, sourcePath);
runtimeImports.add(
loadPolicy === "activated"
? "loadActivatedBundledPluginPublicSurfaceModuleSync"
: "loadBundledPluginPublicSurfaceModuleSync",
);
}
lines.push(
`import { ${[...runtimeImports].toSorted((left, right) => left.localeCompare(right)).join(", ")} } from "./facade-runtime.js";`,
);
for (const [sourceIndex, sourcePath] of listFacadeEntrySourcePaths(entry).entries()) {
if (!valueExportsBySource.has(sourcePath)) {
continue;
}
const { dirName: sourceDirName, artifactBasename: sourceArtifactBasename } =
normalizeFacadeSourceParts(sourcePath);
const loadPolicy = resolveFacadeLoadPolicy(entry, sourcePath);
const loaderName =
loadPolicy === "activated"
? "loadActivatedBundledPluginPublicSurfaceModuleSync"
: "loadBundledPluginPublicSurfaceModuleSync";
const loaderSuffix = sourceIndex === 0 ? "" : String(sourceIndex + 1);
const moduleTypeName = sourceIndex === 0 ? "FacadeModule" : `FacadeModule${sourceIndex + 1}`;
lines.push("");
lines.push(`function loadFacadeModule${loaderSuffix}(): ${moduleTypeName} {`);
lines.push(` return ${loaderName}<${moduleTypeName}>({`);
lines.push(` dirName: ${JSON.stringify(sourceDirName)},`);
lines.push(` artifactBasename: ${JSON.stringify(sourceArtifactBasename)},`);
lines.push(" });");
lines.push("}");
}
}
if (nonDirectValueExports.length) {
const sourceIndexByPath = new Map(
listFacadeEntrySourcePaths(entry).map((sourcePath, index) => [sourcePath, index]),
);
for (const exportName of nonDirectValueExports) {
if (directExportSources[exportName]) {
continue;
}
const kind = sourceExportKinds.get(exportName);
const isExplicitFunctionExport = explicitFunctionExports.has(exportName);
const sourcePath = entry.exportSources?.[exportName] ?? entry.source;
const sourceIndex = sourceIndexByPath.get(sourcePath) ?? 0;
const loaderSuffix = sourceIndex === 0 ? "" : String(sourceIndex + 1);
const moduleTypeName = sourceIndex === 0 ? "FacadeModule" : `FacadeModule${sourceIndex + 1}`;
if (isExplicitFunctionExport || kind?.functionLike || kind?.callable) {
lines.push(
`export const ${exportName}: ${moduleTypeName}[${JSON.stringify(exportName)}] = ((...args) =>`,
);
lines.push(
` loadFacadeModule${loaderSuffix}()[${JSON.stringify(exportName)}](...args)) as ${moduleTypeName}[${JSON.stringify(exportName)}];`,
);
continue;
}
if (kind?.arrayLike) {
lines.push(
`export const ${exportName}: ${moduleTypeName}[${JSON.stringify(exportName)}] = createLazyFacadeArrayValue(() => loadFacadeModule${loaderSuffix}()[${JSON.stringify(exportName)}] as unknown as readonly unknown[]) as ${moduleTypeName}[${JSON.stringify(exportName)}];`,
);
continue;
}
if (!kind?.primitiveLike) {
lines.push(
`export const ${exportName}: ${moduleTypeName}[${JSON.stringify(exportName)}] = createLazyFacadeObjectValue(() => loadFacadeModule${loaderSuffix}()[${JSON.stringify(exportName)}] as object) as ${moduleTypeName}[${JSON.stringify(exportName)}];`,
);
continue;
}
lines.push(
`export const ${exportName}: ${moduleTypeName}[${JSON.stringify(exportName)}] = loadFacadeModule${loaderSuffix}()[${JSON.stringify(exportName)}];`,
);
}
}
if (typeExports.length) {
for (const exportedType of typeExports) {
lines.push(
`export type ${exportedType} = FacadeEntry["types"][${JSON.stringify(exportedType)}];`,
);
}
}
lines.push("");
return lines.join("\n");
}
export function buildPluginSdkFacadeTypeMapModule(entries) {
const lines = [`// Generated by ${GENERATED_PLUGIN_SDK_FACADES_SCRIPT}. Do not edit manually.`];
lines.push("export interface PluginSdkFacadeTypeMap {");
for (const entry of entries) {
const moduleImportPath = rewriteFacadeTypeImportSpecifier(entry.source);
lines.push(` ${JSON.stringify(entry.subpath)}: {`);
lines.push(` module: typeof import(${JSON.stringify(moduleImportPath)});`);
lines.push(" sourceModules: {");
for (const [sourceIndex, sourcePath] of listFacadeEntrySourcePaths(entry).entries()) {
const rewrittenSourcePath = rewriteFacadeTypeImportSpecifier(sourcePath);
lines.push(` ${JSON.stringify(buildFacadeSourceModuleKey(sourceIndex))}: {`);
lines.push(` module: typeof import(${JSON.stringify(rewrittenSourcePath)});`);
lines.push(" };");
}
lines.push(" };");
lines.push(" types: {");
for (const exportedType of entry.typeExports ?? []) {
const typeImportPath = rewriteFacadeTypeImportSpecifier(entry.source);
lines.push(
` ${JSON.stringify(exportedType)}: import(${JSON.stringify(typeImportPath)}).${exportedType};`,
);
}
lines.push(" };");
lines.push(" };");
}
lines.push("}");
lines.push("");
return lines.join("\n");
}

View File

@@ -56,32 +56,6 @@ 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).
//
@@ -104,5 +78,3 @@ for (const entry of pluginSdkEntrypoints) {
fs.mkdirSync(path.dirname(runtimeOut), { recursive: true });
fs.writeFileSync(runtimeOut, runtimeShim, "utf8");
}
rewriteGeneratedFacadeTypeMapDts();