mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
fix: narrow mattermost setup entry seam
This commit is contained in:
38
extensions/mattermost/channel-plugin-api.test.ts
Normal file
38
extensions/mattermost/channel-plugin-api.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||
const importEnv = {
|
||||
HOME: process.env.HOME,
|
||||
NODE_OPTIONS: process.env.NODE_OPTIONS,
|
||||
NODE_PATH: process.env.NODE_PATH,
|
||||
PATH: process.env.PATH,
|
||||
TERM: process.env.TERM,
|
||||
} satisfies NodeJS.ProcessEnv;
|
||||
|
||||
describe("mattermost bundled api seam", () => {
|
||||
it("loads the narrow channel plugin api in direct smoke", async () => {
|
||||
const { stdout } = await execFileAsync(
|
||||
process.execPath,
|
||||
[
|
||||
"--import",
|
||||
"tsx",
|
||||
"-e",
|
||||
'const mod = await import("./extensions/mattermost/channel-plugin-api.ts"); process.stdout.write(JSON.stringify({keys:Object.keys(mod).sort(), id: mod.mattermostPlugin.id, setupId: mod.mattermostSetupPlugin.id}));',
|
||||
],
|
||||
{
|
||||
cwd: repoRoot,
|
||||
env: importEnv,
|
||||
timeout: 40_000,
|
||||
},
|
||||
);
|
||||
|
||||
expect(stdout).toBe(
|
||||
'{"keys":["mattermostPlugin","mattermostSetupPlugin"],"id":"mattermost","setupId":"mattermost"}',
|
||||
);
|
||||
}, 45_000);
|
||||
});
|
||||
@@ -1,33 +1,4 @@
|
||||
import { loadBundledEntryExportSync } from "openclaw/plugin-sdk/channel-entry-contract";
|
||||
|
||||
type ChannelPluginModule = typeof import("./channel-plugin-runtime.js");
|
||||
|
||||
function createLazyObjectValue<T extends object>(load: () => T): T {
|
||||
return new Proxy({} as T, {
|
||||
get(_target, property, receiver) {
|
||||
return Reflect.get(load(), property, receiver);
|
||||
},
|
||||
has(_target, property) {
|
||||
return property in load();
|
||||
},
|
||||
ownKeys() {
|
||||
return Reflect.ownKeys(load());
|
||||
},
|
||||
getOwnPropertyDescriptor(_target, property) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(load(), property);
|
||||
return descriptor ? { ...descriptor, configurable: true } : undefined;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function loadChannelPluginModule(): ChannelPluginModule {
|
||||
return loadBundledEntryExportSync<ChannelPluginModule>(import.meta.url, {
|
||||
specifier: "./channel-plugin-runtime.js",
|
||||
});
|
||||
}
|
||||
|
||||
// Keep bundled channel entry imports narrow so bootstrap/discovery paths do
|
||||
// not drag the broader Mattermost helper surfaces into lightweight plugin loads.
|
||||
export const mattermostPlugin: ChannelPluginModule["mattermostPlugin"] = createLazyObjectValue(
|
||||
() => loadChannelPluginModule().mattermostPlugin as object,
|
||||
) as ChannelPluginModule["mattermostPlugin"];
|
||||
export { mattermostPlugin } from "./channel-plugin-runtime.js";
|
||||
export { mattermostSetupPlugin } from "./src/channel.setup.js";
|
||||
|
||||
@@ -4,6 +4,6 @@ export default defineBundledChannelSetupEntry({
|
||||
importMetaUrl: import.meta.url,
|
||||
plugin: {
|
||||
specifier: "./channel-plugin-api.js",
|
||||
exportName: "mattermostPlugin",
|
||||
exportName: "mattermostSetupPlugin",
|
||||
},
|
||||
});
|
||||
|
||||
86
extensions/mattermost/src/channel.setup.ts
Normal file
86
extensions/mattermost/src/channel.setup.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
||||
import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
|
||||
import {
|
||||
adaptScopedAccountAccessor,
|
||||
createScopedChannelConfigAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import type { ChannelPlugin } from "./channel-api.js";
|
||||
import { MattermostChannelConfigSchema } from "./config-surface.js";
|
||||
import {
|
||||
listMattermostAccountIds,
|
||||
resolveDefaultMattermostAccountId,
|
||||
resolveMattermostAccount,
|
||||
type ResolvedMattermostAccount,
|
||||
} from "./mattermost/accounts.js";
|
||||
import { mattermostSetupAdapter } from "./setup-core.js";
|
||||
import { mattermostSetupWizard } from "./setup-surface.js";
|
||||
|
||||
const mattermostMeta = {
|
||||
id: "mattermost",
|
||||
label: "Mattermost",
|
||||
selectionLabel: "Mattermost (plugin)",
|
||||
detailLabel: "Mattermost Bot",
|
||||
docsPath: "/channels/mattermost",
|
||||
docsLabel: "mattermost",
|
||||
blurb: "self-hosted Slack-style chat; install the plugin to enable.",
|
||||
systemImage: "bubble.left.and.bubble.right",
|
||||
order: 65,
|
||||
quickstartAllowFrom: true,
|
||||
} as const;
|
||||
|
||||
function formatAllowEntry(entry: string): string {
|
||||
const trimmed = entry.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
if (trimmed.startsWith("@")) {
|
||||
const username = trimmed.slice(1).trim();
|
||||
return username ? `@${username.toLowerCase()}` : "";
|
||||
}
|
||||
return trimmed.replace(/^(mattermost|user):/i, "").toLowerCase();
|
||||
}
|
||||
|
||||
const mattermostConfigAdapter = createScopedChannelConfigAdapter<ResolvedMattermostAccount>({
|
||||
sectionKey: "mattermost",
|
||||
listAccountIds: listMattermostAccountIds,
|
||||
resolveAccount: adaptScopedAccountAccessor(resolveMattermostAccount),
|
||||
defaultAccountId: resolveDefaultMattermostAccountId,
|
||||
clearBaseFields: ["botToken", "baseUrl", "name"],
|
||||
resolveAllowFrom: (account) => account.config.allowFrom,
|
||||
formatAllowFrom: (allowFrom) =>
|
||||
formatNormalizedAllowFromEntries({
|
||||
allowFrom,
|
||||
normalizeEntry: formatAllowEntry,
|
||||
}),
|
||||
});
|
||||
|
||||
export const mattermostSetupPlugin: ChannelPlugin<ResolvedMattermostAccount> = {
|
||||
id: "mattermost",
|
||||
meta: {
|
||||
...mattermostMeta,
|
||||
},
|
||||
capabilities: {
|
||||
chatTypes: ["direct", "channel", "group", "thread"],
|
||||
reactions: true,
|
||||
threads: true,
|
||||
media: true,
|
||||
nativeCommands: true,
|
||||
},
|
||||
reload: { configPrefixes: ["channels.mattermost"] },
|
||||
configSchema: MattermostChannelConfigSchema,
|
||||
config: {
|
||||
...mattermostConfigAdapter,
|
||||
isConfigured: (account) => Boolean(account.botToken && account.baseUrl),
|
||||
describeAccount: (account) =>
|
||||
describeAccountSnapshot({
|
||||
account,
|
||||
configured: Boolean(account.botToken && account.baseUrl),
|
||||
extra: {
|
||||
botTokenSource: account.botTokenSource,
|
||||
baseUrl: account.baseUrl,
|
||||
},
|
||||
}),
|
||||
},
|
||||
setup: mattermostSetupAdapter,
|
||||
setupWizard: mattermostSetupWizard,
|
||||
};
|
||||
Reference in New Issue
Block a user