code-transmute
Version:
Convert any codebase into any language ā without changing its brain.
299 lines ⢠13.7 kB
JavaScript
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;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlanCommand = void 0;
const chalk_1 = __importDefault(require("chalk"));
const path_1 = __importDefault(require("path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const config_1 = require("../core/config");
const openai_1 = require("../core/openai");
class PlanCommand {
configManager;
openaiService;
constructor() {
this.configManager = config_1.ConfigManager.getInstance();
this.openaiService = new openai_1.OpenAIService();
}
async execute(options) {
const config = await this.loadConfig(options);
await this.openaiService.initialize(config);
const outputPath = options.output || path_1.default.join(config.projectPath, 'code-transmute-output');
await fs_extra_1.default.ensureDir(outputPath);
// Load existing review if available
const reviewPath = path_1.default.join(outputPath, 'docs', 'review.md');
let codeReview = '';
if (await fs_extra_1.default.pathExists(reviewPath)) {
codeReview = await fs_extra_1.default.readFile(reviewPath, 'utf-8');
}
else {
console.log(chalk_1.default.yellow('No review found. Running analysis first...'));
const { ReviewCommand } = await Promise.resolve().then(() => __importStar(require('./review')));
const reviewCommand = new ReviewCommand();
const reviewResult = await reviewCommand.execute({ output: outputPath });
codeReview = reviewResult.review.summary;
}
console.log(chalk_1.default.blue('š§ Generating migration plan...'));
const migrationPlanMarkdown = await this.openaiService.generateMigrationPlan(codeReview, config.targetLanguage, config.targetFramework);
const plan = this.parseMigrationPlan(migrationPlanMarkdown, config);
// Generate plan files
await this.generatePlanFiles(plan, migrationPlanMarkdown, outputPath);
// Display summary
this.displayPlanSummary(plan);
return { outputPath, plan };
}
async loadConfig(options) {
const config = await this.configManager.getConfig();
if (!config) {
throw new Error('No configuration found. Run "code-transmute init" first.');
}
// Override with command line options
if (options.projectPath) {
config.projectPath = path_1.default.resolve(options.projectPath);
}
if (options.targetLanguage) {
config.targetLanguage = options.targetLanguage;
}
if (options.targetFramework) {
config.targetFramework = options.targetFramework;
}
return config;
}
parseMigrationPlan(markdown, _config) {
// Parse the markdown to extract structured plan data
const phases = this.extractPhases(markdown);
const dependencies = this.extractDependencies(markdown);
const commands = this.extractCommands(markdown);
const warnings = this.extractWarnings(markdown);
return {
phases,
timeline: this.extractTimeline(markdown),
dependencies,
commands,
warnings,
estimatedCost: this.estimateCost(phases)
};
}
extractPhases(markdown) {
const phases = [];
const phaseRegex = /## Phase (\d+): (.+)/g;
let match;
while ((match = phaseRegex.exec(markdown)) !== null) {
const phaseNumber = parseInt(match[1]);
const phaseName = match[2];
// Extract steps for this phase
const steps = this.extractStepsForPhase(markdown, phaseNumber);
phases.push({
name: phaseName,
description: this.extractPhaseDescription(markdown, phaseNumber),
steps,
estimatedTime: this.extractPhaseTime(markdown, phaseNumber),
dependencies: this.extractPhaseDependencies(markdown, phaseNumber)
});
}
return phases;
}
extractStepsForPhase(markdown, phaseNumber) {
const steps = [];
const stepRegex = new RegExp(`## Phase ${phaseNumber}[\\s\\S]*?(?=##|$)`, 'g');
const phaseSection = stepRegex.exec(markdown);
if (phaseSection) {
const stepMatches = phaseSection[0].match(/\d+\.\s+(.+)/g);
if (stepMatches) {
stepMatches.forEach((step, index) => {
const stepText = step.replace(/^\d+\.\s+/, '');
steps.push({
id: `phase-${phaseNumber}-step-${index + 1}`,
action: this.determineAction(stepText),
target: this.extractTarget(stepText),
description: stepText,
commands: this.extractCommandsFromStep(stepText),
validation: this.extractValidation(stepText)
});
});
}
}
return steps;
}
extractPhaseDescription(markdown, phaseNumber) {
const phaseRegex = new RegExp(`## Phase ${phaseNumber}[\\s\\S]*?(?=###|##|$)`, 'g');
const match = phaseRegex.exec(markdown);
return match ? match[0].replace(/## Phase \d+: .+\n/, '').trim() : '';
}
extractPhaseTime(markdown, phaseNumber) {
const timeRegex = new RegExp(`Phase ${phaseNumber}[\\s\\S]*?time[^:]*: ([^\\n]+)`, 'i');
const match = timeRegex.exec(markdown);
return match ? match[1] : 'Unknown';
}
extractPhaseDependencies(markdown, phaseNumber) {
const depsRegex = new RegExp(`Phase ${phaseNumber}[\\s\\S]*?dependencies[^:]*: ([^\\n]+)`, 'i');
const match = depsRegex.exec(markdown);
return match ? match[1].split(',').map(dep => dep.trim()) : [];
}
extractDependencies(markdown) {
const depsRegex = /dependencies[^:]*: ([^\n]+)/gi;
const matches = markdown.match(depsRegex);
const dependencies = [];
if (matches) {
matches.forEach(match => {
const deps = match.replace(/dependencies[^:]*: /i, '').split(',').map(dep => dep.trim());
dependencies.push(...deps);
});
}
return [...new Set(dependencies)];
}
extractCommands(markdown) {
const commandRegex = /```(?:bash|shell|sh)\n([\s\S]*?)\n```/g;
const commands = [];
let match;
while ((match = commandRegex.exec(markdown)) !== null) {
const commandBlock = match[1].trim();
commands.push(...commandBlock.split('\n').filter(cmd => cmd.trim()));
}
return commands;
}
extractWarnings(markdown) {
const warningRegex = /ā ļø|warning[^:]*: ([^\n]+)/gi;
const warnings = [];
let match;
while ((match = warningRegex.exec(markdown)) !== null) {
warnings.push(match[1] || match[0]);
}
return warnings;
}
extractTimeline(markdown) {
const timelineRegex = /timeline[^:]*: ([^\n]+)/i;
const match = timelineRegex.exec(markdown);
return match ? match[1] : 'Unknown';
}
determineAction(stepText) {
const lower = stepText.toLowerCase();
if (lower.includes('create') || lower.includes('generate'))
return 'create';
if (lower.includes('modify') || lower.includes('update') || lower.includes('change'))
return 'modify';
if (lower.includes('delete') || lower.includes('remove'))
return 'delete';
if (lower.includes('install') || lower.includes('add'))
return 'install';
if (lower.includes('configure') || lower.includes('setup'))
return 'configure';
return 'modify';
}
extractTarget(stepText) {
// Extract the target file or component from the step text
const targetRegex = /(?:to|in|for)\s+([^\s]+)/i;
const match = targetRegex.exec(stepText);
return match ? match[1] : 'unknown';
}
extractCommandsFromStep(stepText) {
const commandRegex = /`([^`]+)`/g;
const commands = [];
let match;
while ((match = commandRegex.exec(stepText)) !== null) {
commands.push(match[1]);
}
return commands;
}
extractValidation(stepText) {
if (stepText.toLowerCase().includes('verify') || stepText.toLowerCase().includes('check')) {
return 'Manual verification required';
}
return 'Automatic validation';
}
estimateCost(phases) {
// Rough estimation based on number of phases and complexity
return phases.length * 0.50; // $0.50 per phase estimate
}
async generatePlanFiles(plan, markdown, outputPath) {
const docsDir = path_1.default.join(outputPath, 'docs');
await fs_extra_1.default.ensureDir(docsDir);
// Generate migration_plan.md
await fs_extra_1.default.writeFile(path_1.default.join(docsDir, 'migration_plan.md'), markdown);
// Generate structured plan.json
await fs_extra_1.default.writeJson(path_1.default.join(outputPath, 'migration_plan.json'), plan, { spaces: 2 });
// Generate commands.sh script
const commandsScript = this.generateCommandsScript(plan);
await fs_extra_1.default.writeFile(path_1.default.join(outputPath, 'migration_commands.sh'), commandsScript);
await fs_extra_1.default.chmod(path_1.default.join(outputPath, 'migration_commands.sh'), 0o755);
console.log(chalk_1.default.green('ā
Plan files generated:'));
console.log(chalk_1.default.gray(` š ${path_1.default.join(docsDir, 'migration_plan.md')}`));
console.log(chalk_1.default.gray(` š ${path_1.default.join(outputPath, 'migration_plan.json')}`));
console.log(chalk_1.default.gray(` š§ ${path_1.default.join(outputPath, 'migration_commands.sh')}`));
}
generateCommandsScript(plan) {
let script = '#!/bin/bash\n\n';
script += '# Generated migration commands\n';
script += '# Run this script to execute the migration plan\n\n';
script += 'set -e # Exit on any error\n\n';
plan.phases.forEach((phase, index) => {
script += `echo "Phase ${index + 1}: ${phase.name}"\n`;
script += `echo "====================================="\n\n`;
phase.steps.forEach((step, stepIndex) => {
if (step.commands && step.commands.length > 0) {
script += `echo "Step ${stepIndex + 1}: ${step.description}"\n`;
step.commands.forEach(cmd => {
script += `${cmd}\n`;
});
script += 'echo "Step completed"\n\n';
}
});
});
script += 'echo "Migration plan completed!"\n';
return script;
}
displayPlanSummary(plan) {
console.log(chalk_1.default.blue.bold('\nš Migration Plan Summary'));
console.log(chalk_1.default.gray('ā'.repeat(50)));
console.log(chalk_1.default.cyan('Phases:'), chalk_1.default.white(plan.phases.length));
console.log(chalk_1.default.cyan('Total Steps:'), chalk_1.default.white(plan.phases.reduce((sum, phase) => sum + phase.steps.length, 0)));
console.log(chalk_1.default.cyan('Commands:'), chalk_1.default.white(plan.commands.length));
console.log(chalk_1.default.cyan('Timeline:'), chalk_1.default.white(plan.timeline));
if (plan.warnings.length > 0) {
console.log(chalk_1.default.yellow('\nā ļø Warnings:'));
plan.warnings.forEach(warning => {
console.log(chalk_1.default.gray(` ⢠${warning}`));
});
}
if (plan.estimatedCost) {
console.log(chalk_1.default.cyan('Estimated Cost:'), chalk_1.default.white(`$${plan.estimatedCost.toFixed(2)}`));
}
console.log(chalk_1.default.green('\nā
Plan generated! Run "code-transmute convert" to execute the migration.'));
}
}
exports.PlanCommand = PlanCommand;
//# sourceMappingURL=plan.js.map
;