Skip to content

Commit 3949f5c

Browse files
committed
refactor cli commands to individual files
1 parent 214e657 commit 3949f5c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+3194
-1086
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// bin/commands/config/clear-config.js
2+
import fs from 'fs';
3+
import chalk from 'chalk';
4+
5+
export async function clearConfig(configPath) {
6+
if (fs.existsSync(configPath)) {
7+
fs.unlinkSync(configPath);
8+
console.log(chalk.green("🗑️ Configuration deleted successfully."));
9+
return true;
10+
} else {
11+
console.warn(chalk.yellow("⚠️ No configuration file found."));
12+
return false;
13+
}
14+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// tests/commands/config/config-commands.test.js
2+
import { jest, describe, expect, test, beforeEach } from '@jest/globals';
3+
import chalk from 'chalk';
4+
5+
// Create mock fs functions
6+
const mockFsImpl = {
7+
existsSync: jest.fn(),
8+
readFileSync: jest.fn(),
9+
writeFileSync: jest.fn(),
10+
unlinkSync: jest.fn()
11+
};
12+
13+
// Mock fs module
14+
await jest.unstable_mockModule('fs', () => mockFsImpl);
15+
16+
// Import after mocking
17+
const { showConfig } = await import('../../../bin/commands/config/show-config.js');
18+
const { clearConfig } = await import('../../../bin/commands/config/clear-config.js');
19+
20+
describe('Config Commands', () => {
21+
const mockConfig = {
22+
mongoUrl: 'mongodb+srv://test:test@cluster.mongodb.net',
23+
database: 'test_db',
24+
collection: 'test_collection',
25+
embedding: {
26+
provider: 'openai',
27+
apiKey: 'test-key'
28+
}
29+
};
30+
31+
beforeEach(() => {
32+
jest.clearAllMocks();
33+
mockFsImpl.existsSync.mockReturnValue(true);
34+
mockFsImpl.readFileSync.mockReturnValue(JSON.stringify(mockConfig));
35+
});
36+
37+
test('showConfig displays configuration correctly', async () => {
38+
mockFsImpl.existsSync.mockReturnValue(true);
39+
mockFsImpl.readFileSync.mockReturnValue(JSON.stringify(mockConfig));
40+
41+
const result = await showConfig('test-path');
42+
expect(result).toEqual(mockConfig);
43+
expect(mockFsImpl.readFileSync).toHaveBeenCalledWith('test-path', 'utf-8');
44+
});
45+
46+
test('clearConfig removes configuration file', async () => {
47+
mockFsImpl.existsSync.mockReturnValue(true);
48+
49+
const result = await clearConfig('test-path');
50+
expect(result).toBe(true);
51+
expect(mockFsImpl.unlinkSync).toHaveBeenCalledWith('test-path');
52+
});
53+
54+
test('clearConfig handles missing file', async () => {
55+
mockFsImpl.existsSync.mockReturnValue(false);
56+
57+
const result = await clearConfig('test-path');
58+
expect(result).toBe(false);
59+
expect(mockFsImpl.unlinkSync).not.toHaveBeenCalled();
60+
});
61+
});

bin/commands/config/edit-config.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// bin/commands/config/edit-config.js
2+
import fs from 'fs';
3+
import chalk from 'chalk';
4+
import { promptForConfigEdits } from '../../utils/prompts.js';
5+
6+
export async function editConfig(configPath) {
7+
if (!fs.existsSync(configPath)) {
8+
console.warn(chalk.yellow("⚠️ No configuration found. Run 'npx mongodb-rag init' first."));
9+
return;
10+
}
11+
12+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
13+
const updatedConfig = await promptForConfigEdits(config);
14+
15+
fs.writeFileSync(configPath, JSON.stringify(updatedConfig, null, 2));
16+
console.log(chalk.green("✅ Configuration updated successfully!"));
17+
18+
return updatedConfig;
19+
}

bin/commands/config/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// bin/commands/config/index.js
2+
export { showConfig } from './show-config.js';
3+
export { editConfig } from './edit-config.js';
4+
export { clearConfig } from './clear-config.js';
5+
export { resetConfig } from './reset-config.js';
6+
export { setIndexName } from './set-index-name.js';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// bin/commands/config/reset-config.js
2+
import fs from 'fs';
3+
import chalk from 'chalk';
4+
import { init } from '../init.js';
5+
6+
export async function resetConfig(configPath) {
7+
console.log(chalk.cyan("🔄 Resetting configuration...\n"));
8+
9+
if (fs.existsSync(configPath)) {
10+
fs.unlinkSync(configPath);
11+
}
12+
13+
return await init(configPath);
14+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// bin/commands/config/set-index-name.js
2+
import fs from 'fs';
3+
import chalk from 'chalk';
4+
import { promptForIndexName } from '../../utils/prompts.js';
5+
6+
export async function setIndexName(configPath) {
7+
if (!fs.existsSync(configPath)) {
8+
console.warn(chalk.yellow("⚠️ No configuration found. Run 'npx mongodb-rag init' first."));
9+
return;
10+
}
11+
12+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
13+
const indexName = await promptForIndexName(config.indexName || 'vector_index');
14+
15+
config.indexName = indexName;
16+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
17+
console.log(chalk.green(`✅ Vector Index Name updated to "${indexName}"`));
18+
19+
return config;
20+
}

bin/commands/config/show-config.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// bin/commands/config/show-config.js
2+
import fs from 'fs';
3+
import debug from 'debug';
4+
5+
const log = debug('mongodb-rag:cli:config');
6+
7+
export async function showConfig(configPath = '.mongodb-rag.json') {
8+
try {
9+
if (!fs.existsSync(configPath)) {
10+
throw new Error(`Configuration file not found: ${configPath}`);
11+
}
12+
const content = fs.readFileSync(configPath, 'utf-8');
13+
return JSON.parse(content);
14+
} catch (error) {
15+
throw new Error(`Failed to show config: ${error.message}`);
16+
}
17+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// bin/commands/data/generate-embedding.js
2+
import chalk from 'chalk';
3+
import { isConfigValid } from '../../utils/validation.js';
4+
import MongoRAG from '../../../src/core/MongoRAG.js';
5+
6+
export async function generateEmbedding(config, text) {
7+
const isDevelopment = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test';
8+
9+
// Clean up any duplicate configuration
10+
const cleanConfig = {
11+
...config,
12+
// Remove any root-level embedding properties
13+
provider: undefined,
14+
apiKey: undefined,
15+
model: undefined,
16+
dimensions: undefined,
17+
baseUrl: undefined
18+
};
19+
20+
if (!isConfigValid(cleanConfig)) {
21+
throw new Error("Invalid configuration. Please run 'npx mongodb-rag init' to set up your configuration properly.");
22+
}
23+
24+
const rag = new MongoRAG(cleanConfig);
25+
try {
26+
if (isDevelopment) {
27+
console.log(chalk.blue('🔄 Getting embedding for text:'), text);
28+
}
29+
30+
await rag.connect();
31+
const embedding = await rag.createEmbedding(text);
32+
33+
if (isDevelopment) {
34+
console.log(chalk.cyan("🔢 Generated Embedding:"), embedding);
35+
console.log(chalk.green(`✅ Successfully generated ${embedding.length}-dimensional embedding`));
36+
} else {
37+
console.log(chalk.green('✅ Successfully generated embedding'));
38+
}
39+
40+
return embedding;
41+
} catch (error) {
42+
console.error(chalk.red("❌ Error generating embedding:"), error.message);
43+
44+
if (config.embedding.provider === 'ollama') {
45+
console.log(chalk.yellow('\nTroubleshooting:'));
46+
console.log(chalk.cyan('1. Ensure Ollama is running'));
47+
console.log(chalk.cyan(`2. Check if Ollama is accessible at ${config.embedding.baseUrl}`));
48+
console.log(chalk.cyan('3. Check if the model is installed with `ollama list`'));
49+
}
50+
51+
throw error;
52+
} finally {
53+
await rag.client?.close();
54+
}
55+
}

bin/commands/data/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// bin/commands/data/index.js
2+
export { searchDocuments } from './search.js';
3+
export { ingestData } from './ingest.js';
4+
export { generateEmbedding } from './generate-embedding.js';

bin/commands/data/ingest.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// bin/commands/data/ingest.js
2+
import fs from 'fs';
3+
import path from 'path';
4+
import chalk from 'chalk';
5+
import { isConfigValid } from '../../utils/validation.js';
6+
import MongoRAG from '../../../src/core/MongoRAG.js';
7+
import { parseDocument } from '../../utils/document-parsers.js';
8+
import { DocumentChunker } from '../../utils/chunking.js';
9+
10+
export async function ingestData(config, options) {
11+
if (!isConfigValid(config)) {
12+
throw new Error("Configuration missing. Run 'npx mongodb-rag init' first.");
13+
}
14+
15+
try {
16+
let documents = [];
17+
const isDevelopment = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test';
18+
19+
// Handle directory ingestion
20+
if (options.directory) {
21+
if (isDevelopment) console.log(chalk.blue(`📂 Processing directory: ${options.directory}`));
22+
documents = await processDirectory(options.directory, options);
23+
}
24+
// Handle single file ingestion
25+
else if (options.file) {
26+
if (isDevelopment) console.log(chalk.blue(`📄 Processing file: ${options.file}`));
27+
documents = await processFile(options.file, options);
28+
}
29+
else {
30+
throw new Error("Either --file or --directory option must be specified");
31+
}
32+
33+
// Initialize chunker if chunking is enabled
34+
if (options.chunkSize) {
35+
const chunker = new DocumentChunker({
36+
chunkSize: options.chunkSize,
37+
chunkOverlap: options.chunkOverlap,
38+
method: options.chunkMethod
39+
});
40+
41+
// Chunk each document
42+
const chunkedDocs = [];
43+
for (const doc of documents) {
44+
if (isDevelopment) {
45+
console.log(chalk.blue(`📄 Chunking document: ${doc.metadata.filename}`));
46+
}
47+
const chunks = chunker.chunkDocument(doc);
48+
chunkedDocs.push(...chunks);
49+
50+
if (isDevelopment) {
51+
console.log(chalk.green(`✅ Created ${chunks.length} chunks`));
52+
}
53+
}
54+
documents = chunkedDocs;
55+
}
56+
57+
if (isDevelopment) {
58+
console.log(chalk.blue(`📊 Found ${documents.length} documents to process`));
59+
}
60+
61+
const rag = new MongoRAG(config);
62+
await rag.connect();
63+
64+
const result = await rag.ingestBatch(documents, {
65+
database: options.database,
66+
collection: options.collection
67+
});
68+
69+
console.log(chalk.green(`✅ Successfully ingested ${result.processed} documents!`));
70+
return result;
71+
} catch (error) {
72+
console.error(chalk.red('❌ Ingestion failed:'), error.message);
73+
throw error;
74+
}
75+
}
76+
77+
async function processDirectory(dirPath, options) {
78+
const documents = [];
79+
const files = fs.readdirSync(dirPath);
80+
81+
for (const file of files) {
82+
const filePath = path.join(dirPath, file);
83+
const stat = fs.statSync(filePath);
84+
85+
if (stat.isDirectory() && options.recursive) {
86+
const subDocs = await processDirectory(filePath, options);
87+
documents.push(...subDocs);
88+
} else if (stat.isFile()) {
89+
const docs = await processFile(filePath, options);
90+
documents.push(...docs);
91+
}
92+
}
93+
94+
return documents;
95+
}
96+
97+
async function processFile(filePath, options) {
98+
const ext = path.extname(filePath).toLowerCase();
99+
const isDevelopment = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test';
100+
101+
try {
102+
// If it's a JSON file, parse it directly
103+
if (ext === '.json') {
104+
const content = fs.readFileSync(filePath, 'utf-8');
105+
const data = JSON.parse(content);
106+
return Array.isArray(data) ? data : [data];
107+
}
108+
109+
// For other file types, use the document parser
110+
const doc = await parseDocument(filePath, null, options);
111+
112+
if (isDevelopment) {
113+
console.log(chalk.blue(`📄 Processed ${filePath}`));
114+
if (doc.metadata.processingFailed) {
115+
console.warn(chalk.yellow(`⚠️ Warning: ${doc.metadata.error}`));
116+
}
117+
}
118+
119+
return [doc];
120+
} catch (error) {
121+
if (isDevelopment) {
122+
console.error(chalk.red(`❌ Failed to process ${filePath}:`), error.message);
123+
}
124+
return []; // Skip failed files
125+
}
126+
}

0 commit comments

Comments
 (0)