UNPKG

template-syncer

Version:

智能模板同步工具 - 让你的项目与模板仓库保持同步,支持智能合并、差异对比和交互式更新

1,048 lines 43.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TemplateSyncer = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const child_process_1 = require("child_process"); const inquirer_1 = __importDefault(require("inquirer")); const chalk = require('chalk'); const glob_1 = require("glob"); /** * 智能模板同步工具 * 用于将项目与模板仓库保持同步 */ class TemplateSyncer { constructor(options = {}) { this.tempDir = options.tempDir || '.temp-template'; this.templateRepo = options.templateRepo; this.branch = options.branch; this.configFile = '.template-sync.json'; this.changes = []; this.verbose = options.verbose || false; // 忽略的文件和目录模式 this.ignorePatterns = [ 'node_modules/**', '.git/**', 'dist/**', 'build/**', 'coverage/**', '.nyc_output/**', 'logs/**', '*.log', '.DS_Store', 'Thumbs.db', '.vscode/**', '.idea/**', '*.tmp', '*.temp', '.cache/**', '.nuxt/**', '.next/**', '.output/**', this.tempDir + '/**' ]; // 扩展的文件类型支持 this.fileTypeConfig = { // JavaScript/TypeScript 相关 '.js': { type: 'js', category: '脚本文件', icon: '📜' }, '.mjs': { type: 'js', category: '脚本文件', icon: '📜' }, '.cjs': { type: 'js', category: '脚本文件', icon: '📜' }, '.ts': { type: 'js', category: '脚本文件', icon: '📜' }, '.jsx': { type: 'react', category: 'React 组件', icon: '⚛️' }, '.tsx': { type: 'react', category: 'React 组件', icon: '⚛️' }, // Vue 相关 '.vue': { type: 'vue', category: 'Vue 组件', icon: '💚' }, // Angular 相关 '.component.ts': { type: 'angular', category: 'Angular 组件', icon: '🅰️' }, '.service.ts': { type: 'angular', category: 'Angular 服务', icon: '🅰️' }, '.module.ts': { type: 'angular', category: 'Angular 模块', icon: '🅰️' }, '.directive.ts': { type: 'angular', category: 'Angular 指令', icon: '🅰️' }, // Svelte 相关 '.svelte': { type: 'svelte', category: 'Svelte 组件', icon: '🔥' }, // 样式文件 '.css': { type: 'css', category: '样式文件', icon: '🎨' }, '.scss': { type: 'css', category: '样式文件', icon: '🎨' }, '.sass': { type: 'css', category: '样式文件', icon: '🎨' }, '.less': { type: 'css', category: '样式文件', icon: '🎨' }, '.styl': { type: 'css', category: '样式文件', icon: '🎨' }, '.stylus': { type: 'css', category: '样式文件', icon: '🎨' }, // 配置文件 '.json': { type: 'json', category: '配置文件', icon: '⚙️' }, '.yml': { type: 'yaml', category: '配置文件', icon: '⚙️' }, '.yaml': { type: 'yaml', category: '配置文件', icon: '⚙️' }, '.toml': { type: 'config', category: '配置文件', icon: '⚙️' }, '.ini': { type: 'config', category: '配置文件', icon: '⚙️' }, '.xml': { type: 'config', category: '配置文件', icon: '⚙️' }, // 文档文件 '.md': { type: 'text', category: '文档文件', icon: '📝' }, '.txt': { type: 'text', category: '文档文件', icon: '📝' }, '.rst': { type: 'text', category: '文档文件', icon: '📝' }, // 图片文件 '.png': { type: 'image', category: '图片文件', icon: '🖼️' }, '.jpg': { type: 'image', category: '图片文件', icon: '🖼️' }, '.jpeg': { type: 'image', category: '图片文件', icon: '🖼️' }, '.gif': { type: 'image', category: '图片文件', icon: '🖼️' }, '.svg': { type: 'image', category: '图片文件', icon: '🖼️' }, '.webp': { type: 'image', category: '图片文件', icon: '🖼️' }, '.ico': { type: 'image', category: '图片文件', icon: '🖼️' }, // 其他文件 '.sh': { type: 'text', category: '脚本文件', icon: '🐚' }, '.bat': { type: 'text', category: '脚本文件', icon: '🪟' }, '.ps1': { type: 'text', category: '脚本文件', icon: '💙' }, '.env': { type: 'config', category: '环境配置', icon: '🌍' }, '.example': { type: 'config', category: '示例文件', icon: '📄' }, '.sample': { type: 'config', category: '示例文件', icon: '📄' } }; // 特殊文件配置(无扩展名或特殊命名) this.specialFiles = { // Git 相关 '.gitignore': { type: 'config', category: '版本控制', icon: '📋' }, '.gitattributes': { type: 'config', category: '版本控制', icon: '📋' }, '.gitmessage': { type: 'text', category: '版本控制', icon: '📋' }, // Node.js 相关 '.npmrc': { type: 'config', category: '包管理', icon: '📦' }, '.nvmrc': { type: 'config', category: '环境配置', icon: '🔧' }, 'package.json': { type: 'json', category: '项目配置', icon: '📦' }, 'package-lock.json': { type: 'json', category: '包管理', icon: '🔒' }, 'yarn.lock': { type: 'text', category: '包管理', icon: '🧶' }, 'pnpm-lock.yaml': { type: 'yaml', category: '包管理', icon: '📦' }, // 编辑器配置 '.editorconfig': { type: 'config', category: '编辑器配置', icon: '✏️' }, '.vscode/settings.json': { type: 'json', category: 'VS Code', icon: '💙' }, '.vscode/extensions.json': { type: 'json', category: 'VS Code', icon: '💙' }, '.vscode/launch.json': { type: 'json', category: 'VS Code', icon: '💙' }, // 代码质量工具 '.eslintrc': { type: 'json', category: '代码质量', icon: '🔍' }, '.eslintrc.js': { type: 'js', category: '代码质量', icon: '🔍' }, '.eslintrc.json': { type: 'json', category: '代码质量', icon: '🔍' }, '.eslintrc.yml': { type: 'yaml', category: '代码质量', icon: '🔍' }, '.eslintrc.yaml': { type: 'yaml', category: '代码质量', icon: '🔍' }, '.prettierrc': { type: 'json', category: '代码格式化', icon: '💄' }, '.prettierrc.js': { type: 'js', category: '代码格式化', icon: '💄' }, '.prettierrc.json': { type: 'json', category: '代码格式化', icon: '💄' }, '.prettierrc.yml': { type: 'yaml', category: '代码格式化', icon: '💄' }, '.stylelintrc': { type: 'json', category: '样式检查', icon: '🎨' }, '.stylelintrc.js': { type: 'js', category: '样式检查', icon: '🎨' }, '.stylelintrc.json': { type: 'json', category: '样式检查', icon: '🎨' }, // 构建工具配置 'vite.config.js': { type: 'js', category: '构建配置', icon: '⚡' }, 'vite.config.ts': { type: 'js', category: '构建配置', icon: '⚡' }, 'webpack.config.js': { type: 'js', category: '构建配置', icon: '📦' }, 'rollup.config.js': { type: 'js', category: '构建配置', icon: '🎯' }, 'nuxt.config.js': { type: 'js', category: '构建配置', icon: '💚' }, 'nuxt.config.ts': { type: 'js', category: '构建配置', icon: '💚' }, 'next.config.js': { type: 'js', category: '构建配置', icon: '⚫' }, 'tailwind.config.js': { type: 'js', category: '样式配置', icon: '🌊' }, 'unocss.config.js': { type: 'js', category: '样式配置', icon: '🎨' }, 'unocss.config.ts': { type: 'js', category: '样式配置', icon: '🎨' }, 'postcss.config.js': { type: 'js', category: '样式配置', icon: '📮' }, // TypeScript 配置 'tsconfig.json': { type: 'json', category: 'TypeScript', icon: '🔷' }, 'jsconfig.json': { type: 'json', category: 'JavaScript', icon: '🟨' }, // 测试配置 'jest.config.js': { type: 'js', category: '测试配置', icon: '🃏' }, 'vitest.config.js': { type: 'js', category: '测试配置', icon: '🧪' }, 'vitest.config.ts': { type: 'js', category: '测试配置', icon: '🧪' }, 'cypress.config.js': { type: 'js', category: '测试配置', icon: '🌀' }, 'playwright.config.js': { type: 'js', category: '测试配置', icon: '🎭' }, 'playwright.config.ts': { type: 'js', category: '测试配置', icon: '🎭' }, // 容器化 'Dockerfile': { type: 'text', category: '容器化', icon: '🐳' }, '.dockerignore': { type: 'text', category: '容器化', icon: '🐳' }, 'docker-compose.yml': { type: 'yaml', category: '容器化', icon: '🐳' }, 'docker-compose.yaml': { type: 'yaml', category: '容器化', icon: '🐳' }, // 其他重要文件 'LICENSE': { type: 'text', category: '许可证', icon: '📄' }, 'README.md': { type: 'text', category: '文档', icon: '📖' }, 'CHANGELOG.md': { type: 'text', category: '文档', icon: '📋' }, 'CONTRIBUTING.md': { type: 'text', category: '文档', icon: '🤝' }, 'Makefile': { type: 'text', category: '构建脚本', icon: '🔨' }, 'renovate.json': { type: 'json', category: '自动化', icon: '🔄' }, 'netlify.toml': { type: 'config', category: '部署配置', icon: '🌐' }, 'vercel.json': { type: 'json', category: '部署配置', icon: '▲' } }; } /** * 创建 Git 备份 */ createBackup() { console.log('📦 创建备份...'); try { (0, child_process_1.execSync)('git add . && git stash push -m "Template sync backup"', { stdio: this.verbose ? 'inherit' : 'ignore' }); console.log('✅ 备份已创建'); } catch (error) { console.log('⚠️ Git 备份失败,请确保有 Git 变更需要备份'); } } /** * 克隆模板仓库 */ async cloneTemplate() { console.log('📦 克隆最新模板...'); if (fs.existsSync(this.tempDir)) { (0, child_process_1.execSync)(`rmdir /s /q ${this.tempDir}`, { stdio: 'ignore' }); } try { // 先克隆整个仓库(不使用 --depth 1 以获取所有分支信息) (0, child_process_1.execSync)(`git clone "${this.templateRepo}" ${this.tempDir}`, { stdio: this.verbose ? 'inherit' : 'ignore' }); // 如果没有指定分支,让用户选择 if (!this.branch) { this.branch = await this.selectBranch(); } // 切换到指定分支 if (this.branch && this.branch !== 'main' && this.branch !== 'master') { console.log(`📋 切换到分支: ${this.branch}`); (0, child_process_1.execSync)(`git checkout ${this.branch}`, { cwd: this.tempDir, stdio: this.verbose ? 'inherit' : 'ignore' }); } // 删除 .git 目录 (0, child_process_1.execSync)(`rmdir /s /q ${this.tempDir}\\.git`, { stdio: 'ignore' }); console.log('✅ 模板克隆完成'); } catch (error) { console.log('❌ 模板克隆失败'); console.log('可能的原因:'); console.log(' • 网络连接问题'); console.log(' • 仓库不存在或无访问权限'); console.log(' • Git 未正确安装'); console.log(' • 指定的分支不存在'); if (this.verbose && error instanceof Error) { console.log(`错误详情: ${error.message}`); } throw new Error('模板克隆失败'); } } /** * 获取或设置模板仓库 */ async getTemplateRepo() { if (this.templateRepo) { return this.templateRepo; } // 尝试从配置文件读取 const config = this.loadConfig(); if (config.templateRepo) { this.templateRepo = config.templateRepo; // 同时读取分支配置 if (config.branch && !this.branch) { this.branch = config.branch; } return this.templateRepo; } // 交互式获取 const { templateRepo } = await inquirer_1.default.prompt([ { type: 'input', name: 'templateRepo', message: '请输入模板仓库 URL:', validate: (input) => { if (!input.trim()) { return '模板仓库 URL 不能为空'; } return true; } } ]); this.templateRepo = templateRepo; // 保存到配置文件 this.saveConfig({ ...config, templateRepo }); return templateRepo; } /** * 测试仓库连接 */ async testRepository(repo) { console.log(`🔍 测试仓库连接: ${repo}`); try { // 尝试获取仓库信息 (0, child_process_1.execSync)(`git ls-remote --heads ${repo}`, { stdio: 'ignore' }); console.log('✅ 仓库连接成功'); return true; } catch (error) { console.log('❌ 仓库连接失败'); return false; } } /** * 扫描模板文件 */ async scanTemplateFiles() { const files = []; if (!fs.existsSync(this.tempDir)) { throw new Error('模板目录不存在,请先克隆模板'); } const globPattern = '**/*'; const foundFiles = await (0, glob_1.glob)(globPattern, { cwd: this.tempDir, ignore: this.ignorePatterns, dot: true }); for (const file of foundFiles) { const fullPath = path.join(this.tempDir, file); try { const stat = fs.statSync(fullPath); if (stat.isFile()) { const fileConfig = this.analyzeFile(file, fullPath); files.push(fileConfig); } } catch (error) { // 忽略无法访问的文件 continue; } } return files.sort((a, b) => a.path.localeCompare(b.path)); } /** * 扫描当前项目文件 */ async scanCurrentFiles() { const files = []; const globPattern = '**/*'; const foundFiles = await (0, glob_1.glob)(globPattern, { cwd: process.cwd(), ignore: this.ignorePatterns, dot: true }); for (const file of foundFiles) { const fullPath = path.join(process.cwd(), file); try { const stat = fs.statSync(fullPath); if (stat.isFile()) { const fileConfig = this.analyzeFile(file, fullPath); fileConfig.exists = true; files.push(fileConfig); } } catch (error) { // 忽略无法访问的文件 continue; } } return files.sort((a, b) => a.path.localeCompare(b.path)); } /** * 分析文件类型和特征 */ analyzeFile(relativePath, fullPath) { const fileName = path.basename(relativePath); const ext = path.extname(fileName); // 检查特殊文件 if (this.specialFiles[fileName]) { const config = this.specialFiles[fileName]; return { path: relativePath, fullPath, type: config.type, category: config.category, selected: true, icon: config.icon }; } // 检查扩展名 if (ext && this.fileTypeConfig[ext]) { const config = this.fileTypeConfig[ext]; return { path: relativePath, fullPath, type: config.type, category: config.category, selected: true, icon: config.icon }; } // 默认配置 return { path: relativePath, fullPath, type: 'text', category: '其他文件', selected: true, icon: '📄' }; } /** * 对比文件并生成变更列表 */ async compareFiles(templateFiles, currentFiles) { const changes = []; const currentFileMap = new Map(currentFiles.map(f => [f.path, f])); for (const templateFile of templateFiles) { const currentFile = currentFileMap.get(templateFile.path); if (!currentFile) { // 新文件 changes.push({ ...templateFile, status: 'new', isNew: true, exists: false, templatePath: templateFile.fullPath, currentPath: path.join(process.cwd(), templateFile.path) }); } else { // 检查文件是否有变更 const templateContent = fs.readFileSync(templateFile.fullPath, 'utf8'); const currentContent = fs.readFileSync(currentFile.fullPath, 'utf8'); if (templateContent !== currentContent) { changes.push({ ...templateFile, status: 'modified', isNew: false, exists: true, templatePath: templateFile.fullPath, currentPath: currentFile.fullPath }); } } } return changes; } /** * 交互式选择要应用的变更 */ async selectChangesToApply(changes) { if (changes.length === 0) { console.log('✅ 没有发现任何变更'); return []; } console.log(`\n发现 ${changes.length} 个文件需要处理:\n`); // 按类别分组显示 const categories = new Map(); changes.forEach(file => { const category = file.category; if (!categories.has(category)) { categories.set(category, []); } categories.get(category).push(file); }); // 显示分类统计 for (const [category, files] of categories) { const newFiles = files.filter(f => f.status === 'new').length; const modifiedFiles = files.filter(f => f.status === 'modified').length; console.log(`${files[0].icon} ${category}: ${files.length} 个文件 (新增: ${newFiles}, 修改: ${modifiedFiles})`); } // 询问用户如何处理 const { action } = await inquirer_1.default.prompt([ { type: 'list', name: 'action', message: '选择处理方式:', choices: [ { name: '📋 按分类选择文件', value: 'category' }, { name: '📝 逐一选择文件', value: 'individual' }, { name: '✅ 应用所有变更', value: 'all' }, { name: '❌ 取消操作', value: 'cancel' } ] } ]); if (action === 'cancel') { return []; } if (action === 'all') { return changes; } if (action === 'category') { return await this.selectByCategory(categories); } return await this.selectIndividually(changes); } /** * 按分类选择文件 */ async selectByCategory(categories) { const selectedFiles = []; for (const [category, files] of categories) { const { shouldProcess } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'shouldProcess', message: `处理 ${files[0].icon} ${category} (${files.length} 个文件)?`, default: true } ]); if (shouldProcess) { selectedFiles.push(...files); } } return selectedFiles; } /** * 逐一选择文件 */ async selectIndividually(changes) { const choices = changes.map(file => ({ name: `${file.icon} ${file.path} (${file.status === 'new' ? '新增' : '修改'})`, value: file, checked: true })); const { selectedFiles } = await inquirer_1.default.prompt([ { type: 'checkbox', name: 'selectedFiles', message: '选择要处理的文件:', choices, pageSize: 15 } ]); return selectedFiles; } /** * 批量更新文件 */ async batchUpdateFiles(files) { const results = { success: [], skipped: [], failed: [] }; console.log(`\n开始处理 ${files.length} 个文件...\n`); for (const file of files) { try { console.log(`处理: ${file.icon} ${file.path}`); const result = await this.updateSingleFile(file); if (result.applied) { results.success.push(file.path); console.log(`✅ ${result.message}`); } else { results.skipped.push(file.path); console.log(`⏭️ ${result.message}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); results.failed.push({ path: file.path, error: errorMessage }); console.log(`❌ 失败: ${errorMessage}`); } } return results; } /** * 更新单个文件 */ async updateSingleFile(file) { if (!file.templatePath) { return { applied: false, message: '模板文件路径不存在' }; } // 确保目标目录存在 const targetDir = path.dirname(file.currentPath); if (!fs.existsSync(targetDir)) { fs.mkdirSync(targetDir, { recursive: true }); } // 特殊处理 package.json if (file.path === 'package.json') { return await this.mergePackageJson(file); } // 复制文件 fs.copyFileSync(file.templatePath, file.currentPath); return { applied: true, message: file.status === 'new' ? '文件已创建' : '文件已更新' }; } /** * 智能合并 package.json */ async mergePackageJson(file) { const templateContent = JSON.parse(fs.readFileSync(file.templatePath, 'utf8')); if (!fs.existsSync(file.currentPath)) { // 如果当前文件不存在,直接复制 fs.writeFileSync(file.currentPath, JSON.stringify(templateContent, null, 2)); return { applied: true, message: 'package.json 已创建' }; } const currentContent = JSON.parse(fs.readFileSync(file.currentPath, 'utf8')); // 智能合并逻辑 const merged = { ...currentContent, ...templateContent, // 保留当前项目的基本信息 name: currentContent.name || templateContent.name, version: currentContent.version || templateContent.version, description: currentContent.description || templateContent.description, // 合并依赖 dependencies: { ...currentContent.dependencies, ...templateContent.dependencies }, devDependencies: { ...currentContent.devDependencies, ...templateContent.devDependencies }, // 合并脚本 scripts: { ...currentContent.scripts, ...templateContent.scripts } }; fs.writeFileSync(file.currentPath, JSON.stringify(merged, null, 2)); return { applied: true, message: 'package.json 已智能合并' }; } /** * 显示操作摘要 */ showSummary(results) { console.log('\n' + '='.repeat(50)); console.log('📊 操作摘要'); console.log('='.repeat(50)); console.log(`✅ 成功: ${results.success.length} 个文件`); if (results.success.length > 0) { results.success.forEach(file => console.log(` • ${file}`)); } console.log(`⏭️ 跳过: ${results.skipped.length} 个文件`); if (results.skipped.length > 0) { results.skipped.forEach(file => console.log(` • ${file}`)); } console.log(`❌ 失败: ${results.failed.length} 个文件`); if (results.failed.length > 0) { results.failed.forEach(item => console.log(` • ${item.path}: ${item.error}`)); } console.log('\n' + '='.repeat(50)); } /** * 清理临时文件 */ cleanup() { if (fs.existsSync(this.tempDir)) { try { (0, child_process_1.execSync)(`rmdir /s /q ${this.tempDir}`, { stdio: 'ignore' }); console.log('🧹 清理临时文件完成'); } catch (error) { console.log('⚠️ 清理临时文件失败'); } } } /** * 加载配置文件 */ loadConfig() { if (fs.existsSync(this.configFile)) { try { return JSON.parse(fs.readFileSync(this.configFile, 'utf8')); } catch (error) { console.log('⚠️ 配置文件格式错误,将使用默认配置'); } } return {}; } /** * 保存配置文件 */ saveConfig(config) { try { fs.writeFileSync(this.configFile, JSON.stringify(config, null, 2)); } catch (error) { console.log('⚠️ 保存配置文件失败'); } } /** * 主同步流程 */ async sync(options = {}) { try { console.log(chalk.blue('🚀 开始模板同步...')); // 获取模板仓库 const repo = await this.getTemplateRepo(); // 测试仓库连接 const isReachable = await this.testRepository(repo); if (!isReachable) { throw new Error('无法连接到模板仓库'); } // 创建备份 this.createBackup(); // 克隆模板 await this.cloneTemplate(); // 扫描文件 console.log('📁 扫描模板文件...'); const templateFiles = await this.scanTemplateFiles(); console.log('📁 扫描当前文件...'); const currentFiles = await this.scanCurrentFiles(); // 对比文件 console.log('🔍 对比文件差异...'); const changes = await this.compareFiles(templateFiles, currentFiles); // 选择要应用的变更 const selectedFiles = await this.selectChangesToApply(changes); if (selectedFiles.length === 0) { console.log('ℹ️ 没有选择任何文件进行处理'); return; } // 最终确认 const { confirm } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'confirm', message: `确定要处理 ${selectedFiles.length} 个文件吗?`, default: true } ]); if (!confirm) { console.log('❌ 操作已取消'); return; } // 批量更新文件 const results = await this.batchUpdateFiles(selectedFiles); // 显示摘要 this.showSummary(results); console.log(chalk.green('🎉 模板同步完成!')); } catch (error) { console.error(chalk.red('❌ 同步失败:'), error instanceof Error ? error.message : String(error)); throw error; } finally { // 清理临时文件 this.cleanup(); } } /** * 初始化配置向导 */ async initConfig() { console.log(chalk.blue('🔧 初始化配置向导')); console.log(''); const answers = await inquirer_1.default.prompt([ { type: 'input', name: 'templateRepo', message: '请输入默认模板仓库 URL:', default: 'https://github.com/IceyWu/cloud-template.git', validate: (input) => { if (!input.trim()) { return '模板仓库 URL 不能为空'; } return true; } }, { type: 'input', name: 'branch', message: '请输入默认分支名称 (留空则每次都会询问):', default: '', }, { type: 'checkbox', name: 'ignorePatterns', message: '选择要忽略的文件和目录:', choices: [ { name: 'node_modules/', value: 'node_modules/**', checked: true }, { name: '.git/', value: '.git/**', checked: true }, { name: 'dist/', value: 'dist/**', checked: true }, { name: 'build/', value: 'build/**', checked: true }, { name: 'coverage/', value: 'coverage/**', checked: true }, { name: '*.log', value: '*.log', checked: true }, { name: '.DS_Store', value: '.DS_Store', checked: true }, { name: '.env.local', value: '.env.local', checked: false }, { name: 'README.md', value: 'README.md', checked: false } ] }, { type: 'confirm', name: 'verbose', message: '是否默认启用详细输出?', default: false } ]); const config = { templateRepo: answers.templateRepo, branch: answers.branch || undefined, ignorePatterns: answers.ignorePatterns, verbose: answers.verbose, lastSync: new Date().toISOString() }; this.saveConfig(config); console.log(chalk.green('✅ 配置已保存到 .template-sync.json')); } /** * 批量处理模式 */ async batchProcess() { console.log(chalk.blue('🔄 高级批量操作模式')); // 获取模板仓库 const repo = await this.getTemplateRepo(); // 测试连接 const isReachable = await this.testRepository(repo); if (!isReachable) { throw new Error('无法连接到模板仓库'); } // 克隆模板 await this.cloneTemplate(); // 扫描所有文件 const templateFiles = await this.scanTemplateFiles(); const currentFiles = await this.scanCurrentFiles(); // 生成推荐 const recommendations = await this.generateRecommendations(templateFiles, currentFiles); if (recommendations.length === 0) { console.log('✅ 没有发现需要处理的文件'); return; } // 显示推荐 console.log('\n📊 批量处理推荐:'); for (const rec of recommendations) { const icon = rec.priority === 'high' ? '🔴' : rec.priority === 'medium' ? '🟡' : '🟢'; console.log(`${icon} ${rec.title}`); console.log(` ${rec.description}`); console.log(` 影响: ${rec.impact}`); console.log(` 文件数: ${rec.files.length}`); console.log(''); } // 选择要处理的推荐 const { selectedRecs } = await inquirer_1.default.prompt([ { type: 'checkbox', name: 'selectedRecs', message: '选择要执行的操作:', choices: recommendations.map(rec => ({ name: `${rec.priority === 'high' ? '🔴' : rec.priority === 'medium' ? '🟡' : '🟢'} ${rec.title} (${rec.files.length} 个文件)`, value: rec, checked: rec.priority === 'high' })) } ]); // 执行批量处理 const allFiles = []; selectedRecs.forEach((rec) => { allFiles.push(...rec.files); }); if (allFiles.length > 0) { const results = await this.batchUpdateFiles(allFiles); this.showSummary(results); } } /** * 生成智能推荐 */ async generateRecommendations(templateFiles, currentFiles) { const recommendations = []; const changes = await this.compareFiles(templateFiles, currentFiles); if (changes.length === 0) { return recommendations; } // 按优先级分组 const highPriority = []; const mediumPriority = []; const lowPriority = []; changes.forEach(file => { // 高优先级:核心配置文件 if (['package.json', 'tsconfig.json', 'vite.config.ts', 'vite.config.js'].includes(file.path)) { highPriority.push(file); } // 中优先级:开发工具配置 else if (file.category === '代码质量' || file.category === '构建配置' || file.category === 'TypeScript') { mediumPriority.push(file); } // 低优先级:其他文件 else { lowPriority.push(file); } }); if (highPriority.length > 0) { recommendations.push({ priority: 'high', title: '核心配置更新', description: '更新项目的核心配置文件,包括 package.json、构建配置等', impact: '可能影响项目构建和依赖管理', files: highPriority }); } if (mediumPriority.length > 0) { recommendations.push({ priority: 'medium', title: '开发工具配置', description: '更新代码质量工具、构建工具等配置', impact: '改善开发体验和代码质量', files: mediumPriority }); } if (lowPriority.length > 0) { recommendations.push({ priority: 'low', title: '其他文件更新', description: '更新文档、样式等其他文件', impact: '对项目功能影响较小', files: lowPriority }); } return recommendations; } /** * 预览所有差异 */ async previewAllDifferences(files) { console.log(chalk.blue('🔍 预览所有差异')); if (files.length === 0) { console.log('✅ 没有差异需要预览'); return; } for (const file of files) { console.log(`\n${'='.repeat(60)}`); console.log(`${file.icon} ${file.path} (${file.status})`); console.log('='.repeat(60)); if (file.templatePath && file.currentPath && fs.existsSync(file.currentPath)) { // 显示文件差异(简化版) const templateContent = fs.readFileSync(file.templatePath, 'utf8'); const currentContent = fs.readFileSync(file.currentPath, 'utf8'); if (templateContent !== currentContent) { console.log('📝 文件内容有差异'); console.log(`模板文件长度: ${templateContent.length} 字符`); console.log(`当前文件长度: ${currentContent.length} 字符`); } } else if (file.status === 'new') { console.log('📄 这是一个新文件'); } console.log(`📁 分类: ${file.category}`); console.log(`🏷️ 类型: ${file.type}`); } console.log(`\n总计: ${files.length} 个文件有差异`); } /** * 智能同步模式 */ async intelligentSync() { console.log(chalk.blue('🤖 智能同步模式')); // 扫描文件 const templateFiles = await this.scanTemplateFiles(); const currentFiles = await this.scanCurrentFiles(); // 生成推荐 const recommendations = await this.generateRecommendations(templateFiles, currentFiles); if (recommendations.length === 0) { console.log('✅ 项目已经是最新的,无需同步'); return; } // 自动选择高优先级推荐 const highPriorityRecs = recommendations.filter(rec => rec.priority === 'high'); if (highPriorityRecs.length > 0) { console.log('🔴 发现高优先级更新,建议立即处理:'); for (const rec of highPriorityRecs) { console.log(`• ${rec.title}: ${rec.files.length} 个文件`); } const { autoApply } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'autoApply', message: '是否自动应用高优先级更新?', default: true } ]); if (autoApply) { const allFiles = []; highPriorityRecs.forEach(rec => { allFiles.push(...rec.files); }); const results = await this.batchUpdateFiles(allFiles); this.showSummary(results); } } // 显示其他推荐 const otherRecs = recommendations.filter(rec => rec.priority !== 'high'); if (otherRecs.length > 0) { console.log('\n其他可选更新:'); otherRecs.forEach(rec => { const icon = rec.priority === 'medium' ? '🟡' : '🟢'; console.log(`${icon} ${rec.title}: ${rec.files.length} 个文件`); }); console.log('\n💡 提示: 使用 --batch 模式可以手动选择这些更新'); } } /** * 选择分支 */ async selectBranch() { try { console.log('🌿 获取所有分支...'); // 获取所有远程分支 const branchOutput = (0, child_process_1.execSync)('git branch -r', { cwd: this.tempDir, encoding: 'utf8', stdio: this.verbose ? 'inherit' : 'pipe' }); // 解析分支列表 const branches = branchOutput .split('\n') .map(line => line.trim()) .filter(line => line && !line.includes('HEAD')) .map(line => line.replace('origin/', '')) .filter(branch => branch); if (branches.length === 0) { console.log('⚠️ 未找到任何分支,使用默认分支'); return 'main'; } if (branches.length === 1) { console.log(`📋 只有一个分支: ${branches[0]}`); return branches[0]; } // 让用户选择分支 console.log(`\n发现 ${branches.length} 个分支:`); branches.forEach((branch, index) => { console.log(` ${index + 1}. ${branch}`); }); const { selectedBranch } = await inquirer_1.default.prompt([ { type: 'list', name: 'selectedBranch', message: '请选择要同步的分支:', choices: branches.map(branch => ({ name: branch === 'main' || branch === 'master' ? `${branch} (默认分支)` : branch, value: branch })), default: branches.find(b => b === 'main') || branches.find(b => b === 'master') || branches[0] } ]); return selectedBranch; } catch (error) { console.log('⚠️ 获取分支信息失败,使用默认分支'); if (this.verbose && error instanceof Error) { console.log(`错误详情: ${error.message}`); } return 'main'; } } } exports.TemplateSyncer = TemplateSyncer; //# sourceMappingURL=index.js.map