This commit is contained in:
134
src/commands/destroy.ts
Normal file
134
src/commands/destroy.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import * as path from 'path';
|
||||
import { Command } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import ora from 'ora';
|
||||
import {
|
||||
getWorkspacePath,
|
||||
loadWorkspaceConfig,
|
||||
sanitizeAgentName,
|
||||
hasUncommittedChanges,
|
||||
exportChangesAsPatch,
|
||||
getAllWorkspaces
|
||||
} from '../utils/file-utils';
|
||||
import {
|
||||
createGit,
|
||||
createWorktree,
|
||||
removeWorktree,
|
||||
deleteBranch,
|
||||
hasUncommittedChanges as gitHasUncommittedChanges,
|
||||
stashChanges
|
||||
} from '../utils/git-utils';
|
||||
import { GitAgentSyncError, WorkspaceNotFoundError } from '../utils/errors';
|
||||
|
||||
export function createDestroyCommand(): Command {
|
||||
const cmd = new Command('destroy')
|
||||
.alias('rm')
|
||||
.description('Safely remove an agent workspace')
|
||||
.argument('<agent-name>', 'Name of the agent workspace to destroy')
|
||||
.option('--force', 'Skip confirmation and remove without preservation')
|
||||
.option('--preserve', 'Preserve uncommitted changes as a patch file')
|
||||
.option('--output <path>', 'Path for patch file (if --preserve is used)')
|
||||
.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);
|
||||
}
|
||||
|
||||
const uncommitted = await gitHasUncommittedChanges(workspacePath);
|
||||
const spinner = ora('Checking workspace status...').start();
|
||||
|
||||
if (uncommitted && !options.force) {
|
||||
if (!options.preserve) {
|
||||
spinner.stop();
|
||||
const { shouldPreserve } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'shouldPreserve',
|
||||
message: `Workspace has uncommitted changes. Preserve them as a patch file?`,
|
||||
default: true
|
||||
}
|
||||
]);
|
||||
|
||||
if (shouldPreserve) {
|
||||
options.preserve = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spinner.text = 'Preparing to destroy workspace...';
|
||||
spinner.stop();
|
||||
|
||||
if (!options.force) {
|
||||
console.log(chalk.cyan('\n🗑️ Workspace Destruction Summary'));
|
||||
console.log(chalk.gray('─'.repeat(50)));
|
||||
console.log(` Agent: ${chalk.white(sanitizedName)}`);
|
||||
console.log(` Branch: ${chalk.white(config.branch)}`);
|
||||
console.log(` Path: ${chalk.white(workspacePath)}`);
|
||||
console.log(` Created: ${chalk.white(new Date(config.createdAt).toLocaleString())}`);
|
||||
console.log(` Uncommitted changes: ${uncommitted ? chalk.yellow('Yes') : chalk.green('No')}`);
|
||||
console.log(chalk.gray('─'.repeat(50)));
|
||||
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: `Are you sure you want to destroy workspace '${sanitizedName}'?`,
|
||||
default: false
|
||||
}
|
||||
]);
|
||||
|
||||
if (!confirm) {
|
||||
console.log(chalk.yellow('\n⏸️ Destruction cancelled.'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const destroySpinner = ora('Destroying workspace...').start();
|
||||
|
||||
if (uncommitted && options.preserve) {
|
||||
const patchPath = options.output || path.join(currentPath, `${sanitizedName}-changes.patch`);
|
||||
await exportChangesAsPatch(workspacePath, patchPath);
|
||||
destroySpinner.text = `Changes exported to ${patchPath}`;
|
||||
}
|
||||
|
||||
await removeWorktree(currentPath, workspacePath, config.branch);
|
||||
await deleteBranch(createGit(currentPath), config.branch, true);
|
||||
|
||||
const fs = await import('fs-extra');
|
||||
const configPath = path.join(workspacePath, '.agent-workspace.json');
|
||||
const envPath = path.join(workspacePath, '.env');
|
||||
|
||||
await fs.remove(configPath);
|
||||
await fs.remove(envPath);
|
||||
|
||||
const workspaceRoot = path.dirname(workspacePath);
|
||||
if (await fs.pathExists(workspaceRoot)) {
|
||||
const remaining = await fs.readdir(workspaceRoot);
|
||||
if (remaining.length === 0) {
|
||||
await fs.remove(workspaceRoot);
|
||||
}
|
||||
}
|
||||
|
||||
destroySpinner.succeed(chalk.green('Workspace destroyed successfully!'));
|
||||
|
||||
if (uncommitted && options.preserve) {
|
||||
console.log(chalk.yellow(`\n📦 Changes preserved at: ${options.output || `${sanitizedName}-changes.patch`}`));
|
||||
}
|
||||
|
||||
console.log(chalk.cyan('\n✨ Done'));
|
||||
|
||||
} catch (error) {
|
||||
const gitError = error instanceof GitAgentSyncError ? error : new GitAgentSyncError(String(error));
|
||||
console.error(chalk.red(`\n❌ Error: ${gitError.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
return cmd;
|
||||
}
|
||||
Reference in New Issue
Block a user