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