mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 01:31:08 +00:00
fix(release): handle nested default-wrapped bundled channel entries
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
resolveBundledChannelGeneratedPath,
|
||||
type BundledChannelPluginMetadata,
|
||||
} from "../../plugins/bundled-channel-runtime.js";
|
||||
import { unwrapDefaultModuleExport } from "../../plugins/module-export.js";
|
||||
import type { PluginRuntime } from "../../plugins/runtime/types.js";
|
||||
import { isJavaScriptModulePath, loadChannelPluginModule } from "./module-loader.js";
|
||||
import type { ChannelPlugin } from "./types.plugin.js";
|
||||
@@ -37,12 +38,7 @@ const OPENCLAW_PACKAGE_ROOT =
|
||||
function resolveChannelPluginModuleEntry(
|
||||
moduleExport: unknown,
|
||||
): BundledChannelEntryContract | null {
|
||||
const resolved =
|
||||
moduleExport &&
|
||||
typeof moduleExport === "object" &&
|
||||
"default" in (moduleExport as Record<string, unknown>)
|
||||
? (moduleExport as { default: unknown }).default
|
||||
: moduleExport;
|
||||
const resolved = unwrapDefaultModuleExport(moduleExport);
|
||||
if (!resolved || typeof resolved !== "object") {
|
||||
return null;
|
||||
}
|
||||
@@ -65,12 +61,7 @@ function resolveChannelPluginModuleEntry(
|
||||
function resolveChannelSetupModuleEntry(
|
||||
moduleExport: unknown,
|
||||
): BundledChannelSetupEntryContract | null {
|
||||
const resolved =
|
||||
moduleExport &&
|
||||
typeof moduleExport === "object" &&
|
||||
"default" in (moduleExport as Record<string, unknown>)
|
||||
? (moduleExport as { default: unknown }).default
|
||||
: moduleExport;
|
||||
const resolved = unwrapDefaultModuleExport(moduleExport);
|
||||
if (!resolved || typeof resolved !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { createCapturedPluginRegistration } from "./captured-registration.js";
|
||||
import { discoverOpenClawPlugins } from "./discovery.js";
|
||||
import type { PluginLoadOptions } from "./loader.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import { unwrapDefaultModuleExport } from "./module-export.js";
|
||||
import { createEmptyPluginRegistry } from "./registry-empty.js";
|
||||
import type { PluginRecord, PluginRegistry } from "./registry.js";
|
||||
import {
|
||||
@@ -95,12 +96,7 @@ function resolvePluginModuleExport(moduleExport: unknown): {
|
||||
definition?: OpenClawPluginDefinition;
|
||||
register?: OpenClawPluginDefinition["register"];
|
||||
} {
|
||||
const resolved =
|
||||
moduleExport &&
|
||||
typeof moduleExport === "object" &&
|
||||
"default" in (moduleExport as Record<string, unknown>)
|
||||
? (moduleExport as { default: unknown }).default
|
||||
: moduleExport;
|
||||
const resolved = unwrapDefaultModuleExport(moduleExport);
|
||||
if (typeof resolved === "function") {
|
||||
return {
|
||||
register: resolved as OpenClawPluginDefinition["register"],
|
||||
|
||||
@@ -2781,6 +2781,99 @@ module.exports = { id: "throws-after-import", register() {} };`,
|
||||
expect(disabled?.status).toBe("disabled");
|
||||
});
|
||||
|
||||
it("loads bundled channel entries through nested default export wrappers", () => {
|
||||
useNoBundledPlugins();
|
||||
const pluginDir = makeTempDir();
|
||||
const fullMarker = path.join(pluginDir, "full-loaded.txt");
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "package.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: "@openclaw/nested-default-channel",
|
||||
openclaw: {
|
||||
extensions: ["./index.cjs"],
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "openclaw.plugin.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
id: "nested-default-channel",
|
||||
configSchema: EMPTY_PLUGIN_SCHEMA,
|
||||
channels: ["nested-default-channel"],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, "index.cjs"),
|
||||
`module.exports = {
|
||||
default: {
|
||||
default: {
|
||||
id: "nested-default-channel",
|
||||
kind: "bundled-channel-entry",
|
||||
name: "Nested Default Channel",
|
||||
description: "interop-wrapped bundled channel entry",
|
||||
register(api) {
|
||||
require("node:fs").writeFileSync(${JSON.stringify(fullMarker)}, "loaded", "utf-8");
|
||||
api.registerChannel({
|
||||
plugin: {
|
||||
id: "nested-default-channel",
|
||||
meta: {
|
||||
id: "nested-default-channel",
|
||||
label: "Nested Default Channel",
|
||||
selectionLabel: "Nested Default Channel",
|
||||
docsPath: "/channels/nested-default-channel",
|
||||
blurb: "interop-wrapped bundled channel entry",
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
config: {
|
||||
listAccountIds: () => ["default"],
|
||||
resolveAccount: () => ({ accountId: "default", token: "configured" }),
|
||||
},
|
||||
outbound: { deliveryMode: "direct" },
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
};`,
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const registry = loadOpenClawPlugins({
|
||||
cache: false,
|
||||
config: {
|
||||
channels: {
|
||||
"nested-default-channel": {
|
||||
enabled: true,
|
||||
token: "configured",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
load: { paths: [pluginDir] },
|
||||
allow: ["nested-default-channel"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(fs.existsSync(fullMarker)).toBe(true);
|
||||
expect(registry.plugins.find((entry) => entry.id === "nested-default-channel")?.status).toBe(
|
||||
"loaded",
|
||||
);
|
||||
expect(registry.channels.some((entry) => entry.plugin.id === "nested-default-channel")).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("does not treat manifest channel ids as scoped plugin id matches", () => {
|
||||
useNoBundledPlugins();
|
||||
const target = writePlugin({
|
||||
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
listMemoryPromptSupplements,
|
||||
restoreMemoryPluginState,
|
||||
} from "./memory-state.js";
|
||||
import { unwrapDefaultModuleExport } from "./module-export.js";
|
||||
import { isPathInside, safeStatSync } from "./path-safety.js";
|
||||
import { createPluginRegistry, type PluginRecord, type PluginRegistry } from "./registry.js";
|
||||
import { resolvePluginCacheInputs } from "./roots.js";
|
||||
@@ -602,12 +603,7 @@ function resolvePluginModuleExport(moduleExport: unknown): {
|
||||
definition?: OpenClawPluginDefinition;
|
||||
register?: OpenClawPluginDefinition["register"];
|
||||
} {
|
||||
const resolved =
|
||||
moduleExport &&
|
||||
typeof moduleExport === "object" &&
|
||||
"default" in (moduleExport as Record<string, unknown>)
|
||||
? (moduleExport as { default: unknown }).default
|
||||
: moduleExport;
|
||||
const resolved = unwrapDefaultModuleExport(moduleExport);
|
||||
if (typeof resolved === "function") {
|
||||
return {
|
||||
register: resolved as OpenClawPluginDefinition["register"],
|
||||
@@ -624,12 +620,7 @@ function resolvePluginModuleExport(moduleExport: unknown): {
|
||||
function resolveSetupChannelRegistration(moduleExport: unknown): {
|
||||
plugin?: ChannelPlugin;
|
||||
} {
|
||||
const resolved =
|
||||
moduleExport &&
|
||||
typeof moduleExport === "object" &&
|
||||
"default" in (moduleExport as Record<string, unknown>)
|
||||
? (moduleExport as { default: unknown }).default
|
||||
: moduleExport;
|
||||
const resolved = unwrapDefaultModuleExport(moduleExport);
|
||||
if (!resolved || typeof resolved !== "object") {
|
||||
return {};
|
||||
}
|
||||
|
||||
16
src/plugins/module-export.ts
Normal file
16
src/plugins/module-export.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export function unwrapDefaultModuleExport(moduleExport: unknown): unknown {
|
||||
let resolved = moduleExport;
|
||||
const seen = new Set<unknown>();
|
||||
|
||||
while (
|
||||
resolved &&
|
||||
typeof resolved === "object" &&
|
||||
"default" in (resolved as Record<string, unknown>) &&
|
||||
!seen.has(resolved)
|
||||
) {
|
||||
seen.add(resolved);
|
||||
resolved = (resolved as { default: unknown }).default;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
Reference in New Issue
Block a user