diff --git a/src/commands/merge.ts b/src/commands/merge.ts index a7d85f5..88fe180 100644 --- a/src/commands/merge.ts +++ b/src/commands/merge.ts @@ -1,79 +1,51 @@ +import * as path from 'path'; import { Command } from 'commander'; import chalk from 'chalk'; -import { - getWorkspacePath, - loadWorkspaceConfig, - sanitizeAgentName, - hasUncommittedChanges -} from '../utils/file-utils'; -import { - createGit, - mergeToMain, - hasUncommittedChanges as gitHasUncommittedChanges -} from '../utils/git-utils'; -import { GitAgentSyncError, WorkspaceNotFoundError, MergeFailedError } from '../utils/errors'; +import inquirer from 'inquirer'; +import ora from 'ora'; +import { getWorkspacePath, loadWorkspaceConfig, sanitizeAgentName, readPackageJson } from '../utils/file-utils'; +import { mergeToMain, createGit, hasUncommittedChanges as gitHasUncommittedChanges } from '../utils/git-utils'; +import { GitAgentSyncError, WorkspaceNotFoundError, MergeConflictError } from '../utils/errors'; +import { MergeOptions } from '../types'; export function createMergeCommand(): Command { - const cmd = new Command('merge') - .description('Safely merge agent changes back to main branch') - .argument('', 'Name of the agent workspace to merge') - .option('--force', 'Force merge even with uncommitted changes') - .option('--dry-run', 'Show what would happen without making changes') - .option('-m, --message ', 'Custom commit message') - .action(async (agentName, options) => { - try { - const currentPath = process.cwd(); - const sanitizedName = sanitizeAgentName(agentName); - const workspacePath = getWorkspacePath(currentPath, sanitizedName); - - const config = await loadWorkspaceConfig(workspacePath); - if (!config) { - throw new WorkspaceNotFoundError(sanitizedName); - } - - if (!options.force && await hasUncommittedChanges(workspacePath)) { - throw new GitAgentSyncError('Workspace has uncommitted changes. Commit or stash them first, or use --force.'); - } - - const workspaceGit = createGit(workspacePath); - - console.log(chalk.cyan(`\n🔀 Merging ${sanitizedName} into ${config.mainBranch}...`)); - - const result = await mergeToMain( - currentPath, - workspacePath, - sanitizedName, - config.mainBranch, - options.message, - options.dryRun - ); - - if (result.success) { - if (options.dryRun) { - console.log(chalk.yellow(`\n⚠ [DRY RUN] ${result.message}`)); - } else { - console.log(chalk.green('\n✓ Merge completed successfully!')); - console.log(` Message: ${result.message}`); - } - } else { - if (result.conflicts && result.conflicts.length > 0) { - console.log(chalk.red('\n❌ Merge conflicts detected!')); - console.log(chalk.cyan('\nConflicting files:')); - for (const conflict of result.conflicts) { - console.log(` ${chalk.red(conflict.file)}`); - } - console.log(chalk.cyan('\n💡 Resolve conflicts manually and commit before trying again.')); - } else { - throw new MergeFailedError(sanitizedName, result.error || 'Unknown error'); - } - } - - } catch (error) { - const gitError = error instanceof GitAgentSyncError ? error : new GitAgentSyncError(String(error)); - console.error(chalk.red(`\n❌ Error: ${gitError.message}`)); - process.exit(1); + const cmd = new Command('merge').description('Safely merge agent changes back to main branch').argument('', 'Name of the agent workspace to merge').option('--force', 'Force merge even with conflicts').option('--dry-run', 'Preview merge without making changes').option('--message ', 'Custom merge commit message').action(async (agentName, options: MergeOptions) => { + try { + const currentPath = process.cwd(); + const sanitizedName = sanitizeAgentName(agentName); + const workspacePath = getWorkspacePath(currentPath, sanitizedName); + const config = await loadWorkspaceConfig(workspacePath); + if (!config) throw new WorkspaceNotFoundError(sanitizedName); + const spinner = ora('Preparing for merge...').start(); + const uncommitted = await gitHasUncommittedChanges(workspacePath); + if (uncommitted) { + spinner.stop(); + console.log(chalk.yellow('\n⚠️ Workspace has uncommitted changes.')); } - }); - + const mainBranch = config.mainBranch; + spinner.text = 'Merging changes...'; + const mergeResult = await mergeToMain(currentPath, workspacePath, sanitizedName, mainBranch, options.message, options.dryRun); + if (options.dryRun) { + spinner.stop(); + console.log(chalk.cyan('\n🔍 Dry Run Result:')); + console.log(` ${mergeResult.message}`); + return; + } + if (!mergeResult.success && mergeResult.conflicts) { + spinner.fail(chalk.red('Merge conflicts detected!')); + throw new MergeConflictError(mergeResult.conflicts.map(c => c.file)); + } + spinner.succeed(chalk.green('Merge completed successfully!')); + console.log(chalk.cyan('\n📦 Merge Summary')); + console.log(` Agent: ${chalk.white(sanitizedName)}`); + console.log(` From Branch: ${chalk.white(config.branch)}`); + console.log(` To Branch: ${chalk.white(mainBranch)}`); + if (mergeResult.commitSha) console.log(` Commit: ${chalk.white(mergeResult.commitSha.substring(0, 7))}`); + } catch (error) { + if (error instanceof MergeConflictError) console.error(chalk.red(`\n❌ ${error.message}`)); + else { const gitError = error instanceof GitAgentSyncError ? error : new GitAgentSyncError(String(error)); console.error(chalk.red(`\n❌ Error: ${gitError.message}`)); } + process.exit(1); + } + }); return cmd; -} +} \ No newline at end of file