import { describe, it, expect, vi, beforeEach } from "vitest"; import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, } from "node:fs"; vi.mock("node:fs", () => ({ existsSync: vi.fn(), readdirSync: vi.fn(), readFileSync: vi.fn(), mkdirSync: vi.fn(), writeFileSync: vi.fn(), })); vi.mock("../lib/install-path.js", () => ({ detectInstallTarget: vi.fn(() => ({ type: "claude-code", path: "/home/user/.claude/skills", })), detectAllTargets: vi.fn(() => [ { type: "claude-code", path: "/home/user/.claude/skills" }, { type: "cursor", path: "/home/user/.cursor/skills" }, { type: "windsurf", path: "/home/user/.windsurf/skills" }, ]), ALL_TARGETS: [ "claude-code", "cursor", "windsurf", "cline", "codex", "copilot", "opencode", ], targetToPath: vi.fn((target: string) => { const paths: Record = { "claude-code": "/home/user/.claude/skills", cursor: "/home/user/.cursor/skills", windsurf: "/home/user/.windsurf/skills", cline: "/home/user/.cline/skills", codex: "/home/user/.codex/skills", copilot: "/cwd/.github/copilot/skills", opencode: "/home/user/.opencode/skills", }; return paths[target] || "/home/user/.claude/skills"; }), })); import { syncCommand } from "./sync.js"; const mockExistsSync = vi.mocked(existsSync); const mockReaddirSync = vi.mocked(readdirSync); const mockReadFileSync = vi.mocked(readFileSync); const mockMkdirSync = vi.mocked(mkdirSync); const mockWriteFileSync = vi.mocked(writeFileSync); const VALID_SKILL_MD = `--- name: Code Review description: Reviews code for bugs and style issues version: 1.0.0 category: build platforms: - CLAUDE_CODE --- You are a code reviewer. Analyze the provided code for bugs, style issues, and improvements.`; beforeEach(() => { vi.clearAllMocks(); }); function setupInstalledSkills() { mockExistsSync.mockImplementation((p) => { const path = String(p); if (path === "/home/user/.claude/skills") return true; if (path.endsWith("SKILL.md")) return true; return false; }); mockReaddirSync.mockReturnValue([ { name: "code-review", isDirectory: () => true }, ] as any); mockReadFileSync.mockReturnValue(VALID_SKILL_MD); } describe("sync", () => { it("rejects unknown framework", async () => { setupInstalledSkills(); const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await syncCommand.parseAsync(["node", "sync", "unknown-framework"], { from: "node", }); // Should not write any files for unknown framework expect(mockWriteFileSync).not.toHaveBeenCalled(); logSpy.mockRestore(); }); it("syncs skills to openfang format", async () => { setupInstalledSkills(); const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await syncCommand.parseAsync( ["node", "sync", "openfang", "-o", "/tmp/sync-test"], { from: "node" }, ); expect(mockMkdirSync).toHaveBeenCalledWith("/tmp/sync-test", { recursive: true, }); expect(mockWriteFileSync).toHaveBeenCalledWith( "/tmp/sync-test/code-review.hand.toml", expect.stringContaining("[hand]"), ); const written = String(mockWriteFileSync.mock.calls[0][1]); expect(written).toContain("code-review"); expect(written).toContain("[instructions]"); expect(written).toContain("[model]"); logSpy.mockRestore(); }); it("syncs skills to mcp format", async () => { setupInstalledSkills(); const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await syncCommand.parseAsync( ["node", "sync", "mcp", "-o", "/tmp/mcp-test"], { from: "node" }, ); expect(mockWriteFileSync).toHaveBeenCalledWith( "/tmp/mcp-test/skills-hub-mcp.json", expect.stringContaining("mcpServers"), ); expect(mockWriteFileSync).toHaveBeenCalledWith( "/tmp/mcp-test/tools.json", expect.stringContaining("code-review"), ); logSpy.mockRestore(); }); it("syncs skills to codex format", async () => { setupInstalledSkills(); const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await syncCommand.parseAsync( ["node", "sync", "codex", "-o", "/tmp/codex-test"], { from: "node" }, ); expect(mockWriteFileSync).toHaveBeenCalledWith( "/tmp/codex-test/code-review.md", expect.stringContaining("Code Review"), ); logSpy.mockRestore(); }); it("syncs skills to claude-code format", async () => { setupInstalledSkills(); const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await syncCommand.parseAsync( ["node", "sync", "claude-code", "-o", "/tmp/claude-test"], { from: "node" }, ); expect(mockMkdirSync).toHaveBeenCalledWith("/tmp/claude-test/code-review", { recursive: true, }); expect(mockWriteFileSync).toHaveBeenCalledWith( "/tmp/claude-test/code-review/SKILL.md", expect.stringContaining("name: Code Review"), ); const written = String(mockWriteFileSync.mock.calls[0][1]); expect(written).toContain("version: 1.0.0"); expect(written).toContain("category: build"); expect(written).toContain("CLAUDE_CODE"); logSpy.mockRestore(); }); it("syncs skills to cursor format", async () => { setupInstalledSkills(); const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await syncCommand.parseAsync( ["node", "sync", "cursor", "-o", "/tmp/cursor-test"], { from: "node" }, ); expect(mockWriteFileSync).toHaveBeenCalledWith( "/tmp/cursor-test/code-review/SKILL.md", expect.stringContaining("name: Code Review"), ); logSpy.mockRestore(); }); it("syncs to all detected tools with --all", async () => { setupInstalledSkills(); const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await syncCommand.parseAsync(["node", "sync", "--all"], { from: "node", }); // Should sync to cursor and windsurf (not claude-code since that's the source) expect(mockMkdirSync).toHaveBeenCalledWith( "/home/user/.cursor/skills/code-review", { recursive: true }, ); expect(mockMkdirSync).toHaveBeenCalledWith( "/home/user/.windsurf/skills/code-review", { recursive: true }, ); expect(mockWriteFileSync).toHaveBeenCalledWith( "/home/user/.cursor/skills/code-review/SKILL.md", expect.stringContaining("name: Code Review"), ); expect(mockWriteFileSync).toHaveBeenCalledWith( "/home/user/.windsurf/skills/code-review/SKILL.md", expect.stringContaining("name: Code Review"), ); logSpy.mockRestore(); }); it("handles no installed skills", async () => { mockExistsSync.mockReturnValue(false); const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); await syncCommand.parseAsync(["node", "sync", "openfang"], { from: "node", }); expect(mockWriteFileSync).not.toHaveBeenCalled(); logSpy.mockRestore(); }); });