UNPKG

i18n-automatically-cli

Version:
300 lines (255 loc) 8.1 kB
const fs = require("fs"); const path = require("path"); const chalk = require("chalk"); const ora = require("ora"); const { processFile } = require("../core/processor"); const { ensureConfigExists, readConfig } = require("../utils/config"); // 动态导入 inquirer async function getInquirer() { const inquirer = await import("inquirer"); return inquirer.default; } async function scanBatchCommand(options = {}) { try { // 确保配置文件存在 ensureConfigExists(); const config = readConfig(); let targetDir; // 如果没有指定目录,让用户选择 if (!options.dir || options.dir === ".") { targetDir = await selectTargetDirectory(); } else { targetDir = path.resolve(options.dir); } const excludePatterns = options.exclude || []; console.log(chalk.blue(`🔍 开始批量扫描目录: ${targetDir}`)); if (!fs.existsSync(targetDir)) { console.error(chalk.red(`目录不存在: ${targetDir}`)); process.exit(1); } const spinner = ora("正在扫描文件...").start(); try { const files = getFilesToProcess( targetDir, config.excludedExtensions, excludePatterns ); if (files.length === 0) { spinner.warn(chalk.yellow("未找到需要处理的文件")); return; } spinner.text = `找到 ${files.length} 个文件,开始处理...`; // 显示将要处理的文件列表 console.log(chalk.gray("\n将要处理的文件类型:")); const filesByExt = groupFilesByExtension(files); Object.entries(filesByExt).forEach(([ext, count]) => { console.log(chalk.gray(` ${ext}: ${count} 个文件`)); }); console.log(chalk.cyan(`\n🚀 自动开始处理 ${files.length} 个文件...`)); let totalChanges = 0; let processedFiles = 0; let errorFiles = 0; const errors = []; console.log(chalk.blue(`\n🚀 开始处理文件...`)); for (const file of files) { const relativePath = path.relative(targetDir, file); spinner.text = `处理中 (${processedFiles + 1}/${files.length}): ${relativePath}`; try { const result = await processFile(file); if (result.success) { totalChanges += result.changes; processedFiles++; // 显示处理结果 if (result.changes > 0) { console.log( chalk.green(` ✓ ${relativePath} (${result.changes} 处修改)`) ); } else { console.log(chalk.gray(` - ${relativePath} (无需修改)`)); } } else { errorFiles++; errors.push(`${file}: ${result.errors.join(", ")}`); console.log(chalk.red(` ✗ ${relativePath} (处理失败)`)); } } catch (error) { errorFiles++; errors.push(`${file}: ${error}`); console.log( chalk.red(` ✗ ${relativePath} (异常: ${error.message})`) ); } } if (errorFiles > 0) { spinner.fail( chalk.yellow(`⚠️ 批量扫描完成,但有 ${errorFiles} 个文件处理失败`) ); console.log(chalk.green(`✅ 成功处理: ${processedFiles} 个文件`)); console.log(chalk.green(`🔄 总共替换: ${totalChanges} 个中文字符串`)); console.log(chalk.red(`❌ 失败文件: ${errorFiles} 个`)); if (errors.length > 0) { console.log(chalk.red("\n错误详情:")); errors.forEach((error) => { console.log(chalk.red(` ${error}`)); }); } } else { spinner.succeed(chalk.green(`✅ 批量扫描完成!`)); console.log(chalk.green(`📁 处理文件: ${processedFiles} 个`)); console.log(chalk.green(`🔄 总共替换: ${totalChanges} 个中文字符串`)); } if (totalChanges > 0) { console.log( chalk.cyan('\n💡 提示: 运行 "i18n-auto generate" 来生成多语言包') ); } } catch (error) { spinner.fail(chalk.red("❌ 批量扫描过程中出现错误")); console.error(chalk.red(error)); process.exit(1); } } catch (error) { console.error(chalk.red("批量扫描命令执行失败:"), error); process.exit(1); } } async function selectTargetDirectory() { const currentDir = process.cwd(); const availableDirs = getSubDirectories(currentDir); if (availableDirs.length === 0) { console.log(chalk.yellow("当前目录下没有子目录,将扫描当前目录")); return currentDir; } const inquirer = await getInquirer(); const choices = [ { name: ". (当前目录)", value: currentDir }, ...availableDirs.map((dir) => ({ name: `${dir}/`, value: path.join(currentDir, dir), })), { name: "📝 手动输入路径", value: "custom" }, ]; const { selectedDir } = await inquirer.prompt([ { type: "list", name: "selectedDir", message: "选择要扫描的目录:", choices, pageSize: 10, }, ]); if (selectedDir === "custom") { const { customPath } = await inquirer.prompt([ { type: "input", name: "customPath", message: "请输入目录路径:", validate: (input) => { const resolvedPath = path.resolve(input); if (!fs.existsSync(resolvedPath)) { return "路径不存在,请重新输入"; } if (!fs.statSync(resolvedPath).isDirectory()) { return "请输入有效的目录路径"; } return true; }, }, ]); return path.resolve(customPath); } return selectedDir; } function getSubDirectories(dir) { try { const items = fs.readdirSync(dir); const directories = []; for (const item of items) { const fullPath = path.join(dir, item); try { const stat = fs.statSync(fullPath); if (stat.isDirectory() && !shouldSkipDirectory(item)) { directories.push(item); } } catch (error) { // 忽略无法访问的目录 continue; } } return directories.sort(); } catch (error) { return []; } } function groupFilesByExtension(files) { const groups = {}; files.forEach((file) => { const ext = path.extname(file) || "无扩展名"; groups[ext] = (groups[ext] || 0) + 1; }); return groups; } function getFilesToProcess(dir, excludedExtensions, excludePatterns) { const files = []; function walkDir(currentDir) { const items = fs.readdirSync(currentDir); for (const item of items) { const fullPath = path.join(currentDir, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { // 跳过常见的忽略目录 if (shouldSkipDirectory(item)) { continue; } // 检查是否匹配排除模式 if (excludePatterns.some((pattern) => item.includes(pattern))) { continue; } walkDir(fullPath); } else if (stat.isFile()) { const ext = path.extname(fullPath); // 检查文件扩展名 if (excludedExtensions.includes(ext)) { continue; } // 只处理支持的文件类型 if ([".js", ".jsx", ".ts", ".tsx", ".vue"].includes(ext)) { // 检查是否匹配排除模式 if (!excludePatterns.some((pattern) => fullPath.includes(pattern))) { files.push(fullPath); } } } } } walkDir(dir); return files; } function shouldSkipDirectory(dirName) { const skipDirs = [ "node_modules", ".git", ".svn", ".hg", ".DS_Store", "dist", "build", "coverage", ".nyc_output", ".next", ".nuxt", "out", "temp", "tmp", "vendor", ".idea", ".vscode", "logs", "public", "static", ]; return skipDirs.includes(dirName) || dirName.startsWith("."); } module.exports = { scanBatchCommand, };