mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(plugin-sdk): external plugins typecheck across SDK entrypoints (#121512)
* fix(plugin-sdk): dedupe declaration dependency types Keep Zod declarations owned by the published dependency across partitioned SDK declaration builds, and compile an isolated external consumer that mixes the documented runtime and schema entrypoints. * fix(plugin-sdk): narrow package type regression guard Keep the cross-entrypoint assignment and exported-schema checks while excluding unrelated declaration-library diagnostics. Inline the temporary consumer and carry the current-main unused-import repair required for green CI.
This commit is contained in:
committed by
GitHub
parent
2ed1a62d06
commit
024caf1e51
@@ -3,12 +3,23 @@
|
||||
/**
|
||||
* Verifies that public plugin-sdk subpaths are present in the compiled dist output.
|
||||
*
|
||||
* Run after `pnpm build` to catch missing exports or leaked repo-only type aliases
|
||||
* Run after the package build to catch missing exports or leaked repo-only type aliases
|
||||
* before release.
|
||||
*/
|
||||
|
||||
import { readFileSync, existsSync, statSync } from "node:fs";
|
||||
import { resolve, dirname, relative, sep } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
statSync,
|
||||
symlinkSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join, relative, resolve, sep } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import {
|
||||
MAX_PRIVATE_QA_PUBLIC_PLUGIN_SDK_DECLARATION_BYTES,
|
||||
@@ -20,6 +31,19 @@ import {
|
||||
import { publicPluginSdkEntrypoints, publicPluginSdkSubpaths } from "./lib/plugin-sdk-entries.mts";
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = resolve(scriptDir, "..");
|
||||
const nativePreviewPackageJsonPath = resolve(
|
||||
repoRoot,
|
||||
"node_modules/@typescript/native-preview/package.json",
|
||||
);
|
||||
const nativePreviewPackageJson = JSON.parse(readFileSync(nativePreviewPackageJsonPath, "utf8")) as {
|
||||
bin?: { tsgo?: string };
|
||||
};
|
||||
const nativePreviewTsgoBin = nativePreviewPackageJson.bin?.tsgo;
|
||||
if (!nativePreviewTsgoBin) {
|
||||
throw new Error("@typescript/native-preview does not declare the tsgo binary");
|
||||
}
|
||||
const tsgoPath = resolve(dirname(nativePreviewPackageJsonPath), nativePreviewTsgoBin);
|
||||
const forbiddenPublicDeclarationSpecifiers = ["@openclaw/llm-core"];
|
||||
const FORBIDDEN_PUBLIC_PROTOCOL_REGISTRY_RE = /\bdeclare\s+const\s+ProtocolSchemas(?:\$\d+)?\b/u;
|
||||
const RELATIVE_DECLARATION_SPECIFIER_RE = /\b(?:from|import)\s*(?:\(\s*)?["']([^"']+)["']/gu;
|
||||
@@ -36,6 +60,77 @@ const requiredSubpathExports: Record<string, string[]> = {
|
||||
|
||||
let missing = 0;
|
||||
|
||||
{
|
||||
const tempRoot = mkdtempSync(join(tmpdir(), "openclaw-plugin-sdk-consumer-"));
|
||||
const consumerRoot = join(tempRoot, "consumer");
|
||||
try {
|
||||
mkdirSync(consumerRoot, { recursive: true });
|
||||
writeFileSync(
|
||||
join(consumerRoot, "index.ts"),
|
||||
`import { buildChannelConfigSchema, DmPolicySchema } from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
||||
import { createPluginRuntimeStore, type PluginRuntime } from "openclaw/plugin-sdk/runtime-store";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
|
||||
const runtimeStore = createPluginRuntimeStore<PluginRuntime>({
|
||||
pluginId: "package-consumer",
|
||||
errorMessage: "package consumer runtime not initialized",
|
||||
});
|
||||
export const configSchema = buildChannelConfigSchema(
|
||||
z.object({ dmPolicy: DmPolicySchema.optional() }),
|
||||
);
|
||||
|
||||
declare const plugin: Parameters<typeof defineChannelPluginEntry>[0]["plugin"];
|
||||
export default defineChannelPluginEntry({
|
||||
id: "package-consumer",
|
||||
name: "Package Consumer",
|
||||
description: "Published Plugin SDK declaration compatibility fixture",
|
||||
plugin,
|
||||
setRuntime: runtimeStore.setRuntime,
|
||||
});
|
||||
`,
|
||||
);
|
||||
writeFileSync(join(consumerRoot, "package.json"), '{"private":true,"type":"module"}\n');
|
||||
writeFileSync(
|
||||
join(consumerRoot, "tsconfig.json"),
|
||||
`{
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2023"],
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"types": []
|
||||
},
|
||||
"include": ["index.ts"]
|
||||
}
|
||||
`,
|
||||
);
|
||||
const openclawPackagePath = join(consumerRoot, "node_modules", "openclaw");
|
||||
mkdirSync(dirname(openclawPackagePath), { recursive: true });
|
||||
symlinkSync(repoRoot, openclawPackagePath, process.platform === "win32" ? "junction" : "dir");
|
||||
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[tsgoPath, "-p", join(consumerRoot, "tsconfig.json"), "--pretty", "false"],
|
||||
{ cwd: consumerRoot, encoding: "utf8" },
|
||||
);
|
||||
if (result.error) {
|
||||
console.error("BROKEN PLUGIN SDK CONSUMER: failed to start tsgo");
|
||||
console.error(result.error.message);
|
||||
missing += 1;
|
||||
} else if (result.status !== 0) {
|
||||
console.error("BROKEN PLUGIN SDK CONSUMER: mixed public subpaths are not assignable");
|
||||
process.stderr.write(result.stdout || "");
|
||||
process.stderr.write(result.stderr || "");
|
||||
missing += 1;
|
||||
}
|
||||
} finally {
|
||||
rmSync(tempRoot, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
for (const entry of publicPluginSdkSubpaths) {
|
||||
const jsPath = resolve(scriptDir, "..", "dist", "plugin-sdk", `${entry}.js`);
|
||||
const dtsPath = resolve(scriptDir, "..", "dist", "plugin-sdk", `${entry}.d.ts`);
|
||||
@@ -151,11 +246,9 @@ if (declarationBudget.shouldFail) {
|
||||
}
|
||||
|
||||
if (missing > 0) {
|
||||
console.error(
|
||||
`\nERROR: ${missing} required plugin-sdk artifact(s) missing (named exports or subpath files).`,
|
||||
);
|
||||
console.error(`\nERROR: ${missing} plugin-sdk artifact check(s) failed.`);
|
||||
console.error("This will break published plugin-sdk artifacts.");
|
||||
console.error("Check generated d.ts rewrites, subpath entries, and rebuild.");
|
||||
console.error("Check generated d.ts rewrites, subpath entries, type compatibility, and rebuild.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -233,6 +233,10 @@ function shouldNeverBundleDependency(id: string): boolean {
|
||||
});
|
||||
}
|
||||
|
||||
function shouldNeverBundleDeclarationDependency(id: string): boolean {
|
||||
return shouldNeverBundleDependency(id) || id === "zod" || id.startsWith("zod/");
|
||||
}
|
||||
|
||||
function shouldAlwaysBundleDependency(id: string): boolean {
|
||||
return (
|
||||
id === "openclaw/plugin-sdk/ssrf-runtime-internal" ||
|
||||
@@ -613,8 +617,8 @@ const unifiedDistEntries = buildUnifiedDistEntries();
|
||||
const unifiedDeps = {
|
||||
alwaysBundle: shouldAlwaysBundleDependency,
|
||||
neverBundle: shouldNeverBundleDependency,
|
||||
// Keep dts generation from inlining externalized package types.
|
||||
dts: { neverBundle: shouldNeverBundleDependency },
|
||||
// Keep dependency-owned types canonical across independently emitted declaration graphs.
|
||||
dts: { neverBundle: shouldNeverBundleDeclarationDependency },
|
||||
};
|
||||
|
||||
const configs = [
|
||||
|
||||
Reference in New Issue
Block a user