This commit is contained in:
@@ -3,132 +3,58 @@ import { Command } from 'commander';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import inquirer from 'inquirer';
|
import inquirer from 'inquirer';
|
||||||
import ora from 'ora';
|
import ora from 'ora';
|
||||||
import {
|
import { getWorkspacePath, loadWorkspaceConfig, sanitizeAgentName, hasUncommittedChanges, exportChangesAsPatch } from '../utils/file-utils';
|
||||||
getWorkspacePath,
|
import { createGit, removeWorktree, deleteBranch, hasUncommittedChanges as gitHasUncommittedChanges, stashChanges } from '../utils/git-utils';
|
||||||
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';
|
import { GitAgentSyncError, WorkspaceNotFoundError } from '../utils/errors';
|
||||||
|
|
||||||
export function createDestroyCommand(): Command {
|
export function createDestroyCommand(): Command {
|
||||||
const cmd = new Command('destroy')
|
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').action(async (agentName, options) => {
|
||||||
.alias('rm')
|
try {
|
||||||
.description('Safely remove an agent workspace')
|
const currentPath = process.cwd();
|
||||||
.argument('<agent-name>', 'Name of the agent workspace to destroy')
|
const sanitizedName = sanitizeAgentName(agentName);
|
||||||
.option('--force', 'Skip confirmation and remove without preservation')
|
const workspacePath = getWorkspacePath(currentPath, sanitizedName);
|
||||||
.option('--preserve', 'Preserve uncommitted changes as a patch file')
|
const config = await loadWorkspaceConfig(workspacePath);
|
||||||
.option('--output <path>', 'Path for patch file (if --preserve is used)')
|
if (!config) throw new WorkspaceNotFoundError(sanitizedName);
|
||||||
.action(async (agentName, options) => {
|
const uncommitted = await gitHasUncommittedChanges(workspacePath);
|
||||||
try {
|
const spinner = ora('Checking workspace status...').start();
|
||||||
const currentPath = process.cwd();
|
if (uncommitted && !options.force) {
|
||||||
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();
|
spinner.stop();
|
||||||
|
if (!options.preserve) {
|
||||||
if (!options.force) {
|
const { shouldPreserve } = await inquirer.prompt([{ type: 'confirm', name: 'shouldPreserve', message: `Workspace has uncommitted changes. Preserve them as a patch file?`, default: true }]);
|
||||||
console.log(chalk.cyan('\n🗑️ Workspace Destruction Summary'));
|
if (shouldPreserve) options.preserve = true;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
});
|
if (!options.force) {
|
||||||
|
console.log(chalk.cyan('\n🗑️ Workspace Destruction Summary'));
|
||||||
|
console.log(` Agent: ${chalk.white(sanitizedName)}`);
|
||||||
|
console.log(` Branch: ${chalk.white(config.branch)}`);
|
||||||
|
console.log(` Uncommitted changes: ${uncommitted ? chalk.yellow('Yes') : chalk.green('No')}`);
|
||||||
|
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');
|
||||||
|
await fs.remove(configPath);
|
||||||
|
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`}`));
|
||||||
|
} 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;
|
return cmd;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user