refactor: simplify plugin sdk compatibility aliases
This commit is contained in:
@@ -108,92 +108,26 @@ const fastExports = {
|
||||
resolveControlCommandGate,
|
||||
};
|
||||
|
||||
const rootProxy = new Proxy(fastExports, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === "__esModule") {
|
||||
return true;
|
||||
}
|
||||
if (prop === "default") {
|
||||
return rootProxy;
|
||||
}
|
||||
if (Reflect.has(target, prop)) {
|
||||
return Reflect.get(target, prop, receiver);
|
||||
}
|
||||
return loadMonolithicSdk()[prop];
|
||||
},
|
||||
has(target, prop) {
|
||||
if (prop === "__esModule" || prop === "default") {
|
||||
return true;
|
||||
}
|
||||
if (Reflect.has(target, prop)) {
|
||||
return true;
|
||||
}
|
||||
const monolithic = tryLoadMonolithicSdk();
|
||||
return monolithic ? prop in monolithic : false;
|
||||
},
|
||||
ownKeys(target) {
|
||||
const keys = new Set([...Reflect.ownKeys(target), "default", "__esModule"]);
|
||||
// Keep Object.keys/property reflection fast and deterministic.
|
||||
// Only expose monolithic keys if it was already loaded by direct access.
|
||||
if (monolithicSdk) {
|
||||
for (const key of Reflect.ownKeys(monolithicSdk)) {
|
||||
keys.add(key);
|
||||
const monolithic = tryLoadMonolithicSdk();
|
||||
const rootExports =
|
||||
monolithic && typeof monolithic === "object"
|
||||
? {
|
||||
...monolithic,
|
||||
...fastExports,
|
||||
}
|
||||
}
|
||||
return [...keys];
|
||||
},
|
||||
getOwnPropertyDescriptor(target, prop) {
|
||||
if (prop === "__esModule") {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: true,
|
||||
};
|
||||
}
|
||||
if (prop === "default") {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: rootProxy,
|
||||
};
|
||||
}
|
||||
const own = Object.getOwnPropertyDescriptor(target, prop);
|
||||
if (own) {
|
||||
return own;
|
||||
}
|
||||
const monolithic = tryLoadMonolithicSdk();
|
||||
if (!monolithic) {
|
||||
return undefined;
|
||||
}
|
||||
const descriptor = Object.getOwnPropertyDescriptor(monolithic, prop);
|
||||
if (!descriptor) {
|
||||
return undefined;
|
||||
}
|
||||
if (descriptor.get || descriptor.set) {
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: descriptor.enumerable ?? true,
|
||||
get: descriptor.get
|
||||
? function getLegacyValue() {
|
||||
return descriptor.get.call(monolithic);
|
||||
}
|
||||
: undefined,
|
||||
set: descriptor.set
|
||||
? function setLegacyValue(value) {
|
||||
return descriptor.set.call(monolithic, value);
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
configurable: true,
|
||||
enumerable: descriptor.enumerable ?? true,
|
||||
value: descriptor.value,
|
||||
writable: descriptor.writable,
|
||||
};
|
||||
},
|
||||
: { ...fastExports };
|
||||
|
||||
Object.defineProperty(rootExports, "__esModule", {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: true,
|
||||
});
|
||||
Object.defineProperty(rootExports, "default", {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: rootExports,
|
||||
});
|
||||
|
||||
module.exports = rootProxy;
|
||||
module.exports = rootExports;
|
||||
|
||||
@@ -27,7 +27,7 @@ describe("plugin-sdk root alias", () => {
|
||||
expect(parsed.success).toBe(false);
|
||||
});
|
||||
|
||||
it("loads legacy root exports lazily through the proxy", { timeout: 240_000 }, () => {
|
||||
it("loads legacy root exports through the merged root wrapper", { timeout: 240_000 }, () => {
|
||||
expect(typeof rootSdk.resolveControlCommandGate).toBe("function");
|
||||
expect(typeof rootSdk.default).toBe("object");
|
||||
expect(rootSdk.default).toBe(rootSdk);
|
||||
|
||||
@@ -1439,6 +1439,13 @@ describe("loadOpenClawPlugins", () => {
|
||||
expect(candidates.indexOf(srcFile)).toBeLessThan(candidates.indexOf(distFile));
|
||||
});
|
||||
|
||||
it("derives plugin-sdk subpaths from package exports", () => {
|
||||
const subpaths = __testing.listPluginSdkExportedSubpaths();
|
||||
expect(subpaths).toContain("compat");
|
||||
expect(subpaths).toContain("telegram");
|
||||
expect(subpaths).not.toContain("root-alias");
|
||||
});
|
||||
|
||||
it("falls back to src plugin-sdk alias when dist is missing in production", () => {
|
||||
const { root, srcFile, distFile } = createPluginSdkAliasFixture();
|
||||
fs.rmSync(distFile);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createJiti } from "jiti";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { clearPluginCommands } from "./commands.js";
|
||||
@@ -111,105 +112,46 @@ const resolvePluginSdkAliasFile = (params: {
|
||||
const resolvePluginSdkAlias = (): string | null =>
|
||||
resolvePluginSdkAliasFile({ srcFile: "root-alias.cjs", distFile: "root-alias.cjs" });
|
||||
|
||||
const pluginSdkScopedAliasEntries = [
|
||||
{ subpath: "core", srcFile: "core.ts", distFile: "core.js" },
|
||||
{ subpath: "compat", srcFile: "compat.ts", distFile: "compat.js" },
|
||||
{ subpath: "telegram", srcFile: "telegram.ts", distFile: "telegram.js" },
|
||||
{ subpath: "discord", srcFile: "discord.ts", distFile: "discord.js" },
|
||||
{ subpath: "slack", srcFile: "slack.ts", distFile: "slack.js" },
|
||||
{ subpath: "signal", srcFile: "signal.ts", distFile: "signal.js" },
|
||||
{ subpath: "imessage", srcFile: "imessage.ts", distFile: "imessage.js" },
|
||||
{ subpath: "whatsapp", srcFile: "whatsapp.ts", distFile: "whatsapp.js" },
|
||||
{ subpath: "line", srcFile: "line.ts", distFile: "line.js" },
|
||||
{ subpath: "msteams", srcFile: "msteams.ts", distFile: "msteams.js" },
|
||||
{ subpath: "acpx", srcFile: "acpx.ts", distFile: "acpx.js" },
|
||||
{ subpath: "bluebubbles", srcFile: "bluebubbles.ts", distFile: "bluebubbles.js" },
|
||||
{
|
||||
subpath: "copilot-proxy",
|
||||
srcFile: "copilot-proxy.ts",
|
||||
distFile: "copilot-proxy.js",
|
||||
},
|
||||
{ subpath: "device-pair", srcFile: "device-pair.ts", distFile: "device-pair.js" },
|
||||
{
|
||||
subpath: "diagnostics-otel",
|
||||
srcFile: "diagnostics-otel.ts",
|
||||
distFile: "diagnostics-otel.js",
|
||||
},
|
||||
{ subpath: "diffs", srcFile: "diffs.ts", distFile: "diffs.js" },
|
||||
{ subpath: "feishu", srcFile: "feishu.ts", distFile: "feishu.js" },
|
||||
{
|
||||
subpath: "google-gemini-cli-auth",
|
||||
srcFile: "google-gemini-cli-auth.ts",
|
||||
distFile: "google-gemini-cli-auth.js",
|
||||
},
|
||||
{ subpath: "googlechat", srcFile: "googlechat.ts", distFile: "googlechat.js" },
|
||||
{ subpath: "irc", srcFile: "irc.ts", distFile: "irc.js" },
|
||||
{ subpath: "llm-task", srcFile: "llm-task.ts", distFile: "llm-task.js" },
|
||||
{ subpath: "lobster", srcFile: "lobster.ts", distFile: "lobster.js" },
|
||||
{ subpath: "matrix", srcFile: "matrix.ts", distFile: "matrix.js" },
|
||||
{ subpath: "mattermost", srcFile: "mattermost.ts", distFile: "mattermost.js" },
|
||||
{ subpath: "memory-core", srcFile: "memory-core.ts", distFile: "memory-core.js" },
|
||||
{
|
||||
subpath: "memory-lancedb",
|
||||
srcFile: "memory-lancedb.ts",
|
||||
distFile: "memory-lancedb.js",
|
||||
},
|
||||
{
|
||||
subpath: "minimax-portal-auth",
|
||||
srcFile: "minimax-portal-auth.ts",
|
||||
distFile: "minimax-portal-auth.js",
|
||||
},
|
||||
{
|
||||
subpath: "nextcloud-talk",
|
||||
srcFile: "nextcloud-talk.ts",
|
||||
distFile: "nextcloud-talk.js",
|
||||
},
|
||||
{ subpath: "nostr", srcFile: "nostr.ts", distFile: "nostr.js" },
|
||||
{ subpath: "open-prose", srcFile: "open-prose.ts", distFile: "open-prose.js" },
|
||||
{
|
||||
subpath: "phone-control",
|
||||
srcFile: "phone-control.ts",
|
||||
distFile: "phone-control.js",
|
||||
},
|
||||
{
|
||||
subpath: "qwen-portal-auth",
|
||||
srcFile: "qwen-portal-auth.ts",
|
||||
distFile: "qwen-portal-auth.js",
|
||||
},
|
||||
{
|
||||
subpath: "synology-chat",
|
||||
srcFile: "synology-chat.ts",
|
||||
distFile: "synology-chat.js",
|
||||
},
|
||||
{ subpath: "talk-voice", srcFile: "talk-voice.ts", distFile: "talk-voice.js" },
|
||||
{ subpath: "test-utils", srcFile: "test-utils.ts", distFile: "test-utils.js" },
|
||||
{
|
||||
subpath: "thread-ownership",
|
||||
srcFile: "thread-ownership.ts",
|
||||
distFile: "thread-ownership.js",
|
||||
},
|
||||
{ subpath: "tlon", srcFile: "tlon.ts", distFile: "tlon.js" },
|
||||
{ subpath: "twitch", srcFile: "twitch.ts", distFile: "twitch.js" },
|
||||
{ subpath: "voice-call", srcFile: "voice-call.ts", distFile: "voice-call.js" },
|
||||
{ subpath: "zalo", srcFile: "zalo.ts", distFile: "zalo.js" },
|
||||
{ subpath: "zalouser", srcFile: "zalouser.ts", distFile: "zalouser.js" },
|
||||
{ subpath: "account-id", srcFile: "account-id.ts", distFile: "account-id.js" },
|
||||
{
|
||||
subpath: "keyed-async-queue",
|
||||
srcFile: "keyed-async-queue.ts",
|
||||
distFile: "keyed-async-queue.js",
|
||||
},
|
||||
] as const;
|
||||
const cachedPluginSdkExportedSubpaths = new Map<string, string[]>();
|
||||
|
||||
function listPluginSdkExportedSubpaths(params: { modulePath?: string } = {}): string[] {
|
||||
const modulePath = params.modulePath ?? fileURLToPath(import.meta.url);
|
||||
const packageRoot = resolveOpenClawPackageRootSync({
|
||||
cwd: path.dirname(modulePath),
|
||||
});
|
||||
if (!packageRoot) {
|
||||
return [];
|
||||
}
|
||||
const cached = cachedPluginSdkExportedSubpaths.get(packageRoot);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
try {
|
||||
const pkgRaw = fs.readFileSync(path.join(packageRoot, "package.json"), "utf-8");
|
||||
const pkg = JSON.parse(pkgRaw) as {
|
||||
exports?: Record<string, unknown>;
|
||||
};
|
||||
const subpaths = Object.keys(pkg.exports ?? {})
|
||||
.filter((key) => key.startsWith("./plugin-sdk/"))
|
||||
.map((key) => key.slice("./plugin-sdk/".length))
|
||||
.filter((subpath) => Boolean(subpath) && !subpath.includes("/"))
|
||||
.toSorted();
|
||||
cachedPluginSdkExportedSubpaths.set(packageRoot, subpaths);
|
||||
return subpaths;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const resolvePluginSdkScopedAliasMap = (): Record<string, string> => {
|
||||
const aliasMap: Record<string, string> = {};
|
||||
for (const entry of pluginSdkScopedAliasEntries) {
|
||||
for (const subpath of listPluginSdkExportedSubpaths()) {
|
||||
const resolved = resolvePluginSdkAliasFile({
|
||||
srcFile: entry.srcFile,
|
||||
distFile: entry.distFile,
|
||||
srcFile: `${subpath}.ts`,
|
||||
distFile: `${subpath}.js`,
|
||||
});
|
||||
if (resolved) {
|
||||
aliasMap[`openclaw/plugin-sdk/${entry.subpath}`] = resolved;
|
||||
aliasMap[`openclaw/plugin-sdk/${subpath}`] = resolved;
|
||||
}
|
||||
}
|
||||
return aliasMap;
|
||||
@@ -217,6 +159,7 @@ const resolvePluginSdkScopedAliasMap = (): Record<string, string> => {
|
||||
|
||||
export const __testing = {
|
||||
listPluginSdkAliasCandidates,
|
||||
listPluginSdkExportedSubpaths,
|
||||
resolvePluginSdkAliasCandidateOrder,
|
||||
resolvePluginSdkAliasFile,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user