/** * 文件操作确认提示 * 显示 diff 对比并让用户确认 */ import * as readline from 'readline'; import * as fs from 'fs/promises'; import chalk from 'chalk'; import type { FilePermissionContext, PermissionDecision } from './types.js'; import { computeDiff, formatDiff, countChanges, formatEditDiff } from '../utils/diff.js'; /** * 显示文件写入的 diff 并请求确认 */ export async function promptFileWrite(ctx: FilePermissionContext): Promise { const { path: filePath, newContent } = ctx; if (!newContent) { // 没有内容,使用简单确认 return promptSimpleConfirm(ctx); } // 读取原文件内容 let oldContent: string | null = null; try { oldContent = await fs.readFile(filePath, 'utf-8'); } catch { // 文件不存在,是新文件 } // 如果内容相同,直接允许 if (oldContent === newContent) { return { allow: true, remember: false }; } // 计算 diff const diff = computeDiff(oldContent, newContent); const changes = countChanges(diff); // 显示 diff console.log(''); console.log(chalk.yellow('📝 文件写入预览')); console.log(chalk.cyan('文件: ') + chalk.white(filePath)); if (diff.isNew) { console.log(chalk.green('状态: ') + chalk.white('新文件')); console.log(chalk.green(`+${changes.additions} 行`)); } else { console.log(chalk.green(`+${changes.additions} 行`) + ' / ' + chalk.red(`-${changes.deletions} 行`)); } console.log(''); console.log(chalk.gray('─'.repeat(60))); // 限制显示行数 const diffOutput = formatDiff(diff, filePath); const lines = diffOutput.split('\n'); const MAX_LINES = 50; if (lines.length > MAX_LINES) { console.log(lines.slice(0, MAX_LINES).join('\n')); console.log(chalk.yellow(`\n... 省略 ${lines.length - MAX_LINES} 行 ...`)); } else { console.log(diffOutput); } console.log(chalk.gray('─'.repeat(60))); console.log(''); // 询问用户确认 return promptConfirm(); } /** * 显示文件编辑的 diff 并请求确认 */ export async function promptFileEdit(ctx: FilePermissionContext): Promise { const { path: filePath, oldContent, newContent } = ctx; if (!oldContent || !newContent) { // 没有内容,使用简单确认 return promptSimpleConfirm(ctx); } // 显示编辑 diff console.log(''); console.log(chalk.yellow('✏️ 文件编辑预览')); console.log(chalk.cyan('文件: ') + chalk.white(filePath)); console.log(''); console.log(chalk.gray('─'.repeat(60))); console.log(formatEditDiff(oldContent, newContent)); console.log(chalk.gray('─'.repeat(60))); console.log(''); // 询问用户确认 return promptConfirm(); } /** * 简单确认(无 diff) */ async function promptSimpleConfirm(ctx: FilePermissionContext): Promise { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); return new Promise((resolve) => { console.log(''); console.log(chalk.yellow('⚠️ 文件操作确认')); console.log(chalk.cyan('操作: ') + chalk.white(ctx.operation)); console.log(chalk.cyan('文件: ') + chalk.white(ctx.path)); console.log(''); showConfirmOptions(); rl.question(chalk.yellow('请选择 [y/Y/n/N]: '), (answer) => { rl.close(); resolve(parseAnswer(answer)); }); }); } /** * 通用确认提示 */ async function promptConfirm(): Promise { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); return new Promise((resolve) => { showConfirmOptions(); rl.question(chalk.yellow('请选择 [y/Y/n/N]: '), (answer) => { rl.close(); resolve(parseAnswer(answer)); }); }); } /** * 显示确认选项 */ function showConfirmOptions(): void { console.log(chalk.white('选择操作:')); console.log(chalk.green(' [y] ') + '确认执行'); console.log(chalk.green(' [Y] ') + '确认执行,并记住此类操作(本次会话)'); console.log(chalk.red(' [n] ') + '拒绝执行'); console.log(chalk.red(' [N] ') + '拒绝执行,并记住此类操作(本次会话)'); console.log(''); } /** * 解析用户输入 */ function parseAnswer(answer: string): PermissionDecision { const choice = answer.trim(); switch (choice) { case 'y': return { allow: true, remember: false }; case 'Y': return { allow: true, remember: true }; case 'N': return { allow: false, remember: true }; case 'n': default: return { allow: false, remember: false }; } } /** * 根据操作类型选择合适的确认提示 */ export async function promptFilePermission(ctx: FilePermissionContext): Promise { switch (ctx.operation) { case 'write': return promptFileWrite(ctx); case 'edit': return promptFileEdit(ctx); default: return promptSimpleConfirm(ctx); } }