import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { inspectBundleLspRuntimeSupport } from "./bundle-lsp.js"; import { loadBundleManifest } from "./bundle-manifest.js"; import { inspectBundleMcpRuntimeSupport } from "./bundle-mcp.js"; /** * Integration test: builds a Claude Code bundle plugin fixture on disk * and verifies manifest parsing, capability detection, hook resolution, * MCP server discovery, and settings detection all work end-to-end. */ describe("Claude bundle plugin inspect integration", () => { let rootDir: string; beforeAll(() => { rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-claude-bundle-")); // .claude-plugin/plugin.json const manifestDir = path.join(rootDir, ".claude-plugin"); fs.mkdirSync(manifestDir, { recursive: true }); fs.writeFileSync( path.join(manifestDir, "plugin.json"), JSON.stringify({ name: "Test Claude Plugin", description: "Integration test fixture for Claude bundle inspection", version: "1.0.0", skills: ["skill-packs"], commands: "extra-commands", agents: "agents", hooks: "custom-hooks", mcpServers: ".mcp.json", lspServers: ".lsp.json", outputStyles: "output-styles", }), "utf-8", ); // skills/demo/SKILL.md const skillDir = path.join(rootDir, "skill-packs", "demo"); fs.mkdirSync(skillDir, { recursive: true }); fs.writeFileSync( path.join(skillDir, "SKILL.md"), "---\nname: demo\ndescription: A demo skill\n---\nDo something useful.", "utf-8", ); // commands/cmd/SKILL.md const cmdDir = path.join(rootDir, "extra-commands", "cmd"); fs.mkdirSync(cmdDir, { recursive: true }); fs.writeFileSync( path.join(cmdDir, "SKILL.md"), "---\nname: cmd\ndescription: A command skill\n---\nRun a command.", "utf-8", ); // hooks/hooks.json (default hook path) const hooksDir = path.join(rootDir, "hooks"); fs.mkdirSync(hooksDir, { recursive: true }); fs.writeFileSync(path.join(hooksDir, "hooks.json"), '{"hooks":[]}', "utf-8"); // custom-hooks/ (manifest-declared hook path) fs.mkdirSync(path.join(rootDir, "custom-hooks"), { recursive: true }); // .mcp.json with a stdio MCP server fs.writeFileSync( path.join(rootDir, ".mcp.json"), JSON.stringify({ mcpServers: { "test-stdio-server": { command: "echo", args: ["hello"], }, "test-sse-server": { url: "http://localhost:3000/sse", }, }, }), "utf-8", ); // settings.json fs.writeFileSync( path.join(rootDir, "settings.json"), JSON.stringify({ thinkingLevel: "high" }), "utf-8", ); // agents/ directory fs.mkdirSync(path.join(rootDir, "agents"), { recursive: true }); // .lsp.json with a stdio LSP server fs.writeFileSync( path.join(rootDir, ".lsp.json"), JSON.stringify({ lspServers: { "typescript-lsp": { command: "typescript-language-server", args: ["--stdio"], }, }, }), "utf-8", ); // output-styles/ directory fs.mkdirSync(path.join(rootDir, "output-styles"), { recursive: true }); }); afterAll(() => { fs.rmSync(rootDir, { recursive: true, force: true }); }); it("loads the full Claude bundle manifest with all capabilities", () => { const result = loadBundleManifest({ rootDir, bundleFormat: "claude" }); expect(result.ok).toBe(true); if (!result.ok) { return; } const m = result.manifest; expect(m.name).toBe("Test Claude Plugin"); expect(m.description).toBe("Integration test fixture for Claude bundle inspection"); expect(m.version).toBe("1.0.0"); expect(m.bundleFormat).toBe("claude"); }); it("resolves skills from skills, commands, and agents paths", () => { const result = loadBundleManifest({ rootDir, bundleFormat: "claude" }); expect(result.ok).toBe(true); if (!result.ok) { return; } expect(result.manifest.skills).toContain("skill-packs"); expect(result.manifest.skills).toContain("extra-commands"); // Agent and output style dirs are merged into skills so their .md files are discoverable expect(result.manifest.skills).toContain("agents"); expect(result.manifest.skills).toContain("output-styles"); }); it("resolves hooks from default and declared paths", () => { const result = loadBundleManifest({ rootDir, bundleFormat: "claude" }); expect(result.ok).toBe(true); if (!result.ok) { return; } // Default hooks/hooks.json path + declared custom-hooks expect(result.manifest.hooks).toContain("hooks/hooks.json"); expect(result.manifest.hooks).toContain("custom-hooks"); }); it("detects settings files", () => { const result = loadBundleManifest({ rootDir, bundleFormat: "claude" }); expect(result.ok).toBe(true); if (!result.ok) { return; } expect(result.manifest.settingsFiles).toEqual(["settings.json"]); }); it("detects all bundle capabilities", () => { const result = loadBundleManifest({ rootDir, bundleFormat: "claude" }); expect(result.ok).toBe(true); if (!result.ok) { return; } const caps = result.manifest.capabilities; expect(caps).toContain("skills"); expect(caps).toContain("commands"); expect(caps).toContain("agents"); expect(caps).toContain("hooks"); expect(caps).toContain("mcpServers"); expect(caps).toContain("lspServers"); expect(caps).toContain("outputStyles"); expect(caps).toContain("settings"); }); it("inspects MCP runtime support with supported and unsupported servers", () => { const mcp = inspectBundleMcpRuntimeSupport({ pluginId: "test-claude-plugin", rootDir, bundleFormat: "claude", }); expect(mcp.hasSupportedStdioServer).toBe(true); expect(mcp.supportedServerNames).toContain("test-stdio-server"); expect(mcp.unsupportedServerNames).toContain("test-sse-server"); expect(mcp.diagnostics).toEqual([]); }); it("inspects LSP runtime support with stdio server", () => { const lsp = inspectBundleLspRuntimeSupport({ pluginId: "test-claude-plugin", rootDir, bundleFormat: "claude", }); expect(lsp.hasStdioServer).toBe(true); expect(lsp.supportedServerNames).toContain("typescript-lsp"); expect(lsp.unsupportedServerNames).toEqual([]); expect(lsp.diagnostics).toEqual([]); }); });