UNPKG

creatrip-agent-rules-builder

Version:

Unified converter for AI coding agent rules across Cursor, Windsurf, and Claude

272 lines (221 loc) 8.6 kB
import * as fs from "fs"; import * as path from "path"; import { buildRules } from "../src/build"; // console.log를 모킹하여 테스트 출력 방지 jest.spyOn(console, "log").mockImplementation(() => {}); jest.spyOn(console, "error").mockImplementation(() => {}); // process.exit 모킹 - 절대 실제로 exit 하지 않음 const exitMock = jest.spyOn(process, "exit").mockImplementation((code) => { throw new Error(`Process exited with code ${code}`); }); describe("Integration Tests", () => { const testOutputDir = path.join(__dirname, "temp-integration"); const originalCwd = process.cwd(); beforeEach(() => { if (!fs.existsSync(testOutputDir)) { fs.mkdirSync(testOutputDir, { recursive: true }); } process.chdir(testOutputDir); }); afterEach(() => { process.chdir(originalCwd); if (fs.existsSync(testOutputDir)) { fs.rmSync(testOutputDir, { recursive: true, force: true }); } }); it("should build all agent rules from AGENTS.md", async () => { // 테스트 파일 생성 const rulesContent = `# Test Integration Rules ## Test Section - Integration test rule 1 - Integration test rule 2 \`\`\`jsonc agent-rules-config { "cursor": { "globs": ["*.test.ts"], "description": "Integration test rules" }, "windsurf": {}, "claude": {} } \`\`\``; fs.writeFileSync("AGENTS.md", rulesContent); // 빌드 실행 await buildRules({}); // 생성된 파일들 확인 expect(fs.existsSync(".cursor/rules/rules.mdc")).toBe(true); expect(fs.existsSync(".windsurfrules")).toBe(true); expect(fs.existsSync("CLAUDE.md")).toBe(true); // Cursor 파일 내용 확인 const cursorContent = fs.readFileSync(".cursor/rules/rules.mdc", "utf8"); expect(cursorContent).toContain("---"); expect(cursorContent).toContain("description: Integration test rules"); expect(cursorContent).toContain("globs: *.test.ts"); expect(cursorContent).toContain("# Test Integration Rules"); // Windsurf 파일 내용 확인 const windsurfContent = fs.readFileSync(".windsurfrules", "utf8"); expect(windsurfContent).toContain("# Test Integration Rules"); expect(windsurfContent).not.toContain("agent-rules-config"); // Claude 파일 내용 확인 const claudeContent = fs.readFileSync("CLAUDE.md", "utf8"); expect(claudeContent).toContain("# Test Integration Rules"); expect(claudeContent).not.toContain("agent-rules-config"); }); it("should handle custom file path", async () => { const customRulesContent = `# Custom Rules File ## Custom Section - Custom rule`; fs.writeFileSync("CUSTOM_RULES.md", customRulesContent); await buildRules({ file: "CUSTOM_RULES.md" }); expect(fs.existsSync(".cursor/rules/rules.mdc")).toBe(true); expect(fs.existsSync(".windsurfrules")).toBe(true); expect(fs.existsSync("CLAUDE.md")).toBe(true); const content = fs.readFileSync(".windsurfrules", "utf8"); expect(content).toContain("# Custom Rules File"); }); it("should throw error for non-existent file", async () => { await expect(buildRules({ file: "non-existent.md" })).rejects.toThrow(); }); it("should build recursively with multiple AGENTS.md files", async () => { // 프로젝트 구조 생성 const subDir1 = path.join(testOutputDir, "packages", "frontend"); const subDir2 = path.join(testOutputDir, "packages", "backend"); const nodeModulesDir = path.join( testOutputDir, "node_modules", "some-package", ); fs.mkdirSync(subDir1, { recursive: true }); fs.mkdirSync(subDir2, { recursive: true }); fs.mkdirSync(nodeModulesDir, { recursive: true }); // 메인 AGENTS.md const mainRulesContent = `# Main Project Rules ## Main Section - Main rule 1 \`\`\`jsonc agent-rules-config { "cursor": { "description": "Main project rules", "globs": ["*.ts", "*.js"] } } \`\`\``; // 서브 프로젝트들의 AGENTS.md const frontendRulesContent = `# Frontend Rules ## Frontend Section - Frontend rule 1 \`\`\`jsonc agent-rules-config { "cursor": { "description": "Frontend rules", "alwaysApply": true } } \`\`\``; const backendRulesContent = `# Backend Rules ## Backend Section - Backend rule 1`; // node_modules에도 AGENTS.md (무시되어야 함) const nodeModulesRulesContent = `# Should be ignored`; fs.writeFileSync("AGENTS.md", mainRulesContent); fs.writeFileSync(path.join(subDir1, "AGENTS.md"), frontendRulesContent); fs.writeFileSync(path.join(subDir2, "AGENTS.md"), backendRulesContent); fs.writeFileSync( path.join(nodeModulesDir, "AGENTS.md"), nodeModulesRulesContent, ); // 재귀 빌드 실행 - process.exit으로 인한 에러 처리 try { await buildRules({ recursive: true }); } catch (error: any) { // process.exit(1)이 호출되었다면 그냥 무시 if (!error.message?.includes("Process exited with code")) { throw error; } } // 메인 디렉토리 파일들 확인 expect(fs.existsSync(".cursor/rules/rules.mdc")).toBe(true); expect(fs.existsSync("CLAUDE.md")).toBe(true); expect(fs.existsSync(".windsurfrules")).toBe(true); // 서브 디렉토리 파일들 확인 expect(fs.existsSync(path.join(subDir1, ".cursor/rules/rules.mdc"))).toBe( true, ); expect(fs.existsSync(path.join(subDir1, "CLAUDE.md"))).toBe(true); expect(fs.existsSync(path.join(subDir1, ".windsurfrules"))).toBe(true); expect(fs.existsSync(path.join(subDir2, ".cursor/rules/rules.mdc"))).toBe( true, ); expect(fs.existsSync(path.join(subDir2, "CLAUDE.md"))).toBe(true); expect(fs.existsSync(path.join(subDir2, ".windsurfrules"))).toBe(true); // node_modules는 무시되어야 함 expect( fs.existsSync(path.join(nodeModulesDir, ".cursor/rules/rules.mdc")), ).toBe(false); // 각 파일의 내용 확인 const mainCursorContent = fs.readFileSync( ".cursor/rules/rules.mdc", "utf8", ); expect(mainCursorContent).toContain("Main Project Rules"); expect(mainCursorContent).toContain("description: Main project rules"); const frontendCursorContent = fs.readFileSync( path.join(subDir1, ".cursor/rules/rules.mdc"), "utf8", ); expect(frontendCursorContent).toContain("Frontend Rules"); expect(frontendCursorContent).toContain("alwaysApply: true"); const backendClaudeContent = fs.readFileSync( path.join(subDir2, "CLAUDE.md"), "utf8", ); expect(backendClaudeContent).toContain("Backend Rules"); }); it("should handle recursive build with .gitignore exclusions", async () => { // .gitignore 파일 생성 const gitignoreContent = `# Dependencies node_modules/ .pnp # Custom ignore temp/ *.log`; fs.writeFileSync(".gitignore", gitignoreContent); // 무시되어야 할 디렉토리들 const tempDir = path.join(testOutputDir, "temp"); const nodeModulesDir = path.join(testOutputDir, "node_modules"); fs.mkdirSync(tempDir, { recursive: true }); fs.mkdirSync(nodeModulesDir, { recursive: true }); // 처리되어야 할 디렉토리 const srcDir = path.join(testOutputDir, "src"); fs.mkdirSync(srcDir, { recursive: true }); // AGENTS.md 파일들 생성 fs.writeFileSync("AGENTS.md", "# Root Rules"); fs.writeFileSync(path.join(tempDir, "AGENTS.md"), "# Should be ignored"); fs.writeFileSync( path.join(nodeModulesDir, "AGENTS.md"), "# Should be ignored", ); fs.writeFileSync(path.join(srcDir, "AGENTS.md"), "# Src Rules"); // 재귀 빌드 실행 - process.exit으로 인한 에러 처리 try { await buildRules({ recursive: true }); } catch (error: any) { // process.exit(1)이 호출되었다면 그냥 무시 if (!error.message?.includes("Process exited with code")) { throw error; } } // 루트와 src는 처리되어야 함 expect(fs.existsSync("CLAUDE.md")).toBe(true); expect(fs.existsSync(path.join(srcDir, "CLAUDE.md"))).toBe(true); // temp와 node_modules는 무시되어야 함 expect(fs.existsSync(path.join(tempDir, "CLAUDE.md"))).toBe(false); expect(fs.existsSync(path.join(nodeModulesDir, "CLAUDE.md"))).toBe(false); }); it("should reject when both file and recursive options are provided", async () => { fs.writeFileSync("AGENTS.md", "# Test Rules"); await expect( buildRules({ file: "AGENTS.md", recursive: true }), ).rejects.toThrow("Process exited with code 1"); }); });