164 lines
5.4 KiB
TypeScript
164 lines
5.4 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
import { captureEnv } from "../test-utils/env.js";
|
|
import { loadEnabledBundleMcpConfig } from "./bundle-mcp.js";
|
|
import { clearPluginManifestRegistryCache } from "./manifest-registry.js";
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
async function canonicalizePathForAssertion(filePath: string): Promise<string> {
|
|
return await fs.realpath(filePath).catch(() => path.resolve(filePath));
|
|
}
|
|
|
|
async function createTempDir(prefix: string): Promise<string> {
|
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
tempDirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
afterEach(async () => {
|
|
clearPluginManifestRegistryCache();
|
|
await Promise.all(
|
|
tempDirs.splice(0, tempDirs.length).map((dir) => fs.rm(dir, { recursive: true, force: true })),
|
|
);
|
|
});
|
|
|
|
describe("loadEnabledBundleMcpConfig", () => {
|
|
it("loads enabled Claude bundle MCP config and absolutizes relative args", async () => {
|
|
const env = captureEnv(["HOME", "USERPROFILE", "OPENCLAW_HOME", "OPENCLAW_STATE_DIR"]);
|
|
try {
|
|
const homeDir = await createTempDir("openclaw-bundle-mcp-home-");
|
|
const workspaceDir = await createTempDir("openclaw-bundle-mcp-workspace-");
|
|
process.env.HOME = homeDir;
|
|
process.env.USERPROFILE = homeDir;
|
|
delete process.env.OPENCLAW_HOME;
|
|
delete process.env.OPENCLAW_STATE_DIR;
|
|
|
|
const pluginRoot = path.join(homeDir, ".openclaw", "extensions", "bundle-probe");
|
|
const serverPath = path.join(pluginRoot, "servers", "probe.mjs");
|
|
await fs.mkdir(path.join(pluginRoot, ".claude-plugin"), { recursive: true });
|
|
await fs.mkdir(path.dirname(serverPath), { recursive: true });
|
|
await fs.writeFile(serverPath, "export {};\n", "utf-8");
|
|
await fs.writeFile(
|
|
path.join(pluginRoot, ".claude-plugin", "plugin.json"),
|
|
`${JSON.stringify({ name: "bundle-probe" }, null, 2)}\n`,
|
|
"utf-8",
|
|
);
|
|
await fs.writeFile(
|
|
path.join(pluginRoot, ".mcp.json"),
|
|
`${JSON.stringify(
|
|
{
|
|
mcpServers: {
|
|
bundleProbe: {
|
|
command: "node",
|
|
args: ["./servers/probe.mjs"],
|
|
},
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
"utf-8",
|
|
);
|
|
|
|
const config: OpenClawConfig = {
|
|
plugins: {
|
|
entries: {
|
|
"bundle-probe": { enabled: true },
|
|
},
|
|
},
|
|
};
|
|
|
|
const loaded = loadEnabledBundleMcpConfig({
|
|
workspaceDir,
|
|
cfg: config,
|
|
});
|
|
const loadedServerArgs = loaded.config.mcpServers.bundleProbe?.args;
|
|
const loadedServerPath = Array.isArray(loadedServerArgs) ? loadedServerArgs[0] : undefined;
|
|
|
|
expect(loaded.diagnostics).toEqual([]);
|
|
expect(loaded.config.mcpServers.bundleProbe?.command).toBe("node");
|
|
expect(Array.isArray(loadedServerArgs)).toBe(true);
|
|
expect(typeof loadedServerPath).toBe("string");
|
|
expect(await canonicalizePathForAssertion(String(loadedServerPath))).toBe(
|
|
await canonicalizePathForAssertion(serverPath),
|
|
);
|
|
} finally {
|
|
env.restore();
|
|
}
|
|
});
|
|
|
|
it("merges inline bundle MCP servers and skips disabled bundles", async () => {
|
|
const env = captureEnv(["HOME", "USERPROFILE", "OPENCLAW_HOME", "OPENCLAW_STATE_DIR"]);
|
|
try {
|
|
const homeDir = await createTempDir("openclaw-bundle-inline-home-");
|
|
const workspaceDir = await createTempDir("openclaw-bundle-inline-workspace-");
|
|
process.env.HOME = homeDir;
|
|
process.env.USERPROFILE = homeDir;
|
|
delete process.env.OPENCLAW_HOME;
|
|
delete process.env.OPENCLAW_STATE_DIR;
|
|
|
|
const enabledRoot = path.join(homeDir, ".openclaw", "extensions", "inline-enabled");
|
|
const disabledRoot = path.join(homeDir, ".openclaw", "extensions", "inline-disabled");
|
|
await fs.mkdir(path.join(enabledRoot, ".claude-plugin"), { recursive: true });
|
|
await fs.mkdir(path.join(disabledRoot, ".claude-plugin"), { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(enabledRoot, ".claude-plugin", "plugin.json"),
|
|
`${JSON.stringify(
|
|
{
|
|
name: "inline-enabled",
|
|
mcpServers: {
|
|
enabledProbe: {
|
|
command: "node",
|
|
args: ["./enabled.mjs"],
|
|
},
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
"utf-8",
|
|
);
|
|
await fs.writeFile(
|
|
path.join(disabledRoot, ".claude-plugin", "plugin.json"),
|
|
`${JSON.stringify(
|
|
{
|
|
name: "inline-disabled",
|
|
mcpServers: {
|
|
disabledProbe: {
|
|
command: "node",
|
|
args: ["./disabled.mjs"],
|
|
},
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
"utf-8",
|
|
);
|
|
|
|
const config: OpenClawConfig = {
|
|
plugins: {
|
|
entries: {
|
|
"inline-enabled": { enabled: true },
|
|
"inline-disabled": { enabled: false },
|
|
},
|
|
},
|
|
};
|
|
|
|
const loaded = loadEnabledBundleMcpConfig({
|
|
workspaceDir,
|
|
cfg: config,
|
|
});
|
|
|
|
expect(loaded.config.mcpServers.enabledProbe).toBeDefined();
|
|
expect(loaded.config.mcpServers.disabledProbe).toBeUndefined();
|
|
} finally {
|
|
env.restore();
|
|
}
|
|
});
|
|
});
|