UNPKG

template-syncer

Version:

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

388 lines (387 loc) 14 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.TemplateSyncer = void 0; const path = __importStar(require("path")); const utils_1 = require("./utils"); const ui_1 = require("./ui"); const CONFIG_FILE = '.template-sync.json'; /** * 模板同步器 */ class TemplateSyncer { constructor(options = {}) { this.options = { tempDir: '.temp-template', verbose: false, ...options }; this.rules = { deleteOrphans: false, deletePatterns: [], protectPatterns: [], autoBackup: true, defaultMergeStrategy: 'overwrite', ...options.rules }; this.git = new utils_1.Git(this.options.verbose); this.scanner = new utils_1.Scanner([...(options.ignore || []), `${this.options.tempDir}/**`], options.categories); this.merger = new utils_1.Merger(options.mergers); } /** * 加载配置 */ loadConfig() { const config = utils_1.platform.readJson(CONFIG_FILE) || {}; // 合并 rules if (config.rules) { this.rules = { ...this.rules, ...config.rules }; } return config; } /** * 保存配置 */ saveConfig(config) { utils_1.platform.writeJson(CONFIG_FILE, config); } /** * 获取模板仓库 */ async getRepo() { if (this.options.repo) return this.options.repo; const config = this.loadConfig(); if (config.repo) { this.options.repo = config.repo; if (config.branch && !this.options.branch) { this.options.branch = config.branch; } return config.repo; } const repo = await ui_1.prompts.inputRepo(); this.options.repo = repo; this.saveConfig({ ...config, repo }); return repo; } /** * 克隆模板 */ async cloneTemplate() { const repo = await this.getRepo(); ui_1.logger.step('测试仓库连接...'); if (!this.git.testConnection(repo)) { throw new Error('无法连接到模板仓库'); } ui_1.logger.success('仓库连接成功'); ui_1.logger.step('克隆模板...'); this.git.clone(repo, this.options.tempDir); // 选择分支 if (!this.options.branch) { const branches = this.git.getBranches(this.options.tempDir); if (branches.length > 1) { console.log(`\n发现 ${branches.length} 个分支`); this.options.branch = await ui_1.prompts.selectBranch(branches); } else { this.options.branch = branches[0] || 'main'; } } // 切换分支 if (this.options.branch && !['main', 'master'].includes(this.options.branch)) { ui_1.logger.step(`切换到分支: ${this.options.branch}`); this.git.checkout(this.options.branch, this.options.tempDir); } this.git.removeGitDir(this.options.tempDir); ui_1.logger.success('模板克隆完成'); } /** * 扫描变更 */ async scanChanges() { ui_1.logger.step('扫描文件差异...'); const detectOrphans = this.rules.deleteOrphans || (this.rules.deletePatterns && this.rules.deletePatterns.length > 0); let changes = await this.scanner.compare(this.options.tempDir, process.cwd(), detectOrphans); // 过滤要删除的文件 if (detectOrphans) { const orphans = changes.filter(c => c.status === 'deleted'); const others = changes.filter(c => c.status !== 'deleted'); // 如果 deletePatterns 为空数组,使用默认值 ['**'] 匹配所有文件 const patterns = this.rules.deletePatterns && this.rules.deletePatterns.length > 0 ? this.rules.deletePatterns : ['**']; const filteredOrphans = this.scanner.filterOrphans(orphans, patterns, this.rules.protectPatterns || []); changes = [...others, ...filteredOrphans]; } return changes; } /** * 应用变更 */ async applyChanges(changes) { const result = { success: [], skipped: [], failed: [] }; console.log(`\n开始处理 ${changes.length} 个文件...\n`); // 并发处理所有文件 await Promise.allSettled(changes.map(async (file) => { try { if (file.status === 'deleted') { utils_1.platform.removeFile(file.currentPath); result.success.push(file.path); ui_1.logger.file('🗑️', file.path, '已删除'); } else { const targetPath = path.join(process.cwd(), file.path); const mergeResult = await this.merger.merge(file.templatePath, file.currentPath, targetPath); if (mergeResult.success) { result.success.push(file.path); } else { result.skipped.push(file.path); } ui_1.logger.file(file.icon, file.path, mergeResult.message); } } catch (error) { const message = error instanceof Error ? error.message : String(error); result.failed.push({ path: file.path, error: message }); ui_1.logger.file(file.icon, file.path, `失败: ${message}`); } })); return result; } /** * 生成推荐 */ generateRecommendations(changes) { if (changes.length === 0) return []; const high = []; const medium = []; const low = []; // 核心配置文件 const coreFiles = new Set([ 'package.json', 'tsconfig.json', 'jsconfig.json', 'vite.config.ts', 'vite.config.js', 'vite.config.mjs' ]); // 开发工具配置 const devCategories = new Set([ '代码质量', '代码格式化', '构建配置', 'TypeScript', '测试配置' ]); for (const file of changes) { const fileName = path.basename(file.path); if (coreFiles.has(fileName)) { high.push(file); } else if (devCategories.has(file.category)) { medium.push(file); } else { low.push(file); } } const recommendations = []; if (high.length > 0) { recommendations.push({ priority: 'high', title: '核心配置更新', description: '更新项目核心配置文件', files: high }); } if (medium.length > 0) { recommendations.push({ priority: 'medium', title: '开发工具配置', description: '更新代码质量和构建工具配置', files: medium }); } if (low.length > 0) { recommendations.push({ priority: 'low', title: '其他文件', description: '更新文档、样式等其他文件', files: low }); } return recommendations; } /** * 清理临时文件 */ cleanup() { utils_1.platform.removeDir(this.options.tempDir); ui_1.logger.step('清理临时文件完成'); } /** * 主同步流程 */ async sync() { try { ui_1.logger.info('🚀 开始模板同步...'); // 创建备份 ui_1.logger.step('创建 Git 备份...'); if (this.git.backup()) { ui_1.logger.success('备份已创建'); } else { ui_1.logger.warn('Git 备份失败,请确保有变更需要备份'); } // 克隆模板 await this.cloneTemplate(); // 扫描变更 const changes = await this.scanChanges(); if (changes.length === 0) { ui_1.logger.success('没有发现任何变更,项目已是最新'); return; } // 显示变更统计 ui_1.prompts.showChangeSummary(changes); // 选择处理方式 const action = await ui_1.prompts.selectAction(); if (action === 'cancel') { ui_1.logger.warn('操作已取消'); return; } let selected; if (action === 'all') { selected = changes; } else if (action === 'category') { selected = await ui_1.prompts.selectByCategory(changes); } else { selected = await ui_1.prompts.selectIndividually(changes); } if (selected.length === 0) { ui_1.logger.warn('没有选择任何文件'); return; } // 确认 const confirmed = await ui_1.prompts.confirm(`确定要处理 ${selected.length} 个文件吗?`); if (!confirmed) { ui_1.logger.warn('操作已取消'); return; } // 应用变更 const result = await this.applyChanges(selected); ui_1.logger.summary(result); ui_1.logger.info('🎉 模板同步完成!'); } finally { this.cleanup(); } } async batch() { try { ui_1.logger.info('🔄 批量处理模式'); await this.cloneTemplate(); const changes = await this.scanChanges(); if (changes.length === 0) { ui_1.logger.success('没有发现需要处理的文件'); return; } const recommendations = this.generateRecommendations(changes); const selected = await ui_1.prompts.selectRecommendations(recommendations); const files = selected.flatMap(r => r.files); if (files.length > 0) { const result = await this.applyChanges(files); ui_1.logger.summary(result); } } finally { this.cleanup(); } } /** * 预览模式 */ async preview() { try { ui_1.logger.info('🔍 预览模式'); await this.cloneTemplate(); const changes = await this.scanChanges(); if (changes.length === 0) { ui_1.logger.success('没有发现任何差异'); return; } ui_1.prompts.showChangeSummary(changes); // 显示完整文件树 console.log('📁 文件结构:\n'); console.log((0, ui_1.formatFileTree)(changes)); } finally { this.cleanup(); } } async smart() { try { ui_1.logger.info('🤖 智能同步模式'); await this.cloneTemplate(); const changes = await this.scanChanges(); if (changes.length === 0) { ui_1.logger.success('项目已是最新,无需同步'); return; } const recommendations = this.generateRecommendations(changes); const selected = await ui_1.prompts.selectRecommendations(recommendations); const files = selected.flatMap(r => r.files); if (files.length > 0) { const confirmed = await ui_1.prompts.confirm(`确定要处理 ${files.length} 个文件吗?`); if (confirmed) { const result = await this.applyChanges(files); ui_1.logger.summary(result); } } } finally { this.cleanup(); } } async init() { ui_1.logger.info('🔧 初始化配置向导\n'); const answers = await ui_1.prompts.initConfig(); const config = { repo: answers.repo || undefined, branch: answers.branch || undefined, ignore: answers.ignore.length > 0 ? answers.ignore : undefined, lastSync: new Date().toISOString() }; this.saveConfig(config); ui_1.logger.success(`配置已保存到 ${CONFIG_FILE}`); } } exports.TemplateSyncer = TemplateSyncer;