template-syncer 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Template Syncer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # Template Syncer
2
+
3
+ 智能模板同步工具,让你的项目与模板仓库保持同步。
4
+
5
+ ## ✨ 特性
6
+
7
+ - 🚀 **智能同步** - 自动检测并同步模板更新
8
+ - 📦 **智能合并** - 特别针对 `package.json` 的智能合并策略
9
+ - 🔄 **差异对比** - 使用 Git diff 显示文件变更
10
+ - 💾 **安全备份** - 操作前自动创建 Git 备份
11
+ - 🎯 **交互式操作** - 每个变更都需要用户确认
12
+
13
+ ## 📦 安装
14
+
15
+ ```bash
16
+ npm install -g template-syncer
17
+ ```
18
+
19
+ ## 🚀 使用方法
20
+
21
+ ### 基本用法
22
+
23
+ ```bash
24
+ # 交互式同步(会询问模板仓库)
25
+ template-sync
26
+
27
+ # 指定模板仓库
28
+ template-sync --repo https://github.com/antfu/vitesse-lite.git
29
+
30
+ # 详细模式
31
+ template-sync --verbose
32
+ ```
33
+
34
+ ### 初始化配置
35
+
36
+ ```bash
37
+ template-sync --init
38
+ ```
39
+
40
+ ## 🧪 测试
41
+
42
+ ```bash
43
+ # 创建 vitesse-lite 测试项目
44
+ npm test
45
+
46
+ # 然后在测试项目中修改文件,运行同步命令查看效果
47
+ ```
48
+
49
+ ## 📝 文件处理类型
50
+
51
+ - **merge** - 智能合并(主要用于 `package.json`)
52
+ - **diff** - 显示差异并让用户选择是否更新
53
+ - **overwrite** - 直接覆盖
54
+
55
+ ## 🛡️ 安全性
56
+
57
+ - **Git 备份**: 操作前自动创建 stash 备份
58
+ - **交互确认**: 每个文件更改都需要用户确认
59
+ - **差异显示**: 清楚展示即将进行的更改
60
+
61
+ ## 📄 许可证
62
+
63
+ MIT License
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+
3
+ const TemplateSyncer = require('../src/index');
4
+
5
+ // 解析命令行参数
6
+ const args = process.argv.slice(2);
7
+ const options = {};
8
+
9
+ // 显示帮助信息
10
+ function showHelp() {
11
+ console.log(`
12
+ 📦 Template Syncer - 智能模板同步工具
13
+
14
+ 让你的项目与模板仓库保持同步,支持智能合并和差异对比。
15
+
16
+ 用法:
17
+ template-sync [选项]
18
+
19
+ 选项:
20
+ -r, --repo <url> 指定模板仓库 URL
21
+ -v, --verbose 显示详细输出信息
22
+ -i, --init 初始化配置向导
23
+ -h, --help 显示此帮助信息
24
+ --version 显示版本号
25
+
26
+ 示例:
27
+ template-sync # 交互式同步
28
+ template-sync --init # 初始化配置
29
+ template-sync --repo https://github.com/antfu/vitesse-lite.git
30
+ template-sync --repo git@github.com:your/template.git --verbose
31
+
32
+ 支持的仓库格式:
33
+ • GitHub: https://github.com/owner/repo.git
34
+ • GitLab: https://gitlab.com/owner/repo.git
35
+ • Bitbucket: https://bitbucket.org/owner/repo.git
36
+ • SSH: git@github.com:owner/repo.git
37
+
38
+ 功能特性:
39
+ ✅ 智能合并 package.json
40
+ ✅ 文件差异对比
41
+ ✅ 交互式确认更新
42
+ ✅ Git 备份保护
43
+ ✅ 配置文件保存
44
+
45
+ 更多信息: https://github.com/your/template-sync
46
+ `);
47
+ }
48
+
49
+ // 简单的参数解析
50
+ for (let i = 0; i < args.length; i++) {
51
+ const arg = args[i];
52
+ switch (arg) {
53
+ case '--repo':
54
+ case '-r':
55
+ if (i + 1 >= args.length) {
56
+ console.error('❌ --repo 选项需要提供仓库 URL');
57
+ process.exit(1);
58
+ }
59
+ options.templateRepo = args[++i];
60
+ break; case '--verbose':
61
+ case '-v':
62
+ options.verbose = true;
63
+ break;
64
+ case '--init':
65
+ case '-i':
66
+ options.init = true;
67
+ break;
68
+ case '--help':
69
+ case '-h':
70
+ showHelp();
71
+ process.exit(0);
72
+ case '--version':
73
+ const pkg = require('../package.json');
74
+ console.log(`v${pkg.version}`);
75
+ process.exit(0);
76
+ default:
77
+ if (arg.startsWith('-')) {
78
+ console.error(`❌ 未知选项: ${arg}`);
79
+ console.log('使用 --help 查看可用选项');
80
+ process.exit(1);
81
+ }
82
+ }
83
+ }
84
+
85
+ // 显示启动信息
86
+ if (options.verbose) {
87
+ console.log('🔧 启动配置:');
88
+ if (options.templateRepo) {
89
+ console.log(` 模板仓库: ${options.templateRepo}`);
90
+ }
91
+ console.log(` 详细模式: 已启用`);
92
+ console.log('');
93
+ }
94
+
95
+ // 创建并运行同步器
96
+ async function main() {
97
+ try {
98
+ const syncer = new TemplateSyncer(options);
99
+
100
+ if (options.init) {
101
+ await syncer.initConfig();
102
+ } else {
103
+ await syncer.sync();
104
+ }
105
+ } catch (error) {
106
+ console.error('❌ 程序执行失败:', error.message);
107
+ if (options.verbose) {
108
+ console.error(error.stack);
109
+ }
110
+ process.exit(1);
111
+ }
112
+ }
113
+
114
+ main();
package/lib/index.js ADDED
@@ -0,0 +1,492 @@
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;
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "template-syncer",
3
+ "version": "1.0.0",
4
+ "description": "智能模板同步工具 - 让你的项目与模板仓库保持同步,支持智能合并、差异对比和交互式更新",
5
+ "main": "lib/index.js",
6
+ "bin": {
7
+ "template-sync": "bin/template-sync.js"
8
+ },
9
+ "scripts": {
10
+ "build": "node build.js",
11
+ "test": "node test/test.js",
12
+ "start": "node bin/template-sync.js",
13
+ "dev": "node bin/template-sync.js --verbose"
14
+ },
15
+ "keywords": [
16
+ "template",
17
+ "sync",
18
+ "vitesse",
19
+ "vue",
20
+ "vite"
21
+ ],
22
+ "author": "IceyWu <3128006406@qq.com>",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/IceyWu/template-syncer.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/IceyWu/template-syncer/issues"
30
+ },
31
+ "homepage": "https://github.com/IceyWu/template-syncer#readme",
32
+ "engines": {
33
+ "node": ">=14.0.0"
34
+ },
35
+ "files": [
36
+ "lib/",
37
+ "bin/",
38
+ "README.md",
39
+ "LICENSE"
40
+ ],
41
+ "dependencies": {},
42
+ "devDependencies": {
43
+ "chalk": "^4.1.2"
44
+ }
45
+ }