UNPKG

@craftapit/tester

Version:

A focused, LLM-powered testing framework for natural language test scenarios

222 lines (221 loc) 9.77 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.CraftACoderCLIAdapter = void 0; const BaseAdapter_1 = require("./BaseAdapter"); const child_process_1 = require("child_process"); const util_1 = require("util"); const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const logger_1 = require("../utils/logger"); const execAsync = (0, util_1.promisify)(child_process_1.exec); class CraftACoderCLIAdapter extends BaseAdapter_1.BaseAdapter { constructor(config = {}) { super(config); this.sessionId = null; this.cliPath = config.cliPath || 'craftacoder'; this.workingDir = config.workingDir || process.cwd(); this.logger = new logger_1.Logger('CraftACoderCLIAdapter'); } async initialize() { this.logger.info('Initializing CraftACoder CLI adapter'); try { // Check if the CLI is installed and accessible const { stdout } = await execAsync(`${this.cliPath} --version`); this.logger.info(`CraftACoder CLI version: ${stdout.trim()}`); } catch (error) { this.logger.error('Failed to initialize CraftACoder CLI', error); throw new Error('CraftACoder CLI not found or not accessible. Please make sure it is installed and in your PATH.'); } } async cleanup() { this.logger.info('Cleaning up CraftACoder CLI adapter'); if (this.sessionId) { try { await execAsync(`${this.cliPath} session end --id ${this.sessionId}`, { cwd: this.workingDir }); this.logger.info(`Ended session ${this.sessionId}`); } catch (error) { this.logger.warn(`Failed to end session ${this.sessionId}`, error); } this.sessionId = null; } } async startSession() { this.logger.info('Starting new CraftACoder session'); try { const { stdout } = await execAsync(`${this.cliPath} session start`, { cwd: this.workingDir }); // Extract session ID from output const sessionMatch = stdout.match(/Session ID: ([a-zA-Z0-9-]+)/); if (!sessionMatch) { throw new Error('Failed to extract session ID from CLI output'); } this.sessionId = sessionMatch[1]; this.logger.info(`Started session with ID: ${this.sessionId}`); return this.sessionId; } catch (error) { this.logger.error('Failed to start CraftACoder session', error); throw error; } } async generateCode(prompt) { if (!this.sessionId) { throw new Error('No active session. Call startSession() first.'); } this.logger.info('Generating code with CraftACoder'); // Write prompt to a temporary file const promptFile = path.join(this.workingDir, '.craftacoder-prompt.txt'); await fs.writeFile(promptFile, prompt); try { const { stdout } = await execAsync(`${this.cliPath} generate --session ${this.sessionId} --prompt-file ${promptFile}`, { cwd: this.workingDir }); // Clean up the temporary file await fs.unlink(promptFile).catch(() => { }); // Extract the generated code files from the output const filesGenerated = stdout.match(/Generated file: ([^\n]+)/g); if (!filesGenerated) { this.logger.warn('No files were generated'); return ''; } const fileList = filesGenerated.map(line => line.replace('Generated file: ', '').trim()); this.logger.info(`Generated ${fileList.length} files: ${fileList.join(', ')}`); return fileList.join('\n'); } catch (error) { this.logger.error('Failed to generate code', error); // Clean up the temporary file await fs.unlink(promptFile).catch(() => { }); throw error; } } async improveCode(feedback) { if (!this.sessionId) { throw new Error('No active session. Call startSession() first.'); } this.logger.info('Improving code with CraftACoder'); // Write feedback to a temporary file const feedbackFile = path.join(this.workingDir, '.craftacoder-feedback.txt'); await fs.writeFile(feedbackFile, feedback); try { const { stdout } = await execAsync(`${this.cliPath} improve --session ${this.sessionId} --feedback-file ${feedbackFile}`, { cwd: this.workingDir }); // Clean up the temporary file await fs.unlink(feedbackFile).catch(() => { }); // Extract the improved code files from the output const filesImproved = stdout.match(/Improved file: ([^\n]+)/g); if (!filesImproved) { this.logger.warn('No files were improved'); return ''; } const fileList = filesImproved.map(line => line.replace('Improved file: ', '').trim()); this.logger.info(`Improved ${fileList.length} files: ${fileList.join(', ')}`); return fileList.join('\n'); } catch (error) { this.logger.error('Failed to improve code', error); // Clean up the temporary file await fs.unlink(feedbackFile).catch(() => { }); throw error; } } async runTests(command) { this.logger.info(`Running tests with command: ${command}`); try { const { stdout, stderr } = await execAsync(command, { cwd: this.workingDir }); const output = stdout + (stderr ? `\n${stderr}` : ''); const success = !output.includes('FAIL') && !output.includes('ERROR'); this.logger.info(`Tests ${success ? 'passed' : 'failed'}`); return { success, output }; } catch (error) { this.logger.error('Test execution failed', error); // Even if the command fails, we want to capture the output return { success: false, output: error instanceof Error ? error.message : String(error) }; } } async explainCode(filePath) { if (!this.sessionId) { throw new Error('No active session. Call startSession() first.'); } this.logger.info(`Explaining code in file: ${filePath}`); try { const { stdout } = await execAsync(`${this.cliPath} explain --session ${this.sessionId} --file ${filePath}`, { cwd: this.workingDir }); return stdout.trim(); } catch (error) { this.logger.error('Failed to explain code', error); throw error; } } async addFeature(description) { if (!this.sessionId) { throw new Error('No active session. Call startSession() first.'); } this.logger.info('Adding feature with CraftACoder'); // Write description to a temporary file const descFile = path.join(this.workingDir, '.craftacoder-feature.txt'); await fs.writeFile(descFile, description); try { const { stdout } = await execAsync(`${this.cliPath} feature --session ${this.sessionId} --description-file ${descFile}`, { cwd: this.workingDir }); // Clean up the temporary file await fs.unlink(descFile).catch(() => { }); // Extract the modified files from the output const filesModified = stdout.match(/(Created|Modified) file: ([^\n]+)/g); if (!filesModified) { this.logger.warn('No files were modified'); return ''; } const fileList = filesModified.map(line => line.replace(/(Created|Modified) file: /, '').trim()); this.logger.info(`Modified ${fileList.length} files: ${fileList.join(', ')}`); return fileList.join('\n'); } catch (error) { this.logger.error('Failed to add feature', error); // Clean up the temporary file await fs.unlink(descFile).catch(() => { }); throw error; } } } exports.CraftACoderCLIAdapter = CraftACoderCLIAdapter;