feat: 升级至 v0.3.12 — 文件与代码类工具全面优化(16 项)
【阶段一 · 紧急修复 F1】 - F1-1 file_editor regex 计数 bug:强制 g 标志 + 同一 RegExp 实例做 match+replace - F1-2 code_search 统一 safeResolvePath(含路径遍历 + MEMORY.md 拦截) - F1-3 git_commit amend 模式:message 可选 + --no-edit 防止编辑器 hang - F1-4 write_file append 模式原子化:显式 open(O_APPEND)+write+fsync+close 【阶段二 · 增强现有工具 F2】 - F2-1 read_file 智能编码检测:BOM(UTF-8/UTF-16 LE/BE) + GBK 降级 - F2-2 read_file 支持 tail 模式(读取末尾 N 行,适用日志) - F2-3 search_files 二进制过滤 + 智能编码检测 - F2-4 search_files 多 glob 匹配(逗号分隔,如 *.ts,*.js) - F2-5 file_editor 新增 find_replace 操作(字面量替换,规避正则歧义) - F2-6 file_editor regex 支持跨行匹配(multiline 参数) - F2-7 file_editor 新增 backup 参数(编辑前 .bak 备份) 【阶段三 · 新增工具 F3】 - F3-1 file_move:双路径校验 + overwrite + 自动建父目录 + rename 原子 - F3-2 file_info:大小/时间/类型/编码/二进制/权限位 【阶段四 · 优化 F4】 - F4-1 diff_viewer Uint32Array→Uint16Array(省一半内存)+ safeResolvePath + 智能编码 - F4-2 错误处理统一:diff-viewer/file-editor/code-search catch 块改用 extractErrorMessage 【阶段五 · 验证 F5】 - tsc --noEmit 类型检查通过 - 人工审查通过:路径校验/错误处理/原子性/资源释放/权限策略/导出注册 工具数量:13 → 15(新增 file_move / file_info)
This commit is contained in:
@@ -149,16 +149,93 @@ export function matchGlob(name: string, glob: string): boolean {
|
||||
return new RegExp(`^${pattern}$`, 'i').test(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* F2-4: 多 glob 匹配(逗号分隔)
|
||||
*
|
||||
* 支持 "*.ts,*.js,*.tsx" 形式的多 glob 匹配,任一匹配即通过。
|
||||
* 单个 glob 时等价于 matchGlob。空字符串或空白字符串视为匹配所有。
|
||||
*
|
||||
* @param name 待匹配的文件名
|
||||
* @param globStr 通配符模式字符串(支持逗号分隔多 glob)
|
||||
* @returns 是否匹配任一 glob
|
||||
*/
|
||||
export function matchAnyGlob(name: string, globStr: string): boolean {
|
||||
// 按逗号分割,去除空白,过滤空字符串
|
||||
const globs = globStr.split(',').map((g) => g.trim()).filter((g) => g.length > 0);
|
||||
if (globs.length === 0) return true; // 空字符串视为匹配所有
|
||||
for (const g of globs) {
|
||||
if (matchGlob(name, g)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 共享错误提取
|
||||
*
|
||||
* 统一从 unknown 错误对象中提取 message 字符串
|
||||
* 统一从 unknown 错误对象中提取 message 字符串,可选附加 stderr 信息
|
||||
*
|
||||
* F4-2: 支持 optional stderr 参数,用于 child_process 错误(git/command)
|
||||
*
|
||||
* @param error catch 块中的 unknown 错误
|
||||
* @param includeStderr 是否尝试从 error.stderr 提取 stderr 信息(默认 false)
|
||||
* @returns 错误消息字符串
|
||||
*/
|
||||
export function extractErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
export function extractErrorMessage(error: unknown, includeStderr = false): string {
|
||||
if (error instanceof Error) {
|
||||
if (includeStderr) {
|
||||
const stderr = (error as Error & { stderr?: string }).stderr ?? '';
|
||||
return stderr ? `${error.message}\n${stderr}` : error.message;
|
||||
}
|
||||
return error.message;
|
||||
}
|
||||
return String(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* F2-1: 智能文件编码检测与解码
|
||||
*
|
||||
* 支持 BOM 检测(UTF-8 / UTF-16 LE / UTF-16 BE)和无 BOM 时的编码推断
|
||||
* (UTF-8 strict → GBK → UTF-8 loose 三级降级)。
|
||||
*
|
||||
* 解决 Windows 中文环境 GBK 文件读取乱码问题,以及 UTF-16 文件读取问题。
|
||||
*
|
||||
* @param buffer 文件/命令输出的原始字节
|
||||
* @returns 解码后的文本和检测到的编码名(utf-8 / utf-8-bom / utf-16le / utf-16be / gbk / utf-8-loose)
|
||||
*/
|
||||
export function decodeBufferWithDetection(buffer: Buffer): { content: string; encoding: string } {
|
||||
if (buffer.length === 0) {
|
||||
return { content: '', encoding: 'utf-8' };
|
||||
}
|
||||
|
||||
// BOM 检测
|
||||
// UTF-8 BOM: EF BB BF
|
||||
if (buffer.length >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
|
||||
return { content: buffer.slice(3).toString('utf-8'), encoding: 'utf-8-bom' };
|
||||
}
|
||||
// UTF-16 LE BOM: FF FE
|
||||
if (buffer.length >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) {
|
||||
return { content: buffer.slice(2).toString('utf16le'), encoding: 'utf-16le' };
|
||||
}
|
||||
// UTF-16 BE BOM: FE FF
|
||||
if (buffer.length >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) {
|
||||
const body = buffer.slice(2);
|
||||
// 偶数长度保护(UTF-16 每字符 2 字节)
|
||||
const safe = body.length % 2 === 0 ? body : body.slice(0, body.length - 1);
|
||||
const swapped = Buffer.from(safe); // 复制避免修改原 buffer
|
||||
swapped.swap16(); // BE → LE 字节交换
|
||||
return { content: swapped.toString('utf16le'), encoding: 'utf-16be' };
|
||||
}
|
||||
|
||||
// 无 BOM:UTF-8 strict → GBK → UTF-8 loose 三级降级
|
||||
try {
|
||||
return { content: new TextDecoder('utf-8', { fatal: true }).decode(buffer), encoding: 'utf-8' };
|
||||
} catch {
|
||||
try {
|
||||
return { content: new TextDecoder('gbk').decode(buffer), encoding: 'gbk' };
|
||||
} catch {
|
||||
return { content: buffer.toString('utf-8'), encoding: 'utf-8-loose' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user