diff --git a/packages/cli/src/__tests__/create-command.test.ts b/packages/cli/src/__tests__/create-command.test.ts index 67a89d5..4127b24 100644 --- a/packages/cli/src/__tests__/create-command.test.ts +++ b/packages/cli/src/__tests__/create-command.test.ts @@ -73,33 +73,164 @@ describe("create command", () => { }); it("creates git-repo docset", async () => { - const cliPath = join(process.cwd(), "dist/index.js"); - const cmd = `node ${cliPath} create --preset git-repo --id react-docs --name "React Docs" --url https://github.com/facebook/react.git`; + const { createCommand } = await import("../commands/create.js"); - execSync(cmd, { cwd: testDir }); + const originalCwd = process.cwd; + const originalLog = console.log; + process.cwd = () => testDir; + console.log = () => {}; - const config = await fs.readFile(configPath, "utf-8"); - expect(config).toContain("id: react-docs"); - expect(config).toContain("name: React Docs"); - expect(config).toContain("url: https://github.com/facebook/react.git"); - expect(config).toContain("type: git_repo"); + try { + await createCommand.parseAsync([ + "node", + "create", + "--preset", + "git-repo", + "--id", + "react-docs", + "--name", + "React Docs", + "--url", + "https://github.com/facebook/react.git", + ]); + + const config = await fs.readFile(configPath, "utf-8"); + expect(config).toContain("id: react-docs"); + expect(config).toContain("name: React Docs"); + expect(config).toContain("url: https://github.com/facebook/react.git"); + expect(config).toContain("type: git_repo"); + } finally { + process.cwd = originalCwd; + console.log = originalLog; + } }); it("fails with invalid path", async () => { - const cliPath = join(process.cwd(), "dist/index.js"); - const cmd = `node ${cliPath} create --preset local-folder --id test --name "Test" --path ./nonexistent`; + const { createCommand } = await import("../commands/create.js"); + + const originalCwd = process.cwd; + const originalLog = console.log; + const originalError = console.error; + process.cwd = () => testDir; + console.log = () => {}; + console.error = () => {}; + + try { + await expect( + createCommand.parseAsync([ + "node", + "create", + "--preset", + "local-folder", + "--id", + "test", + "--name", + "Test", + "--path", + "./nonexistent", + ]), + ).rejects.toThrow(); + } finally { + process.cwd = originalCwd; + console.log = originalLog; + console.error = originalError; + } + }); + + it("creates config file when missing and creates symlinks for local folder", async () => { + // Remove the config file to test creation from scratch + await fs.rm(join(testDir, ".knowledge"), { recursive: true, force: true }); + + // Import and run create command directly + const { createCommand } = await import("../commands/create.js"); + + // Mock process.cwd and console.log + const originalCwd = process.cwd; + const originalLog = console.log; + process.cwd = () => testDir; + console.log = () => {}; // Suppress output + + try { + await createCommand.parseAsync([ + "node", + "create", + "--preset", + "local-folder", + "--id", + "test-docs", + "--name", + "Test Docs", + "--path", + "./docs", + ]); + + // Check config was created + const configExists = await fs + .access(configPath) + .then(() => true) + .catch(() => false); + expect(configExists).toBe(true); + + const config = await fs.readFile(configPath, "utf-8"); + expect(config).toContain("version: '1.0'"); + expect(config).toContain("id: test-docs"); + expect(config).toContain("name: Test Docs"); + expect(config).toContain("type: local_folder"); - expect(() => execSync(cmd, { cwd: testDir, stdio: "pipe" })).toThrow(); + // Check symlinks were created + const symlinkDir = join(testDir, ".knowledge", "docsets", "test-docs"); + const symlinkExists = await fs + .access(symlinkDir) + .then(() => true) + .catch(() => false); + expect(symlinkExists).toBe(true); + } finally { + process.cwd = originalCwd; + console.log = originalLog; + } }); it("fails with duplicate ID", async () => { - const cliPath = join(process.cwd(), "dist/index.js"); - // Create first docset - const cmd1 = `node ${cliPath} create --preset local-folder --id test-docs --name "Test Docs" --path ./docs`; - execSync(cmd1, { cwd: testDir }); - - // Try to create duplicate - const cmd2 = `node ${cliPath} create --preset local-folder --id test-docs --name "Test Docs 2" --path ./docs`; - expect(() => execSync(cmd2, { cwd: testDir, stdio: "pipe" })).toThrow(); + const { createCommand } = await import("../commands/create.js"); + + const originalCwd = process.cwd; + const originalLog = console.log; + process.cwd = () => testDir; + console.log = () => {}; + + try { + // Create first docset + await createCommand.parseAsync([ + "node", + "create", + "--preset", + "local-folder", + "--id", + "test-docs", + "--name", + "Test Docs", + "--path", + "./docs", + ]); + + // Try to create duplicate - should throw + await expect( + createCommand.parseAsync([ + "node", + "create", + "--preset", + "local-folder", + "--id", + "test-docs", + "--name", + "Test Docs 2", + "--path", + "./docs", + ]), + ).rejects.toThrow(); + } finally { + process.cwd = originalCwd; + console.log = originalLog; + } }); }); diff --git a/packages/cli/src/commands/create.ts b/packages/cli/src/commands/create.ts index 9e3f7ec..bf13983 100644 --- a/packages/cli/src/commands/create.ts +++ b/packages/cli/src/commands/create.ts @@ -26,9 +26,27 @@ export const createCommand = new Command("create") console.log(chalk.blue("🚀 Creating new docset...")); const configManager = new ConfigManager(); - const { config, configPath } = await configManager.loadConfig( - process.cwd(), - ); + + // Check if config exists, create if not + let config, configPath; + const configExists = await configManager.configExists(process.cwd()); + + if (!configExists) { + // Create initial config structure + configPath = path.join(process.cwd(), ".knowledge", "config.yaml"); + config = { + version: "1.0", + docsets: [], + }; + + // Ensure .knowledge directory exists + await fs.mkdir(path.dirname(configPath), { recursive: true }); + console.log(chalk.gray("📁 Created .knowledge directory")); + } else { + ({ config, configPath } = await configManager.loadConfig( + process.cwd(), + )); + } // Check if docset ID already exists if (config.docsets.find((d) => d.id === options.id)) { @@ -51,6 +69,24 @@ export const createCommand = new Command("create") config.docsets.push(newDocset); await configManager.saveConfig(config, configPath); + // For local folders, create symlinks immediately + if (options.preset === "local-folder") { + console.log(chalk.gray("🔗 Creating symlinks for local folder...")); + const { calculateLocalPathWithSymlinks } = await import( + "@codemcp/knowledge-core" + ); + try { + await calculateLocalPathWithSymlinks(newDocset, configPath); + console.log(chalk.gray(" ✅ Symlinks created successfully")); + } catch (error) { + console.log( + chalk.yellow( + ` ⚠️ Warning: Could not create symlinks: ${(error as Error).message}`, + ), + ); + } + } + console.log( chalk.green(`✅ Created docset '${options.id}' successfully`), ); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7ab1fd9..8c9f1b0 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -16,6 +16,7 @@ export { ConfigManager } from "./config/manager.js"; // Export path calculation utilities export { calculateLocalPath, + calculateLocalPathWithSymlinks, formatPath, validatePath, validatePathSync,