template-syncer 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,492 +1,1048 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { execSync } = require('child_process');
4
- const readline = require('readline');
5
-
6
- /**
7
- * 简化的模板同步工具
8
- * 用于将项目与模板仓库保持同步
9
- */
10
- class TemplateSyncer { constructor(options = {}) {
11
- this.tempDir = options.tempDir || '.temp-template';
12
- this.templateRepo = options.templateRepo;
13
- this.configFile = '.template-sync.json';
14
- this.rl = readline.createInterface({
15
- input: process.stdin,
16
- output: process.stdout,
17
- });
18
- this.changes = [];
19
- this.verbose = options.verbose || false;
20
-
21
- // 需要处理的文件列表
22
- this.filesToProcess = options.filesToProcess || [
23
- { path: 'package.json', type: 'merge' },
24
- { path: 'tsconfig.json', type: 'diff' },
25
- { path: 'vite.config.ts', type: 'diff' },
26
- { path: 'unocss.config.ts', type: 'diff' },
27
- { path: '.gitignore', type: 'overwrite' },
28
- { path: '.eslintrc.yml', type: 'overwrite' },
29
- { path: 'netlify.toml', type: 'overwrite' },
30
- ];
31
- }
32
- /**
33
- * 询问用户输入
34
- * @param {string} question 问题
35
- * @param {string[]} validAnswers 有效答案列表
36
- * @returns {Promise<string>}
37
- */
38
- askUser(question, validAnswers = []) {
39
- return new Promise((resolve) => {
40
- if (!this.rl) {
41
- // 如果没有 readline 接口,返回默认值
42
- resolve(validAnswers[0] || 'y');
43
- return;
44
- }
45
-
46
- const ask = () => {
47
- this.rl.question(question, (answer) => {
48
- const normalizedAnswer = answer.toLowerCase().trim();
49
- if (validAnswers.length === 0 || validAnswers.includes(normalizedAnswer)) {
50
- resolve(normalizedAnswer);
51
- } else {
52
- console.log(`请输入有效选项: ${validAnswers.join(', ')}`);
53
- ask();
54
- }
55
- });
56
- };
57
- ask();
58
- });
59
- }
60
-
61
- /**
62
- * 创建 Git 备份
63
- */
64
- createBackup() {
65
- console.log('📦 创建备份...');
66
- try {
67
- execSync('git add . && git stash push -m "Template sync backup"', { stdio: this.verbose ? 'inherit' : 'ignore' });
68
- console.log('✅ 备份已创建');
69
- } catch (error) {
70
- console.log('⚠️ Git 备份失败,请确保有 Git 变更需要备份');
71
- }
72
- }
73
- /**
74
- * 克隆模板仓库
75
- */
76
- async cloneTemplate() {
77
- console.log('📦 克隆最新模板...');
78
-
79
- if (fs.existsSync(this.tempDir)) {
80
- execSync(`rmdir /s /q ${this.tempDir}`, { stdio: 'ignore' });
81
- }
82
-
83
- try {
84
- execSync(`git clone --depth 1 "${this.templateRepo}" ${this.tempDir}`, { stdio: this.verbose ? 'inherit' : 'ignore' });
85
- execSync(`rmdir /s /q ${this.tempDir}\\.git`, { stdio: 'ignore' });
86
- console.log(' 模板克隆完成');
87
- } catch (error) {
88
- console.log(' 模板克隆失败');
89
- console.log('可能的原因:');
90
- console.log(' 网络连接问题');
91
- console.log(' • 仓库不存在或无访问权限');
92
- console.log(' Git 未正确安装');
93
- if (this.verbose) {
94
- console.log(`错误详情: ${error.message}`);
95
- }
96
- throw new Error('模板克隆失败');
97
- }
98
- }
99
-
100
- /**
101
- * 显示文件差异
102
- * @param {string} templatePath 模板文件路径
103
- * @param {string} currentPath 当前文件路径
104
- * @returns {Promise<string>}
105
- */
106
- async showDiff(templatePath, currentPath) {
107
- console.log(`\n📄 对比文件: ${currentPath}`);
108
-
109
- if (!fs.existsSync(currentPath)) {
110
- console.log('🆕 这是一个新文件');
111
- console.log('模板内容:');
112
- console.log('-'.repeat(50));
113
- console.log(fs.readFileSync(templatePath, 'utf8'));
114
- console.log('-'.repeat(50));
115
- return await this.askUser('是否添加此文件?[y/n]: ', ['y', 'n']);
116
- }
117
-
118
- try {
119
- const result = execSync(`git diff --no-index --color=always "${currentPath}" "${templatePath}"`,
120
- { encoding: 'utf8', stdio: 'pipe' });
121
- console.log(result);
122
- } catch (error) {
123
- console.log(error.stdout || '文件内容相同');
124
- }
125
-
126
- return await this.askUser('选择操作 [u]更新 [s]跳过: ', ['u', 's']);
127
- }
128
-
129
- /**
130
- * 智能合并 package.json
131
- * @param {string} templatePath 模板文件路径
132
- * @param {string} currentPath 当前文件路径
133
- * @returns {Object}
134
- */
135
- mergePackageJson(templatePath, currentPath) {
136
- console.log('\n📦 智能合并 package.json...');
137
-
138
- const templatePkg = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
139
- const currentPkg = JSON.parse(fs.readFileSync(currentPath, 'utf8'));
140
-
141
- // 保留当前项目的基本信息和依赖
142
- const merged = {
143
- ...currentPkg,
144
- // 合并 scripts (保留当前的,添加新的)
145
- scripts: {
146
- ...currentPkg.scripts,
147
- ...templatePkg.scripts,
148
- },
149
- // 合并 devDependencies (使用模板的版本)
150
- devDependencies: {
151
- ...currentPkg.devDependencies,
152
- ...templatePkg.devDependencies,
153
- },
154
- };
155
-
156
- // 显示将要合并的内容
157
- console.log('🔄 将要合并的新 scripts:');
158
- for (const [key, value] of Object.entries(templatePkg.scripts || {})) {
159
- if (!currentPkg.scripts || currentPkg.scripts[key] !== value) {
160
- console.log(` + ${key}: ${value}`);
161
- }
162
- }
163
-
164
- console.log('🔄 将要合并的新 devDependencies:');
165
- for (const [key, value] of Object.entries(templatePkg.devDependencies || {})) {
166
- if (!currentPkg.devDependencies || currentPkg.devDependencies[key] !== value) {
167
- console.log(` + ${key}: ${value}`);
168
- }
169
- }
170
-
171
- return merged;
172
- }
173
-
174
- /**
175
- * 处理单个文件
176
- * @param {Object} fileConfig 文件配置
177
- */
178
- async processFile(fileConfig) {
179
- const templatePath = path.join(this.tempDir, fileConfig.path);
180
- const currentPath = fileConfig.path;
181
-
182
- if (!fs.existsSync(templatePath)) {
183
- console.log(`⚠️ 模板中不存在: ${fileConfig.path}`);
184
- return;
185
- }
186
-
187
- console.log(`\n🔍 处理: ${fileConfig.path}`);
188
-
189
- switch (fileConfig.type) {
190
- case 'merge':
191
- if (fileConfig.path === 'package.json') {
192
- const merged = this.mergePackageJson(templatePath, currentPath);
193
- const shouldUpdate = await this.askUser('是否应用 package.json 合并?[y/n]: ', ['y', 'n']);
194
- if (shouldUpdate === 'y') {
195
- fs.writeFileSync(currentPath, JSON.stringify(merged, null, 2));
196
- console.log(' package.json 已合并');
197
- this.changes.push(`合并: ${fileConfig.path}`);
198
- }
199
- }
200
- break;
201
-
202
- case 'diff':
203
- const action = await this.showDiff(templatePath, currentPath);
204
- if (action === 'u') {
205
- fs.copyFileSync(templatePath, currentPath);
206
- console.log(`✅ 已更新: ${fileConfig.path}`);
207
- this.changes.push(`更新: ${fileConfig.path}`);
208
- } else {
209
- console.log(`⏭️ 跳过: ${fileConfig.path}`);
210
- }
211
- break;
212
-
213
- case 'overwrite':
214
- const shouldOverwrite = await this.askUser(`是否覆盖 ${fileConfig.path}?[y/n]: `, ['y', 'n']);
215
- if (shouldOverwrite === 'y') {
216
- fs.copyFileSync(templatePath, currentPath);
217
- console.log(`✅ 已覆盖: ${fileConfig.path}`);
218
- this.changes.push(`覆盖: ${fileConfig.path}`);
219
- }
220
- break;
221
- }
222
- }
223
-
224
- /**
225
- * 显示变更摘要
226
- */
227
- showSummary() {
228
- console.log('\n📋 同步摘要:');
229
- console.log('='.repeat(50));
230
-
231
- if (this.changes.length === 0) {
232
- console.log('✨ 没有应用任何变更');
233
- } else {
234
- this.changes.forEach((change, index) => {
235
- console.log(`${index + 1}. ${change}`);
236
- });
237
- }
238
-
239
- console.log('='.repeat(50));
240
- }
241
- /**
242
- * 清理临时文件
243
- */
244
- cleanup() {
245
- if (fs.existsSync(this.tempDir)) {
246
- try {
247
- execSync(`rmdir /s /q ${this.tempDir}`, { stdio: 'ignore' });
248
- } catch (error) {
249
- // 尝试使用 Node.js 方法清理
250
- try {
251
- fs.rmSync(this.tempDir, { recursive: true, force: true });
252
- } catch (e) {
253
- console.log('⚠️ 临时目录清理失败');
254
- }
255
- }
256
- }
257
- if (this.rl) {
258
- this.rl.close();
259
- }
260
- }
261
-
262
- /**
263
- * 加载配置文件
264
- * @returns {Object}
265
- */
266
- loadConfig() {
267
- try {
268
- if (fs.existsSync(this.configFile)) {
269
- return JSON.parse(fs.readFileSync(this.configFile, 'utf8'));
270
- }
271
- } catch (error) {
272
- console.log('⚠️ 配置文件读取失败,将使用默认配置');
273
- }
274
- return {};
275
- }
276
-
277
- /**
278
- * 保存配置文件
279
- * @param {Object} config 配置对象
280
- */
281
- saveConfig(config) {
282
- try {
283
- fs.writeFileSync(this.configFile, JSON.stringify(config, null, 2));
284
- console.log('💾 配置已保存');
285
- } catch (error) {
286
- console.log('⚠️ 配置保存失败');
287
- }
288
- }
289
-
290
- /**
291
- * 验证仓库 URL
292
- * @param {string} repoUrl 仓库 URL
293
- * @returns {boolean}
294
- */
295
- validateRepoUrl(repoUrl) {
296
- if (!repoUrl) return false;
297
-
298
- // 支持多种格式的 Git 仓库 URL
299
- const patterns = [
300
- /^https:\/\/github\.com\/[\w\-\.]+\/[\w\-\.]+\.git$/,
301
- /^https:\/\/github\.com\/[\w\-\.]+\/[\w\-\.]+$/,
302
- /^git@github\.com:[\w\-\.]+\/[\w\-\.]+\.git$/,
303
- /^https:\/\/gitlab\.com\/[\w\-\.\/]+\.git$/,
304
- /^https:\/\/gitlab\.com\/[\w\-\.\/]+$/,
305
- /^git@gitlab\.com:[\w\-\.\/]+\.git$/,
306
- /^https:\/\/bitbucket\.org\/[\w\-\.]+\/[\w\-\.]+\.git$/,
307
- /^https:\/\/bitbucket\.org\/[\w\-\.]+\/[\w\-\.]+$/,
308
- /^git@bitbucket\.org:[\w\-\.]+\/[\w\-\.]+\.git$/,
309
- ];
310
-
311
- return patterns.some(pattern => pattern.test(repoUrl));
312
- }
313
-
314
- /**
315
- * 获取模板仓库 URL
316
- * @returns {Promise<string>}
317
- */
318
- async getTemplateRepo() {
319
- // 如果构造函数中已指定,直接使用
320
- if (this.templateRepo && this.validateRepoUrl(this.templateRepo)) {
321
- console.log(`📦 使用指定的模板仓库: ${this.templateRepo}`);
322
- return this.templateRepo;
323
- }
324
-
325
- // 尝试从配置文件加载
326
- const config = this.loadConfig();
327
- if (config.templateRepo && this.validateRepoUrl(config.templateRepo)) {
328
- const useLastRepo = await this.askUser(
329
- `📦 发现之前使用的模板仓库: ${config.templateRepo}\n是否继续使用?[y/n]: `,
330
- ['y', 'n']
331
- );
332
-
333
- if (useLastRepo === 'y') {
334
- this.templateRepo = config.templateRepo;
335
- return this.templateRepo;
336
- }
337
- }
338
-
339
- // 交互式输入新的仓库 URL
340
- console.log('\n🔗 请输入模板仓库 URL');
341
- console.log('支持的格式:');
342
- console.log(' • https://github.com/owner/repo.git');
343
- console.log(' • https://github.com/owner/repo');
344
- console.log(' • git@github.com:owner/repo.git');
345
- console.log(' • GitLab, Bitbucket 等其他 Git 托管平台');
346
- console.log('\n💡 示例: https://github.com/antfu/vitesse-lite.git');
347
-
348
- while (true) {
349
- const repoUrl = await this.askUser('\n请输入模板仓库 URL: ');
350
-
351
- if (!repoUrl.trim()) {
352
- console.log('❌ 仓库 URL 不能为空,请重新输入');
353
- continue;
354
- }
355
-
356
- const normalizedUrl = repoUrl.trim();
357
-
358
- if (this.validateRepoUrl(normalizedUrl)) {
359
- this.templateRepo = normalizedUrl;
360
-
361
- // 询问是否保存配置
362
- const shouldSave = await this.askUser('💾 是否保存此配置供下次使用?[y/n]: ', ['y', 'n']);
363
- if (shouldSave === 'y') {
364
- this.saveConfig({ templateRepo: normalizedUrl });
365
- }
366
-
367
- return this.templateRepo;
368
- } else {
369
- console.log('❌ 仓库 URL 格式不正确,请检查后重新输入');
370
- console.log(' 确保 URL 指向有效的 Git 仓库');
371
- }
372
- }
373
- }
374
-
375
- /**
376
- * 测试仓库连接
377
- * @param {string} repoUrl 仓库 URL
378
- * @returns {Promise<boolean>}
379
- */
380
- async testRepository(repoUrl) {
381
- console.log('🔍 测试仓库连接...');
382
- try {
383
- // 使用 git ls-remote 测试仓库是否可访问
384
- execSync(`git ls-remote "${repoUrl}" HEAD`, {
385
- stdio: this.verbose ? 'inherit' : 'ignore',
386
- timeout: 10000 // 10秒超时
387
- });
388
- console.log('✅ 仓库连接成功');
389
- return true;
390
- } catch (error) {
391
- console.log('❌ 仓库连接失败');
392
- if (this.verbose) {
393
- console.log(`错误详情: ${error.message}`);
394
- }
395
- console.log('请检查:');
396
- console.log(' • 仓库 URL 是否正确');
397
- console.log(' • 网络连接是否正常');
398
- console.log(' • 是否有访问权限(对于私有仓库)');
399
- return false;
400
- }
401
- }
402
- /**
403
- * 初始化配置向导
404
- */
405
- async initConfig() {
406
- console.log('🔧 初始化配置向导\n');
407
-
408
- const config = {};
409
-
410
- console.log('请设置默认模板仓库:');
411
- config.templateRepo = await this.getTemplateRepo();
412
-
413
- // 询问默认文件处理配置
414
- console.log('\n📁 配置要同步的文件类型:');
415
- const configureFiles = await this.askUser('是否要自定义文件同步配置?[y/n]: ', ['y', 'n']);
416
-
417
- if (configureFiles === 'y') {
418
- const customFiles = [];
419
- console.log('\n可选的文件类型:');
420
- console.log('1. package.json (智能合并)');
421
- console.log('2. tsconfig.json (差异对比)');
422
- console.log('3. 配置文件 (.eslintrc, vite.config 等)');
423
- console.log('4. 工作流文件 (GitHub Actions, CI 等)');
424
-
425
- // 这里可以添加更多自定义文件配置逻辑
426
- // 为简化演示,暂时使用默认配置
427
- config.filesToProcess = this.filesToProcess;
428
- } else {
429
- config.filesToProcess = this.filesToProcess;
430
- }
431
-
432
- // 保存配置
433
- this.saveConfig(config);
434
- console.log('\n✅ 配置初始化完成!');
435
- console.log('💡 可以直接运行 template-sync 开始同步');
436
- }
437
- /**
438
- * 主同步流程
439
- */
440
- async sync() {
441
- try {
442
- console.log('🚀 开始模板同步...\n');
443
-
444
- // 1. 获取模板仓库 URL
445
- await this.getTemplateRepo();
446
-
447
- // 2. 测试仓库连接
448
- const isReachable = await this.testRepository(this.templateRepo);
449
- if (!isReachable) {
450
- const shouldContinue = await this.askUser('是否仍要尝试克隆?[y/n]: ', ['y', 'n']);
451
- if (shouldContinue === 'n') {
452
- console.log('⏹️ 同步已取消');
453
- return;
454
- }
455
- }
456
-
457
- // 3. 创建备份
458
- this.createBackup();
459
-
460
- // 4. 克隆模板
461
- await this.cloneTemplate();
462
-
463
- // 5. 逐个处理文件
464
- for (const fileConfig of this.filesToProcess) {
465
- await this.processFile(fileConfig);
466
- }
467
-
468
- // 6. 显示摘要
469
- this.showSummary();
470
-
471
- // 7. 询问是否安装依赖
472
- if (this.changes.some(change => change.includes('package.json'))) {
473
- const shouldInstall = await this.askUser('\n是否运行 pnpm install 更新依赖?[y/n]: ', ['y', 'n']);
474
- if (shouldInstall === 'y') {
475
- console.log('\n📦 安装依赖...');
476
- execSync('pnpm install', { stdio: 'inherit' });
477
- }
478
- }
479
-
480
- console.log('\n✅ 模板同步完成!');
481
- console.log('💡 如有问题,可以使用 git stash pop 恢复更改');
482
-
483
- } catch (error) {
484
- console.error('❌ 同步失败:', error.message);
485
- process.exit(1);
486
- } finally {
487
- this.cleanup();
488
- }
489
- }
490
- }
491
-
492
- module.exports = TemplateSyncer;
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.TemplateSyncer = void 0;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const child_process_1 = require("child_process");
43
+ const inquirer_1 = __importDefault(require("inquirer"));
44
+ const chalk = require('chalk');
45
+ const glob_1 = require("glob");
46
+ /**
47
+ * 智能模板同步工具
48
+ * 用于将项目与模板仓库保持同步
49
+ */
50
+ class TemplateSyncer {
51
+ constructor(options = {}) {
52
+ this.tempDir = options.tempDir || '.temp-template';
53
+ this.templateRepo = options.templateRepo;
54
+ this.branch = options.branch;
55
+ this.configFile = '.template-sync.json';
56
+ this.changes = [];
57
+ this.verbose = options.verbose || false;
58
+ // 忽略的文件和目录模式
59
+ this.ignorePatterns = [
60
+ 'node_modules/**',
61
+ '.git/**',
62
+ 'dist/**',
63
+ 'build/**',
64
+ 'coverage/**',
65
+ '.nyc_output/**',
66
+ 'logs/**',
67
+ '*.log',
68
+ '.DS_Store',
69
+ 'Thumbs.db',
70
+ '.vscode/**',
71
+ '.idea/**',
72
+ '*.tmp',
73
+ '*.temp',
74
+ '.cache/**',
75
+ '.nuxt/**',
76
+ '.next/**',
77
+ '.output/**',
78
+ this.tempDir + '/**'
79
+ ];
80
+ // 扩展的文件类型支持
81
+ this.fileTypeConfig = {
82
+ // JavaScript/TypeScript 相关
83
+ '.js': { type: 'js', category: '脚本文件', icon: '📜' },
84
+ '.mjs': { type: 'js', category: '脚本文件', icon: '📜' },
85
+ '.cjs': { type: 'js', category: '脚本文件', icon: '📜' },
86
+ '.ts': { type: 'js', category: '脚本文件', icon: '📜' },
87
+ '.jsx': { type: 'react', category: 'React 组件', icon: '⚛️' },
88
+ '.tsx': { type: 'react', category: 'React 组件', icon: '⚛️' },
89
+ // Vue 相关
90
+ '.vue': { type: 'vue', category: 'Vue 组件', icon: '💚' },
91
+ // Angular 相关
92
+ '.component.ts': { type: 'angular', category: 'Angular 组件', icon: '🅰️' },
93
+ '.service.ts': { type: 'angular', category: 'Angular 服务', icon: '🅰️' },
94
+ '.module.ts': { type: 'angular', category: 'Angular 模块', icon: '🅰️' },
95
+ '.directive.ts': { type: 'angular', category: 'Angular 指令', icon: '🅰️' },
96
+ // Svelte 相关
97
+ '.svelte': { type: 'svelte', category: 'Svelte 组件', icon: '🔥' },
98
+ // 样式文件
99
+ '.css': { type: 'css', category: '样式文件', icon: '🎨' },
100
+ '.scss': { type: 'css', category: '样式文件', icon: '🎨' },
101
+ '.sass': { type: 'css', category: '样式文件', icon: '🎨' },
102
+ '.less': { type: 'css', category: '样式文件', icon: '🎨' },
103
+ '.styl': { type: 'css', category: '样式文件', icon: '🎨' },
104
+ '.stylus': { type: 'css', category: '样式文件', icon: '🎨' },
105
+ // 配置文件
106
+ '.json': { type: 'json', category: '配置文件', icon: '⚙️' },
107
+ '.yml': { type: 'yaml', category: '配置文件', icon: '⚙️' },
108
+ '.yaml': { type: 'yaml', category: '配置文件', icon: '⚙️' },
109
+ '.toml': { type: 'config', category: '配置文件', icon: '⚙️' },
110
+ '.ini': { type: 'config', category: '配置文件', icon: '⚙️' },
111
+ '.xml': { type: 'config', category: '配置文件', icon: '⚙️' },
112
+ // 文档文件
113
+ '.md': { type: 'text', category: '文档文件', icon: '📝' },
114
+ '.txt': { type: 'text', category: '文档文件', icon: '📝' },
115
+ '.rst': { type: 'text', category: '文档文件', icon: '📝' },
116
+ // 图片文件
117
+ '.png': { type: 'image', category: '图片文件', icon: '🖼️' },
118
+ '.jpg': { type: 'image', category: '图片文件', icon: '🖼️' },
119
+ '.jpeg': { type: 'image', category: '图片文件', icon: '🖼️' },
120
+ '.gif': { type: 'image', category: '图片文件', icon: '🖼️' },
121
+ '.svg': { type: 'image', category: '图片文件', icon: '🖼️' },
122
+ '.webp': { type: 'image', category: '图片文件', icon: '🖼️' },
123
+ '.ico': { type: 'image', category: '图片文件', icon: '🖼️' },
124
+ // 其他文件
125
+ '.sh': { type: 'text', category: '脚本文件', icon: '🐚' },
126
+ '.bat': { type: 'text', category: '脚本文件', icon: '🪟' },
127
+ '.ps1': { type: 'text', category: '脚本文件', icon: '💙' },
128
+ '.env': { type: 'config', category: '环境配置', icon: '🌍' },
129
+ '.example': { type: 'config', category: '示例文件', icon: '📄' },
130
+ '.sample': { type: 'config', category: '示例文件', icon: '📄' }
131
+ };
132
+ // 特殊文件配置(无扩展名或特殊命名)
133
+ this.specialFiles = {
134
+ // Git 相关
135
+ '.gitignore': { type: 'config', category: '版本控制', icon: '📋' },
136
+ '.gitattributes': { type: 'config', category: '版本控制', icon: '📋' },
137
+ '.gitmessage': { type: 'text', category: '版本控制', icon: '📋' },
138
+ // Node.js 相关
139
+ '.npmrc': { type: 'config', category: '包管理', icon: '📦' },
140
+ '.nvmrc': { type: 'config', category: '环境配置', icon: '🔧' },
141
+ 'package.json': { type: 'json', category: '项目配置', icon: '📦' },
142
+ 'package-lock.json': { type: 'json', category: '包管理', icon: '🔒' },
143
+ 'yarn.lock': { type: 'text', category: '包管理', icon: '🧶' },
144
+ 'pnpm-lock.yaml': { type: 'yaml', category: '包管理', icon: '📦' },
145
+ // 编辑器配置
146
+ '.editorconfig': { type: 'config', category: '编辑器配置', icon: '✏️' },
147
+ '.vscode/settings.json': { type: 'json', category: 'VS Code', icon: '💙' },
148
+ '.vscode/extensions.json': { type: 'json', category: 'VS Code', icon: '💙' },
149
+ '.vscode/launch.json': { type: 'json', category: 'VS Code', icon: '💙' },
150
+ // 代码质量工具
151
+ '.eslintrc': { type: 'json', category: '代码质量', icon: '🔍' },
152
+ '.eslintrc.js': { type: 'js', category: '代码质量', icon: '🔍' },
153
+ '.eslintrc.json': { type: 'json', category: '代码质量', icon: '🔍' },
154
+ '.eslintrc.yml': { type: 'yaml', category: '代码质量', icon: '🔍' },
155
+ '.eslintrc.yaml': { type: 'yaml', category: '代码质量', icon: '🔍' },
156
+ '.prettierrc': { type: 'json', category: '代码格式化', icon: '💄' },
157
+ '.prettierrc.js': { type: 'js', category: '代码格式化', icon: '💄' },
158
+ '.prettierrc.json': { type: 'json', category: '代码格式化', icon: '💄' },
159
+ '.prettierrc.yml': { type: 'yaml', category: '代码格式化', icon: '💄' },
160
+ '.stylelintrc': { type: 'json', category: '样式检查', icon: '🎨' },
161
+ '.stylelintrc.js': { type: 'js', category: '样式检查', icon: '🎨' },
162
+ '.stylelintrc.json': { type: 'json', category: '样式检查', icon: '🎨' },
163
+ // 构建工具配置
164
+ 'vite.config.js': { type: 'js', category: '构建配置', icon: '⚡' },
165
+ 'vite.config.ts': { type: 'js', category: '构建配置', icon: '⚡' },
166
+ 'webpack.config.js': { type: 'js', category: '构建配置', icon: '📦' },
167
+ 'rollup.config.js': { type: 'js', category: '构建配置', icon: '🎯' },
168
+ 'nuxt.config.js': { type: 'js', category: '构建配置', icon: '💚' },
169
+ 'nuxt.config.ts': { type: 'js', category: '构建配置', icon: '💚' },
170
+ 'next.config.js': { type: 'js', category: '构建配置', icon: '⚫' },
171
+ 'tailwind.config.js': { type: 'js', category: '样式配置', icon: '🌊' },
172
+ 'unocss.config.js': { type: 'js', category: '样式配置', icon: '🎨' },
173
+ 'unocss.config.ts': { type: 'js', category: '样式配置', icon: '🎨' },
174
+ 'postcss.config.js': { type: 'js', category: '样式配置', icon: '📮' },
175
+ // TypeScript 配置
176
+ 'tsconfig.json': { type: 'json', category: 'TypeScript', icon: '🔷' },
177
+ 'jsconfig.json': { type: 'json', category: 'JavaScript', icon: '🟨' },
178
+ // 测试配置
179
+ 'jest.config.js': { type: 'js', category: '测试配置', icon: '🃏' },
180
+ 'vitest.config.js': { type: 'js', category: '测试配置', icon: '🧪' },
181
+ 'vitest.config.ts': { type: 'js', category: '测试配置', icon: '🧪' },
182
+ 'cypress.config.js': { type: 'js', category: '测试配置', icon: '🌀' },
183
+ 'playwright.config.js': { type: 'js', category: '测试配置', icon: '🎭' },
184
+ 'playwright.config.ts': { type: 'js', category: '测试配置', icon: '🎭' },
185
+ // 容器化
186
+ 'Dockerfile': { type: 'text', category: '容器化', icon: '🐳' },
187
+ '.dockerignore': { type: 'text', category: '容器化', icon: '🐳' },
188
+ 'docker-compose.yml': { type: 'yaml', category: '容器化', icon: '🐳' },
189
+ 'docker-compose.yaml': { type: 'yaml', category: '容器化', icon: '🐳' },
190
+ // 其他重要文件
191
+ 'LICENSE': { type: 'text', category: '许可证', icon: '📄' },
192
+ 'README.md': { type: 'text', category: '文档', icon: '📖' },
193
+ 'CHANGELOG.md': { type: 'text', category: '文档', icon: '📋' },
194
+ 'CONTRIBUTING.md': { type: 'text', category: '文档', icon: '🤝' },
195
+ 'Makefile': { type: 'text', category: '构建脚本', icon: '🔨' },
196
+ 'renovate.json': { type: 'json', category: '自动化', icon: '🔄' },
197
+ 'netlify.toml': { type: 'config', category: '部署配置', icon: '🌐' }, 'vercel.json': { type: 'json', category: '部署配置', icon: '▲' }
198
+ };
199
+ }
200
+ /**
201
+ * 创建 Git 备份
202
+ */
203
+ createBackup() {
204
+ console.log('📦 创建备份...');
205
+ try {
206
+ (0, child_process_1.execSync)('git add . && git stash push -m "Template sync backup"', {
207
+ stdio: this.verbose ? 'inherit' : 'ignore'
208
+ });
209
+ console.log('✅ 备份已创建');
210
+ }
211
+ catch (error) {
212
+ console.log('⚠️ Git 备份失败,请确保有 Git 变更需要备份');
213
+ }
214
+ }
215
+ /**
216
+ * 克隆模板仓库
217
+ */
218
+ async cloneTemplate() {
219
+ console.log('📦 克隆最新模板...');
220
+ if (fs.existsSync(this.tempDir)) {
221
+ (0, child_process_1.execSync)(`rmdir /s /q ${this.tempDir}`, { stdio: 'ignore' });
222
+ }
223
+ try {
224
+ // 先克隆整个仓库(不使用 --depth 1 以获取所有分支信息)
225
+ (0, child_process_1.execSync)(`git clone "${this.templateRepo}" ${this.tempDir}`, {
226
+ stdio: this.verbose ? 'inherit' : 'ignore'
227
+ });
228
+ // 如果没有指定分支,让用户选择
229
+ if (!this.branch) {
230
+ this.branch = await this.selectBranch();
231
+ }
232
+ // 切换到指定分支
233
+ if (this.branch && this.branch !== 'main' && this.branch !== 'master') {
234
+ console.log(`📋 切换到分支: ${this.branch}`);
235
+ (0, child_process_1.execSync)(`git checkout ${this.branch}`, {
236
+ cwd: this.tempDir,
237
+ stdio: this.verbose ? 'inherit' : 'ignore'
238
+ });
239
+ }
240
+ // 删除 .git 目录
241
+ (0, child_process_1.execSync)(`rmdir /s /q ${this.tempDir}\\.git`, { stdio: 'ignore' });
242
+ console.log('✅ 模板克隆完成');
243
+ }
244
+ catch (error) {
245
+ console.log('❌ 模板克隆失败');
246
+ console.log('可能的原因:');
247
+ console.log(' • 网络连接问题');
248
+ console.log(' • 仓库不存在或无访问权限');
249
+ console.log(' • Git 未正确安装');
250
+ console.log(' • 指定的分支不存在');
251
+ if (this.verbose && error instanceof Error) {
252
+ console.log(`错误详情: ${error.message}`);
253
+ }
254
+ throw new Error('模板克隆失败');
255
+ }
256
+ } /**
257
+ * 获取或设置模板仓库
258
+ */
259
+ async getTemplateRepo() {
260
+ if (this.templateRepo) {
261
+ return this.templateRepo;
262
+ }
263
+ // 尝试从配置文件读取
264
+ const config = this.loadConfig();
265
+ if (config.templateRepo) {
266
+ this.templateRepo = config.templateRepo;
267
+ // 同时读取分支配置
268
+ if (config.branch && !this.branch) {
269
+ this.branch = config.branch;
270
+ }
271
+ return this.templateRepo;
272
+ }
273
+ // 交互式获取
274
+ const { templateRepo } = await inquirer_1.default.prompt([
275
+ {
276
+ type: 'input',
277
+ name: 'templateRepo',
278
+ message: '请输入模板仓库 URL:',
279
+ validate: (input) => {
280
+ if (!input.trim()) {
281
+ return '模板仓库 URL 不能为空';
282
+ }
283
+ return true;
284
+ }
285
+ }
286
+ ]);
287
+ this.templateRepo = templateRepo;
288
+ // 保存到配置文件
289
+ this.saveConfig({ ...config, templateRepo });
290
+ return templateRepo;
291
+ }
292
+ /**
293
+ * 测试仓库连接
294
+ */
295
+ async testRepository(repo) {
296
+ console.log(`🔍 测试仓库连接: ${repo}`);
297
+ try {
298
+ // 尝试获取仓库信息
299
+ (0, child_process_1.execSync)(`git ls-remote --heads ${repo}`, { stdio: 'ignore' });
300
+ console.log('✅ 仓库连接成功');
301
+ return true;
302
+ }
303
+ catch (error) {
304
+ console.log('❌ 仓库连接失败');
305
+ return false;
306
+ }
307
+ }
308
+ /**
309
+ * 扫描模板文件
310
+ */
311
+ async scanTemplateFiles() {
312
+ const files = [];
313
+ if (!fs.existsSync(this.tempDir)) {
314
+ throw new Error('模板目录不存在,请先克隆模板');
315
+ }
316
+ const globPattern = '**/*';
317
+ const foundFiles = await (0, glob_1.glob)(globPattern, {
318
+ cwd: this.tempDir,
319
+ ignore: this.ignorePatterns,
320
+ dot: true
321
+ });
322
+ for (const file of foundFiles) {
323
+ const fullPath = path.join(this.tempDir, file);
324
+ try {
325
+ const stat = fs.statSync(fullPath);
326
+ if (stat.isFile()) {
327
+ const fileConfig = this.analyzeFile(file, fullPath);
328
+ files.push(fileConfig);
329
+ }
330
+ }
331
+ catch (error) {
332
+ // 忽略无法访问的文件
333
+ continue;
334
+ }
335
+ }
336
+ return files.sort((a, b) => a.path.localeCompare(b.path));
337
+ }
338
+ /**
339
+ * 扫描当前项目文件
340
+ */
341
+ async scanCurrentFiles() {
342
+ const files = [];
343
+ const globPattern = '**/*';
344
+ const foundFiles = await (0, glob_1.glob)(globPattern, {
345
+ cwd: process.cwd(),
346
+ ignore: this.ignorePatterns,
347
+ dot: true
348
+ });
349
+ for (const file of foundFiles) {
350
+ const fullPath = path.join(process.cwd(), file);
351
+ try {
352
+ const stat = fs.statSync(fullPath);
353
+ if (stat.isFile()) {
354
+ const fileConfig = this.analyzeFile(file, fullPath);
355
+ fileConfig.exists = true;
356
+ files.push(fileConfig);
357
+ }
358
+ }
359
+ catch (error) {
360
+ // 忽略无法访问的文件
361
+ continue;
362
+ }
363
+ }
364
+ return files.sort((a, b) => a.path.localeCompare(b.path));
365
+ }
366
+ /**
367
+ * 分析文件类型和特征
368
+ */
369
+ analyzeFile(relativePath, fullPath) {
370
+ const fileName = path.basename(relativePath);
371
+ const ext = path.extname(fileName);
372
+ // 检查特殊文件
373
+ if (this.specialFiles[fileName]) {
374
+ const config = this.specialFiles[fileName];
375
+ return {
376
+ path: relativePath,
377
+ fullPath,
378
+ type: config.type,
379
+ category: config.category,
380
+ selected: true,
381
+ icon: config.icon
382
+ };
383
+ }
384
+ // 检查扩展名
385
+ if (ext && this.fileTypeConfig[ext]) {
386
+ const config = this.fileTypeConfig[ext];
387
+ return {
388
+ path: relativePath,
389
+ fullPath,
390
+ type: config.type,
391
+ category: config.category,
392
+ selected: true,
393
+ icon: config.icon
394
+ };
395
+ }
396
+ // 默认配置
397
+ return {
398
+ path: relativePath,
399
+ fullPath,
400
+ type: 'text',
401
+ category: '其他文件',
402
+ selected: true,
403
+ icon: '📄'
404
+ };
405
+ }
406
+ /**
407
+ * 对比文件并生成变更列表
408
+ */
409
+ async compareFiles(templateFiles, currentFiles) {
410
+ const changes = [];
411
+ const currentFileMap = new Map(currentFiles.map(f => [f.path, f]));
412
+ for (const templateFile of templateFiles) {
413
+ const currentFile = currentFileMap.get(templateFile.path);
414
+ if (!currentFile) {
415
+ // 新文件
416
+ changes.push({
417
+ ...templateFile,
418
+ status: 'new',
419
+ isNew: true,
420
+ exists: false,
421
+ templatePath: templateFile.fullPath,
422
+ currentPath: path.join(process.cwd(), templateFile.path)
423
+ });
424
+ }
425
+ else {
426
+ // 检查文件是否有变更
427
+ const templateContent = fs.readFileSync(templateFile.fullPath, 'utf8');
428
+ const currentContent = fs.readFileSync(currentFile.fullPath, 'utf8');
429
+ if (templateContent !== currentContent) {
430
+ changes.push({
431
+ ...templateFile,
432
+ status: 'modified',
433
+ isNew: false,
434
+ exists: true,
435
+ templatePath: templateFile.fullPath,
436
+ currentPath: currentFile.fullPath
437
+ });
438
+ }
439
+ }
440
+ }
441
+ return changes;
442
+ }
443
+ /**
444
+ * 交互式选择要应用的变更
445
+ */
446
+ async selectChangesToApply(changes) {
447
+ if (changes.length === 0) {
448
+ console.log('✅ 没有发现任何变更');
449
+ return [];
450
+ }
451
+ console.log(`\n发现 ${changes.length} 个文件需要处理:\n`);
452
+ // 按类别分组显示
453
+ const categories = new Map();
454
+ changes.forEach(file => {
455
+ const category = file.category;
456
+ if (!categories.has(category)) {
457
+ categories.set(category, []);
458
+ }
459
+ categories.get(category).push(file);
460
+ });
461
+ // 显示分类统计
462
+ for (const [category, files] of categories) {
463
+ const newFiles = files.filter(f => f.status === 'new').length;
464
+ const modifiedFiles = files.filter(f => f.status === 'modified').length;
465
+ console.log(`${files[0].icon} ${category}: ${files.length} 个文件 (新增: ${newFiles}, 修改: ${modifiedFiles})`);
466
+ }
467
+ // 询问用户如何处理
468
+ const { action } = await inquirer_1.default.prompt([
469
+ {
470
+ type: 'list',
471
+ name: 'action',
472
+ message: '选择处理方式:',
473
+ choices: [
474
+ { name: '📋 按分类选择文件', value: 'category' },
475
+ { name: '📝 逐一选择文件', value: 'individual' },
476
+ { name: ' 应用所有变更', value: 'all' },
477
+ { name: '❌ 取消操作', value: 'cancel' }
478
+ ]
479
+ }
480
+ ]);
481
+ if (action === 'cancel') {
482
+ return [];
483
+ }
484
+ if (action === 'all') {
485
+ return changes;
486
+ }
487
+ if (action === 'category') {
488
+ return await this.selectByCategory(categories);
489
+ }
490
+ return await this.selectIndividually(changes);
491
+ }
492
+ /**
493
+ * 按分类选择文件
494
+ */
495
+ async selectByCategory(categories) {
496
+ const selectedFiles = [];
497
+ for (const [category, files] of categories) {
498
+ const { shouldProcess } = await inquirer_1.default.prompt([
499
+ {
500
+ type: 'confirm',
501
+ name: 'shouldProcess',
502
+ message: `处理 ${files[0].icon} ${category} (${files.length} 个文件)?`,
503
+ default: true
504
+ }
505
+ ]);
506
+ if (shouldProcess) {
507
+ selectedFiles.push(...files);
508
+ }
509
+ }
510
+ return selectedFiles;
511
+ }
512
+ /**
513
+ * 逐一选择文件
514
+ */
515
+ async selectIndividually(changes) {
516
+ const choices = changes.map(file => ({
517
+ name: `${file.icon} ${file.path} (${file.status === 'new' ? '新增' : '修改'})`,
518
+ value: file,
519
+ checked: true
520
+ }));
521
+ const { selectedFiles } = await inquirer_1.default.prompt([
522
+ {
523
+ type: 'checkbox',
524
+ name: 'selectedFiles',
525
+ message: '选择要处理的文件:',
526
+ choices,
527
+ pageSize: 15
528
+ }
529
+ ]);
530
+ return selectedFiles;
531
+ }
532
+ /**
533
+ * 批量更新文件
534
+ */
535
+ async batchUpdateFiles(files) {
536
+ const results = {
537
+ success: [],
538
+ skipped: [],
539
+ failed: []
540
+ };
541
+ console.log(`\n开始处理 ${files.length} 个文件...\n`);
542
+ for (const file of files) {
543
+ try {
544
+ console.log(`处理: ${file.icon} ${file.path}`);
545
+ const result = await this.updateSingleFile(file);
546
+ if (result.applied) {
547
+ results.success.push(file.path);
548
+ console.log(`✅ ${result.message}`);
549
+ }
550
+ else {
551
+ results.skipped.push(file.path);
552
+ console.log(`⏭️ ${result.message}`);
553
+ }
554
+ }
555
+ catch (error) {
556
+ const errorMessage = error instanceof Error ? error.message : String(error);
557
+ results.failed.push({ path: file.path, error: errorMessage });
558
+ console.log(`❌ 失败: ${errorMessage}`);
559
+ }
560
+ }
561
+ return results;
562
+ }
563
+ /**
564
+ * 更新单个文件
565
+ */
566
+ async updateSingleFile(file) {
567
+ if (!file.templatePath) {
568
+ return { applied: false, message: '模板文件路径不存在' };
569
+ }
570
+ // 确保目标目录存在
571
+ const targetDir = path.dirname(file.currentPath);
572
+ if (!fs.existsSync(targetDir)) {
573
+ fs.mkdirSync(targetDir, { recursive: true });
574
+ }
575
+ // 特殊处理 package.json
576
+ if (file.path === 'package.json') {
577
+ return await this.mergePackageJson(file);
578
+ }
579
+ // 复制文件
580
+ fs.copyFileSync(file.templatePath, file.currentPath);
581
+ return {
582
+ applied: true,
583
+ message: file.status === 'new' ? '文件已创建' : '文件已更新'
584
+ };
585
+ }
586
+ /**
587
+ * 智能合并 package.json
588
+ */
589
+ async mergePackageJson(file) {
590
+ const templateContent = JSON.parse(fs.readFileSync(file.templatePath, 'utf8'));
591
+ if (!fs.existsSync(file.currentPath)) {
592
+ // 如果当前文件不存在,直接复制
593
+ fs.writeFileSync(file.currentPath, JSON.stringify(templateContent, null, 2));
594
+ return { applied: true, message: 'package.json 已创建' };
595
+ }
596
+ const currentContent = JSON.parse(fs.readFileSync(file.currentPath, 'utf8'));
597
+ // 智能合并逻辑
598
+ const merged = {
599
+ ...currentContent,
600
+ ...templateContent,
601
+ // 保留当前项目的基本信息
602
+ name: currentContent.name || templateContent.name,
603
+ version: currentContent.version || templateContent.version,
604
+ description: currentContent.description || templateContent.description,
605
+ // 合并依赖
606
+ dependencies: {
607
+ ...currentContent.dependencies,
608
+ ...templateContent.dependencies
609
+ },
610
+ devDependencies: {
611
+ ...currentContent.devDependencies,
612
+ ...templateContent.devDependencies
613
+ },
614
+ // 合并脚本
615
+ scripts: {
616
+ ...currentContent.scripts,
617
+ ...templateContent.scripts
618
+ }
619
+ };
620
+ fs.writeFileSync(file.currentPath, JSON.stringify(merged, null, 2));
621
+ return { applied: true, message: 'package.json 已智能合并' };
622
+ }
623
+ /**
624
+ * 显示操作摘要
625
+ */
626
+ showSummary(results) {
627
+ console.log('\n' + '='.repeat(50));
628
+ console.log('📊 操作摘要');
629
+ console.log('='.repeat(50));
630
+ console.log(`✅ 成功: ${results.success.length} 个文件`);
631
+ if (results.success.length > 0) {
632
+ results.success.forEach(file => console.log(` • ${file}`));
633
+ }
634
+ console.log(`⏭️ 跳过: ${results.skipped.length} 个文件`);
635
+ if (results.skipped.length > 0) {
636
+ results.skipped.forEach(file => console.log(` • ${file}`));
637
+ }
638
+ console.log(`❌ 失败: ${results.failed.length} 个文件`);
639
+ if (results.failed.length > 0) {
640
+ results.failed.forEach(item => console.log(` • ${item.path}: ${item.error}`));
641
+ }
642
+ console.log('\n' + '='.repeat(50));
643
+ }
644
+ /**
645
+ * 清理临时文件
646
+ */
647
+ cleanup() {
648
+ if (fs.existsSync(this.tempDir)) {
649
+ try {
650
+ (0, child_process_1.execSync)(`rmdir /s /q ${this.tempDir}`, { stdio: 'ignore' });
651
+ console.log('🧹 清理临时文件完成');
652
+ }
653
+ catch (error) {
654
+ console.log('⚠️ 清理临时文件失败');
655
+ }
656
+ }
657
+ }
658
+ /**
659
+ * 加载配置文件
660
+ */
661
+ loadConfig() {
662
+ if (fs.existsSync(this.configFile)) {
663
+ try {
664
+ return JSON.parse(fs.readFileSync(this.configFile, 'utf8'));
665
+ }
666
+ catch (error) {
667
+ console.log('⚠️ 配置文件格式错误,将使用默认配置');
668
+ }
669
+ }
670
+ return {};
671
+ }
672
+ /**
673
+ * 保存配置文件
674
+ */
675
+ saveConfig(config) {
676
+ try {
677
+ fs.writeFileSync(this.configFile, JSON.stringify(config, null, 2));
678
+ }
679
+ catch (error) {
680
+ console.log('⚠️ 保存配置文件失败');
681
+ }
682
+ }
683
+ /**
684
+ * 主同步流程
685
+ */
686
+ async sync(options = {}) {
687
+ try {
688
+ console.log(chalk.blue('🚀 开始模板同步...'));
689
+ // 获取模板仓库
690
+ const repo = await this.getTemplateRepo();
691
+ // 测试仓库连接
692
+ const isReachable = await this.testRepository(repo);
693
+ if (!isReachable) {
694
+ throw new Error('无法连接到模板仓库');
695
+ }
696
+ // 创建备份
697
+ this.createBackup();
698
+ // 克隆模板
699
+ await this.cloneTemplate();
700
+ // 扫描文件
701
+ console.log('📁 扫描模板文件...');
702
+ const templateFiles = await this.scanTemplateFiles();
703
+ console.log('📁 扫描当前文件...');
704
+ const currentFiles = await this.scanCurrentFiles();
705
+ // 对比文件
706
+ console.log('🔍 对比文件差异...');
707
+ const changes = await this.compareFiles(templateFiles, currentFiles);
708
+ // 选择要应用的变更
709
+ const selectedFiles = await this.selectChangesToApply(changes);
710
+ if (selectedFiles.length === 0) {
711
+ console.log('ℹ️ 没有选择任何文件进行处理');
712
+ return;
713
+ }
714
+ // 最终确认
715
+ const { confirm } = await inquirer_1.default.prompt([
716
+ {
717
+ type: 'confirm',
718
+ name: 'confirm',
719
+ message: `确定要处理 ${selectedFiles.length} 个文件吗?`,
720
+ default: true
721
+ }
722
+ ]);
723
+ if (!confirm) {
724
+ console.log('❌ 操作已取消');
725
+ return;
726
+ }
727
+ // 批量更新文件
728
+ const results = await this.batchUpdateFiles(selectedFiles);
729
+ // 显示摘要
730
+ this.showSummary(results);
731
+ console.log(chalk.green('🎉 模板同步完成!'));
732
+ }
733
+ catch (error) {
734
+ console.error(chalk.red('❌ 同步失败:'), error instanceof Error ? error.message : String(error));
735
+ throw error;
736
+ }
737
+ finally {
738
+ // 清理临时文件
739
+ this.cleanup();
740
+ }
741
+ }
742
+ /**
743
+ * 初始化配置向导
744
+ */
745
+ async initConfig() {
746
+ console.log(chalk.blue('🔧 初始化配置向导'));
747
+ console.log('');
748
+ const answers = await inquirer_1.default.prompt([
749
+ {
750
+ type: 'input',
751
+ name: 'templateRepo',
752
+ message: '请输入默认模板仓库 URL:',
753
+ default: 'https://github.com/IceyWu/cloud-template.git',
754
+ validate: (input) => {
755
+ if (!input.trim()) {
756
+ return '模板仓库 URL 不能为空';
757
+ }
758
+ return true;
759
+ }
760
+ },
761
+ {
762
+ type: 'input',
763
+ name: 'branch',
764
+ message: '请输入默认分支名称 (留空则每次都会询问):',
765
+ default: '',
766
+ },
767
+ {
768
+ type: 'checkbox',
769
+ name: 'ignorePatterns',
770
+ message: '选择要忽略的文件和目录:',
771
+ choices: [
772
+ { name: 'node_modules/', value: 'node_modules/**', checked: true },
773
+ { name: '.git/', value: '.git/**', checked: true },
774
+ { name: 'dist/', value: 'dist/**', checked: true },
775
+ { name: 'build/', value: 'build/**', checked: true },
776
+ { name: 'coverage/', value: 'coverage/**', checked: true },
777
+ { name: '*.log', value: '*.log', checked: true },
778
+ { name: '.DS_Store', value: '.DS_Store', checked: true },
779
+ { name: '.env.local', value: '.env.local', checked: false },
780
+ { name: 'README.md', value: 'README.md', checked: false }
781
+ ]
782
+ },
783
+ {
784
+ type: 'confirm',
785
+ name: 'verbose',
786
+ message: '是否默认启用详细输出?',
787
+ default: false
788
+ }
789
+ ]);
790
+ const config = {
791
+ templateRepo: answers.templateRepo,
792
+ branch: answers.branch || undefined,
793
+ ignorePatterns: answers.ignorePatterns,
794
+ verbose: answers.verbose,
795
+ lastSync: new Date().toISOString()
796
+ };
797
+ this.saveConfig(config);
798
+ console.log(chalk.green('✅ 配置已保存到 .template-sync.json'));
799
+ }
800
+ /**
801
+ * 批量处理模式
802
+ */
803
+ async batchProcess() {
804
+ console.log(chalk.blue('🔄 高级批量操作模式'));
805
+ // 获取模板仓库
806
+ const repo = await this.getTemplateRepo();
807
+ // 测试连接
808
+ const isReachable = await this.testRepository(repo);
809
+ if (!isReachable) {
810
+ throw new Error('无法连接到模板仓库');
811
+ }
812
+ // 克隆模板
813
+ await this.cloneTemplate();
814
+ // 扫描所有文件
815
+ const templateFiles = await this.scanTemplateFiles();
816
+ const currentFiles = await this.scanCurrentFiles();
817
+ // 生成推荐
818
+ const recommendations = await this.generateRecommendations(templateFiles, currentFiles);
819
+ if (recommendations.length === 0) {
820
+ console.log('✅ 没有发现需要处理的文件');
821
+ return;
822
+ }
823
+ // 显示推荐
824
+ console.log('\n📊 批量处理推荐:');
825
+ for (const rec of recommendations) {
826
+ const icon = rec.priority === 'high' ? '🔴' : rec.priority === 'medium' ? '🟡' : '🟢';
827
+ console.log(`${icon} ${rec.title}`);
828
+ console.log(` ${rec.description}`);
829
+ console.log(` 影响: ${rec.impact}`);
830
+ console.log(` 文件数: ${rec.files.length}`);
831
+ console.log('');
832
+ }
833
+ // 选择要处理的推荐
834
+ const { selectedRecs } = await inquirer_1.default.prompt([
835
+ {
836
+ type: 'checkbox',
837
+ name: 'selectedRecs',
838
+ message: '选择要执行的操作:',
839
+ choices: recommendations.map(rec => ({
840
+ name: `${rec.priority === 'high' ? '🔴' : rec.priority === 'medium' ? '🟡' : '🟢'} ${rec.title} (${rec.files.length} 个文件)`,
841
+ value: rec,
842
+ checked: rec.priority === 'high'
843
+ }))
844
+ }
845
+ ]);
846
+ // 执行批量处理
847
+ const allFiles = [];
848
+ selectedRecs.forEach((rec) => {
849
+ allFiles.push(...rec.files);
850
+ });
851
+ if (allFiles.length > 0) {
852
+ const results = await this.batchUpdateFiles(allFiles);
853
+ this.showSummary(results);
854
+ }
855
+ }
856
+ /**
857
+ * 生成智能推荐
858
+ */
859
+ async generateRecommendations(templateFiles, currentFiles) {
860
+ const recommendations = [];
861
+ const changes = await this.compareFiles(templateFiles, currentFiles);
862
+ if (changes.length === 0) {
863
+ return recommendations;
864
+ }
865
+ // 按优先级分组
866
+ const highPriority = [];
867
+ const mediumPriority = [];
868
+ const lowPriority = [];
869
+ changes.forEach(file => {
870
+ // 高优先级:核心配置文件
871
+ if (['package.json', 'tsconfig.json', 'vite.config.ts', 'vite.config.js'].includes(file.path)) {
872
+ highPriority.push(file);
873
+ }
874
+ // 中优先级:开发工具配置
875
+ else if (file.category === '代码质量' || file.category === '构建配置' || file.category === 'TypeScript') {
876
+ mediumPriority.push(file);
877
+ }
878
+ // 低优先级:其他文件
879
+ else {
880
+ lowPriority.push(file);
881
+ }
882
+ });
883
+ if (highPriority.length > 0) {
884
+ recommendations.push({
885
+ priority: 'high',
886
+ title: '核心配置更新',
887
+ description: '更新项目的核心配置文件,包括 package.json、构建配置等',
888
+ impact: '可能影响项目构建和依赖管理',
889
+ files: highPriority
890
+ });
891
+ }
892
+ if (mediumPriority.length > 0) {
893
+ recommendations.push({
894
+ priority: 'medium',
895
+ title: '开发工具配置',
896
+ description: '更新代码质量工具、构建工具等配置',
897
+ impact: '改善开发体验和代码质量',
898
+ files: mediumPriority
899
+ });
900
+ }
901
+ if (lowPriority.length > 0) {
902
+ recommendations.push({
903
+ priority: 'low',
904
+ title: '其他文件更新',
905
+ description: '更新文档、样式等其他文件',
906
+ impact: '对项目功能影响较小',
907
+ files: lowPriority
908
+ });
909
+ }
910
+ return recommendations;
911
+ }
912
+ /**
913
+ * 预览所有差异
914
+ */
915
+ async previewAllDifferences(files) {
916
+ console.log(chalk.blue('🔍 预览所有差异'));
917
+ if (files.length === 0) {
918
+ console.log('✅ 没有差异需要预览');
919
+ return;
920
+ }
921
+ for (const file of files) {
922
+ console.log(`\n${'='.repeat(60)}`);
923
+ console.log(`${file.icon} ${file.path} (${file.status})`);
924
+ console.log('='.repeat(60));
925
+ if (file.templatePath && file.currentPath && fs.existsSync(file.currentPath)) {
926
+ // 显示文件差异(简化版)
927
+ const templateContent = fs.readFileSync(file.templatePath, 'utf8');
928
+ const currentContent = fs.readFileSync(file.currentPath, 'utf8');
929
+ if (templateContent !== currentContent) {
930
+ console.log('📝 文件内容有差异');
931
+ console.log(`模板文件长度: ${templateContent.length} 字符`);
932
+ console.log(`当前文件长度: ${currentContent.length} 字符`);
933
+ }
934
+ }
935
+ else if (file.status === 'new') {
936
+ console.log('📄 这是一个新文件');
937
+ }
938
+ console.log(`📁 分类: ${file.category}`);
939
+ console.log(`🏷️ 类型: ${file.type}`);
940
+ }
941
+ console.log(`\n总计: ${files.length} 个文件有差异`);
942
+ }
943
+ /**
944
+ * 智能同步模式
945
+ */
946
+ async intelligentSync() {
947
+ console.log(chalk.blue('🤖 智能同步模式'));
948
+ // 扫描文件
949
+ const templateFiles = await this.scanTemplateFiles();
950
+ const currentFiles = await this.scanCurrentFiles();
951
+ // 生成推荐
952
+ const recommendations = await this.generateRecommendations(templateFiles, currentFiles);
953
+ if (recommendations.length === 0) {
954
+ console.log('✅ 项目已经是最新的,无需同步');
955
+ return;
956
+ }
957
+ // 自动选择高优先级推荐
958
+ const highPriorityRecs = recommendations.filter(rec => rec.priority === 'high');
959
+ if (highPriorityRecs.length > 0) {
960
+ console.log('🔴 发现高优先级更新,建议立即处理:');
961
+ for (const rec of highPriorityRecs) {
962
+ console.log(`• ${rec.title}: ${rec.files.length} 个文件`);
963
+ }
964
+ const { autoApply } = await inquirer_1.default.prompt([
965
+ {
966
+ type: 'confirm',
967
+ name: 'autoApply',
968
+ message: '是否自动应用高优先级更新?',
969
+ default: true
970
+ }
971
+ ]);
972
+ if (autoApply) {
973
+ const allFiles = [];
974
+ highPriorityRecs.forEach(rec => {
975
+ allFiles.push(...rec.files);
976
+ });
977
+ const results = await this.batchUpdateFiles(allFiles);
978
+ this.showSummary(results);
979
+ }
980
+ }
981
+ // 显示其他推荐
982
+ const otherRecs = recommendations.filter(rec => rec.priority !== 'high');
983
+ if (otherRecs.length > 0) {
984
+ console.log('\n其他可选更新:');
985
+ otherRecs.forEach(rec => {
986
+ const icon = rec.priority === 'medium' ? '🟡' : '🟢';
987
+ console.log(`${icon} ${rec.title}: ${rec.files.length} 个文件`);
988
+ });
989
+ console.log('\n💡 提示: 使用 --batch 模式可以手动选择这些更新');
990
+ }
991
+ }
992
+ /**
993
+ * 选择分支
994
+ */
995
+ async selectBranch() {
996
+ try {
997
+ console.log('🌿 获取所有分支...');
998
+ // 获取所有远程分支
999
+ const branchOutput = (0, child_process_1.execSync)('git branch -r', {
1000
+ cwd: this.tempDir,
1001
+ encoding: 'utf8',
1002
+ stdio: this.verbose ? 'inherit' : 'pipe'
1003
+ });
1004
+ // 解析分支列表
1005
+ const branches = branchOutput
1006
+ .split('\n')
1007
+ .map(line => line.trim())
1008
+ .filter(line => line && !line.includes('HEAD'))
1009
+ .map(line => line.replace('origin/', ''))
1010
+ .filter(branch => branch);
1011
+ if (branches.length === 0) {
1012
+ console.log('⚠️ 未找到任何分支,使用默认分支');
1013
+ return 'main';
1014
+ }
1015
+ if (branches.length === 1) {
1016
+ console.log(`📋 只有一个分支: ${branches[0]}`);
1017
+ return branches[0];
1018
+ }
1019
+ // 让用户选择分支
1020
+ console.log(`\n发现 ${branches.length} 个分支:`);
1021
+ branches.forEach((branch, index) => {
1022
+ console.log(` ${index + 1}. ${branch}`);
1023
+ });
1024
+ const { selectedBranch } = await inquirer_1.default.prompt([
1025
+ {
1026
+ type: 'list',
1027
+ name: 'selectedBranch',
1028
+ message: '请选择要同步的分支:',
1029
+ choices: branches.map(branch => ({
1030
+ name: branch === 'main' || branch === 'master' ? `${branch} (默认分支)` : branch,
1031
+ value: branch
1032
+ })),
1033
+ default: branches.find(b => b === 'main') || branches.find(b => b === 'master') || branches[0]
1034
+ }
1035
+ ]);
1036
+ return selectedBranch;
1037
+ }
1038
+ catch (error) {
1039
+ console.log('⚠️ 获取分支信息失败,使用默认分支');
1040
+ if (this.verbose && error instanceof Error) {
1041
+ console.log(`错误详情: ${error.message}`);
1042
+ }
1043
+ return 'main';
1044
+ }
1045
+ }
1046
+ }
1047
+ exports.TemplateSyncer = TemplateSyncer;
1048
+ //# sourceMappingURL=index.js.map