@craftapit/tester
Version:
A focused, LLM-powered testing framework for natural language test scenarios
222 lines (221 loc) • 9.77 kB
JavaScript
"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;