axhub-make 1.0.5 → 1.0.7

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.
@@ -63,11 +63,10 @@
63
63
  | 跳过依赖安装 | `--no-install` | 不自动安装 |
64
64
  | 跳过启动 | `--no-start` | 不自动启动 |
65
65
  | 强制模式 | `--force` | 确认风险后使用 |
66
- | **冲突处理** | `--conflict keep` | keep=保留本地 |
67
- | **冲突处理** | `--conflict overwrite` | overwrite=覆盖本地 |
68
- | **预检查模式** | `pre` | 仅检查冲突,不实际操作 |
69
66
  | **指定模板源** | `-t <url>` 或 `--template <url>` | 手动指定 Git 仓库 URL |
70
67
 
68
+ **注意**:`--conflict` 参数已内部化,默认使用 `overwrite` 模式,不对外暴露。
69
+
71
70
  ---
72
71
 
73
72
  ## 🔄 更新模式核心流程
@@ -113,100 +112,77 @@ CLI 会检测目标目录是否包含:
113
112
  如果都存在 → **更新模式**
114
113
  否则 → **初始化模式**
115
114
 
116
- ### 2️⃣ 冲突处理策略
117
-
118
- **冲突规则配置文件:`scaffold.update.json`**
119
-
120
- ⚠️ **重要**:在处理冲突前,你应该先查看项目中的 `scaffold.update.json` 文件,了解冲突规则配置。
121
-
122
- **配置文件示例:**
123
- ```json
124
- {
125
- "schemaVersion": 1,
126
- "neverOverwrite": [
127
- "src/**",
128
- "assets/**"
129
- ],
130
- "conflictCheck": [
131
- "package.json"
132
- ],
133
- "defaultOverwrite": true
134
- }
135
- ```
115
+ ### 2️⃣ 默认覆盖策略(简化流程)⭐
116
+
117
+ **核心原则:默认直接覆盖,无需用户选择**
118
+
119
+ CLI 现在默认使用 `overwrite` 模式,会自动:
120
+ - 覆盖所有需要更新的文件
121
+ - **自动备份配置文件**(如 package.json)
122
+ - 自动合并旧配置中的自定义内容
123
+ - 保护用户的业务文件(src/**、assets/**)
136
124
 
137
- **冲突文件分类:**
138
- - `conflictCheck`:需要检查的文件(如配置文件)
139
- - 当前配置:`package.json`
140
- - 未来可能添加:`vite.config.ts`、`tsconfig.json` 等
141
- - `neverOverwrite`:永不覆盖的文件(如用户数据)
142
- - 当前配置:`src/**`、`assets/**`(用户业务代码和资源)
143
- - `defaultOverwrite`:默认行为(true = 其他文件默认覆盖)
144
-
145
- **你的工作流程:**
146
- 1. 执行 `npx -y axhub-make pre` 检查冲突
147
- 2. 如果有冲突,读取 `scaffold.update.json` 了解规则
148
- 3. 向用户解释哪些文件会冲突以及原因
149
- 4. 根据用户选择执行对应操作
150
-
151
- **用户场景映射:**
152
-
153
- | 用户说 | 你的行为 |
154
- |-------|---------|
155
- | "更新项目" | 先执行 `npx -y axhub-make pre` 检查冲突 |
156
- | "有冲突吗?" | 执行 `pre` 模式,解析 JSON 输出 |
157
- | "保留我的修改" / "保留" | 使用 `--conflict keep`(默认) |
158
- | "用新版本覆盖" / "覆盖" | 使用 `--conflict overwrite` + 确认 |
159
- | "智能合并" / "帮我合并" | 进入智能合并流程 |
160
- | "我想看看冲突文件" | 执行 `pre`,列出 `conflicts` 数组 |
161
- | "为什么这个文件会冲突?" | 查看 `scaffold.update.json`,解释规则 |
162
-
163
- ### 3️⃣ 智能冲突处理流程
125
+ **文件处理规则:**
126
+ - 脚手架文件:自动更新
127
+ - 配置文件:备份后更新,保留自定义内容
128
+ - 用户业务文件:永不覆盖
129
+
130
+ ### 3️⃣ 简化的更新流程
164
131
 
165
132
  ```
166
133
  用户:"更新项目"
167
134
 
168
- 执行:npx -y axhub-make pre
169
-
170
- 解析输出:{ mode: "update", conflicts: [...] }
135
+ 你:直接执行更新
136
+ npx -y axhub-make
171
137
 
172
- 如果 conflicts.length > 0:
173
- 列出冲突文件
174
- 询问:"这些文件有本地修改,请选择:
175
- 1️⃣ 保留 - 保留本地版本
176
- 2️⃣ 覆盖 - 使用模板版本
177
- 3️⃣ 智能合并 - AI 帮你合并(推荐)"
178
- → 根据回答执行:
179
- • 保留 → npx -y axhub-make --conflict keep
180
- • 覆盖 → npx -y axhub-make --conflict overwrite
181
- • 智能合并 → 智能合并流程
138
+ CLI 自动完成:
139
+ 1. 下载最新模板
140
+ 2. 备份配置文件
141
+ 3. 更新项目文件
142
+ 4. 安装依赖
182
143
 
183
- 如果 conflicts.length === 0:
184
- 直接执行:npx -y axhub-make
144
+ 更新完成后,提示用户:
145
+ "✅ 更新完成!已自动备份配置文件。
146
+
147
+ 请测试项目功能,有问题随时告诉我。"
148
+ ```
149
+
150
+ ### 4️⃣ 问题处理流程
151
+
152
+ **当用户反馈问题时:**
153
+
154
+ ```
155
+ 用户:"更新后启动报错了"
156
+ 你:
157
+ 1. 查看错误信息
158
+ 2. 检查备份文件,对比差异
159
+ 3. 分析问题原因
160
+ 4. 提供解决方案或恢复备份
185
161
  ```
186
162
 
187
163
  ---
188
164
 
189
165
  ## 🗣️ 对话策略
190
166
 
191
- ### 原则:猜参数,不问参数
167
+ ### 原则:直接执行,减少询问
192
168
 
193
169
  **好的示例:**
194
170
  > 用户:"更新一下项目"
195
- > 你:"好的,我先检查是否有冲突文件..."
196
- > 执行:`npx -y axhub-make pre`
171
+ > 你:"好的,开始更新项目..."
172
+ > 执行:`npx -y axhub-make`
173
+ > 完成后:"✅ 更新完成!package.json 已自动备份。请测试项目功能,有问题随时告诉我。"
197
174
 
198
175
  **不好的示例:**
199
- > 你:"你要用 npm 还是 pnpm?要不要安装依赖?"
176
+ > 你:"你要保留本地文件还是覆盖?要用 npm 还是 pnpm"
200
177
  > ❌ 过度询问
201
178
 
202
179
  ### 必须提问的场景
203
180
 
204
181
  **仅在以下情况提问:**
205
182
  1. 当前目录非空 + 用户要在当前目录初始化
206
- 2. 检测到冲突文件(更新模式)
207
- 3. 用户明确要求覆盖/重装
183
+ 2. 用户明确要求覆盖/重装时需要确认风险
208
184
 
209
- **其他情况:使用安全默认值**
185
+ **其他情况:直接执行,使用默认值**
210
186
 
211
187
  ---
212
188
 
@@ -215,136 +191,45 @@ CLI 会检测目标目录是否包含:
215
191
  ### 场景 1:初始化新项目
216
192
  ```
217
193
  用户:"创建一个新项目"
218
- 你:npx -y axhub-make my-project --install --start
194
+ 你:npx -y axhub-make my-project
219
195
  ```
220
196
 
221
- ### 场景 2:更新现有项目(无冲突)
197
+ ### 场景 2:更新现有项目
222
198
  ```
223
199
  用户:"更新项目"
224
200
  你:
225
- 1. npx -y axhub-make pre
226
- 2. 解析输出 → conflicts: []
227
- 3. npx -y axhub-make --install
228
- ```
229
-
230
- ### 场景 3:更新项目(有冲突)
231
- ```
232
- 用户:"更新项目"
233
- 你:
234
- 1. npx -y axhub-make pre
235
-
236
- 2. 解析输出 → conflicts: ["package.json"], templateDir: "/tmp/axhub-make-xxx"
237
-
238
- 3. 读取 scaffold.update.json 了解冲突规则
201
+ 1. npx -y axhub-make
239
202
 
240
- 4. 提示:"检测到 1 个冲突文件:
241
-
242
- 📦 package.json
243
- 原因:此文件在 conflictCheck 列表中,可能包含你的自定义依赖
244
-
245
- ℹ️ 以下文件会被保护,不会覆盖:
246
- • src/** - 你的业务代码
247
- • assets/** - 你的资源文件
248
-
249
- 请选择处理方式:
250
-
251
- 1️⃣ 保留 - 保留你的本地版本(推荐,安全)
252
- 2️⃣ 覆盖 - 使用模板的新版本(会丢失本地修改)
253
- 3️⃣ 智能合并 - 让我帮你合并两个版本的内容(推荐)
254
-
255
- 请回复:保留 / 覆盖 / 智能合并"
256
-
257
- 5. 根据回答执行:
258
- • 保留 → npx -y axhub-make --conflict keep
259
- • 覆盖 → npx -y axhub-make --conflict overwrite
260
- • 智能合并 → 进入智能合并流程(见场景 7)
203
+ 2. 更新完成后提示:
204
+ "✅ 更新完成!已自动备份配置文件。
205
+ 请测试项目功能,有问题随时告诉我。"
261
206
  ```
262
207
 
263
- ### 场景 4:用户主动要求覆盖
208
+ ### 场景 3:更新后出现问题
264
209
  ```
265
- 用户:"更新项目,直接覆盖所有文件"
210
+ 用户:"更新后启动报错了"
266
211
  你:
267
- 1. 确认:"这将覆盖所有冲突文件,可能丢失本地修改。确认吗?"
268
- 2. 用户确认后:
269
- npx -y axhub-make --conflict overwrite
212
+ 1. "让我看看错误信息..."
213
+ 2. 检查备份文件,对比差异
214
+ 3. 分析问题并提供解决方案
215
+ 4. 如需要可以恢复备份
270
216
  ```
271
217
 
272
- ### 场景 5:仅检查冲突
218
+ ### 场景 4:恢复备份
273
219
  ```
274
- 用户:"看看更新会影响哪些文件"
220
+ 用户:"还是用回旧版本的配置吧"
275
221
  你:
276
- 1. npx -y axhub-make pre
277
-
278
- 2. 读取 scaffold.update.json 了解规则
279
-
280
- 3. 友好展示结果:
281
- "📋 更新影响分析:
282
-
283
- ✅ 会自动更新的文件:
284
- • vite.config.ts
285
- • tsconfig.json
286
- • 其他脚手架文件
287
-
288
- ⚠️ 需要确认的冲突文件:
289
- • package.json(在 conflictCheck 列表中)
290
-
291
- 🔒 永不覆盖的文件:
292
- • src/** - 你的业务代码
293
- • assets/** - 你的资源文件
294
-
295
- 当前冲突文件:package.json
296
- 建议选择智能合并来保留你的自定义配置。"
222
+ 1. 找到备份文件(.backup.{timestamp})
223
+ 2. 恢复备份并重新安装依赖
224
+ 3. "已恢复旧版本配置。现在试试看?"
297
225
  ```
298
226
 
299
- ### 场景 6:重新安装依赖
227
+ ### 场景 5:重新安装依赖
300
228
  ```
301
229
  用户:"依赖坏了,重装一下"
302
230
  你:npx -y axhub-make --no-start
303
- (更新模式会重新复制文件并安装依赖)
304
231
  ```
305
232
 
306
- ### 场景 7:智能合并冲突文件 ⭐
307
- ```
308
- 用户选择:"智能合并"
309
- 你:
310
- 1. 先执行 keep 模式保护用户数据:
311
- npx -y axhub-make --conflict keep
312
-
313
- 2. 对每个冲突文件执行智能合并:
314
- a. 读取本地文件内容(用户版本)
315
- b. 从模板仓库获取新版本内容
316
- c. 分析两个版本的差异
317
- d. 智能合并:
318
- - 保留用户的自定义配置
319
- - 合并模板的新功能和修复
320
- - 处理冲突部分(优先保留用户修改)
321
- e. 展示合并结果,询问确认
322
- f. 用户确认后写入文件
323
-
324
- 3. 示例对话:
325
- "正在合并 vite.config.ts...
326
-
327
- 发现以下差异:
328
- • 你添加了自定义插件:customPlugin()
329
- • 模板更新了构建配置:build.target
330
-
331
- 合并策略:
332
- ✓ 保留你的 customPlugin
333
- ✓ 应用模板的 build.target 更新
334
-
335
- 合并后的内容:
336
- [显示合并后的代码]
337
-
338
- 确认应用这个合并吗?(是/否/手动编辑)"
339
- ```
340
-
341
- **智能合并的核心原则:**
342
- - 用户的业务逻辑 > 模板的默认配置
343
- - 模板的新功能和修复 > 旧版本代码
344
- - 有疑问时询问用户,不要擅自决定
345
- - 提供清晰的 diff 展示
346
- - 支持用户手动调整合并结果
347
-
348
233
  ---
349
234
 
350
235
  ## 🛡️ 风险确认机制
@@ -353,27 +238,19 @@ CLI 会检测目标目录是否包含:
353
238
 
354
239
  **1. 非空目录初始化**
355
240
  ```
356
- 检测到当前目录非空,包含以下文件:
357
- - README.md
358
- - package.json
359
-
360
- 继续将覆盖这些文件。确认吗?(yes/no)
241
+ 检测到当前目录非空,继续将覆盖现有文件。确认吗?(yes/no)
361
242
  ```
362
243
 
363
- **2. 覆盖冲突文件**
244
+ **2. 强制模式**
364
245
  ```
365
- 以下文件将被覆盖:
366
- - vite.config.ts (你的自定义配置)
367
- - entries.json (你的页面配置)
368
-
369
- 确认覆盖吗?(yes/no)
246
+ --force 将跳过所有安全检查。确认吗?(yes/no)
370
247
  ```
371
248
 
372
- **3. 强制模式**
373
- ```
374
- --force 将跳过所有安全检查。
375
- 仅在你明确知道后果时使用。确认吗?(yes/no)
376
- ```
249
+ **注意**:更新模式不再需要确认,因为:
250
+ - 默认自动覆盖,提升效率
251
+ - 配置文件自动备份,保证安全
252
+ - 用户业务文件受保护,不会被覆盖
253
+ - 出现问题时可以通过备份恢复
377
254
 
378
255
  ---
379
256
 
@@ -415,65 +292,26 @@ CLI 会检测目标目录是否包含:
415
292
 
416
293
  ## 📊 输出解析
417
294
 
418
- ### pre 模式输出格式
419
- ```json
420
- {
421
- "mode": "update",
422
- "conflictMode": "keep",
423
- "conflicts": [
424
- "vite.config.ts",
425
- "package.json"
426
- ]
427
- }
428
- ```
295
+ ### 更新完成后的输出
296
+ CLI 会输出更新摘要,包括:
297
+ - 已更新文件数量
298
+ - 已覆盖文件列表
299
+ - 跳过文件列表(受保护的用户文件)
300
+ - 备份文件路径
429
301
 
430
302
  ### 你的处理逻辑
431
- ```javascript
432
- if (output.mode === "update" && output.conflicts.length > 0) {
433
- // 1. 展示冲突文件
434
- // 2. 询问用户意图
435
- // 3. 如果选择智能合并:
436
- // - 使用 templateDir 读取模板文件
437
- // - 对比本地文件和模板文件
438
- // - 执行智能合并
439
- // 4. 否则执行带 --conflict 参数的命令
440
- } else {
441
- // 直接执行更新
442
- }
443
303
  ```
304
+ 更新完成后:
305
+ 1. 确认备份文件已创建
306
+ 2. 提示用户测试项目
307
+ 3. 告知如有问题可协助处理
308
+ 4. 等待用户反馈
444
309
 
445
- ### 临时目录使用场景
446
-
447
- **场景 1:pre 命令执行失败**
448
- ```
449
- 用户:"更新项目"
450
- 你:执行 npx -y axhub-make pre
451
-
452
- 命令失败或输出解析失败
453
-
454
- 你:尝试手动读取临时目录
455
- 1. 查找最新的 /tmp/axhub-make-* 目录
456
- 2. 读取 scaffold.update.json 获取冲突规则
457
- 3. 手动对比文件差异
458
- 4. 继续智能合并流程
459
- ```
460
-
461
- **场景 2:智能合并时读取模板文件**
462
- ```
463
- 用户选择:"智能合并"
464
- 你:
465
- 1. 从 pre 输出获取 templateDir
466
-
467
- 2. 读取 scaffold.update.json 了解冲突规则
468
-
469
- 3. 对每个冲突文件(当前只有 package.json):
470
- - 读取本地文件:{projectDir}/package.json
471
- - 读取模板文件:{templateDir}/package.json
472
- - 对比差异并智能合并
473
-
474
- 4. 合并完成后,临时目录会在下次更新时自动清理
475
-
476
- 5. 如果未来 conflictCheck 添加了新文件,重复步骤 3
310
+ 如果用户报告问题:
311
+ 1. 读取备份文件
312
+ 2. 对比新旧版本
313
+ 3. 分析问题原因
314
+ 4. 提供解决方案或恢复备份
477
315
  ```
478
316
 
479
317
  ---
@@ -481,24 +319,23 @@ if (output.mode === "update" && output.conflicts.length > 0) {
481
319
  ## ✅ 成功标准
482
320
 
483
321
  **你的任务完成标志:**
484
- 1. 成功执行了正确的 `npx -y axhub-make ...` 命令
485
- 2. 用户的目标达成(项目初始化/更新完成)
486
- 3. 冲突文件按用户意图处理
487
- 4. 没有数据丢失或意外覆盖
322
+ 1. 成功执行 CLI 命令
323
+ 2. 项目初始化/更新完成
324
+ 3. 提示用户测试功能
325
+ 4. 告知备份位置,以便出现问题时恢复
326
+ 5. 准备好协助处理更新后的问题
488
327
 
489
328
  ---
490
329
 
491
330
  ## 🎯 关键要点总结
492
331
 
493
- 1. **自动源选择**:CLI 自动检测并选择可访问的仓库(GitHub/Gitee)
494
- 2. **更新前先检查**:使用 `pre` 模式预检查冲突
495
- 3. **查看冲突规则**:读取 `scaffold.update.json` 了解配置
496
- 4. **默认安全策略**:`--conflict keep` 保护用户数据
497
- 5. **明确风险提示**:覆盖操作必须确认
498
- 6. **一条命令原则**:所有操作最终落到一条 CLI 命令
499
- 7. **环境优先检查**:Node/Git 缺失时先协助安装
500
- 8. **智能猜测意图**:减少不必要的询问
501
- 9. **友好展示结果**:解析 JSON 输出为人类可读格式
502
- 10. **智能合并优先**:有冲突时推荐用户选择智能合并
503
- 11. **利用临时目录**:pre 模式保留 templateDir,方便智能对比
504
- 12. **中文交互**:所有用户提示和选项使用中文
332
+ 1. **自动源选择**:CLI 自动选择可访问的仓库(GitHub/Gitee)
333
+ 2. **默认覆盖**:直接覆盖更新,无需用户选择
334
+ 3. **自动备份**:配置文件自动备份
335
+ 4. **内容合并**:自动合并用户的自定义配置
336
+ 5. **用户文件保护**:业务文件永不覆盖
337
+ 6. **测试提示**:更新后提示用户测试
338
+ 7. **问题协助**:利用备份文件协助解决问题
339
+ 8. **一条命令**:所有操作落到一条 CLI 命令
340
+ 9. **减少询问**:直接执行,只在必要时确认
341
+ 10. **简单语言**:用非技术人员能理解的语言交流
package/bin/index.js CHANGED
@@ -45,9 +45,7 @@ function matchesAny(relPath, patterns) {
45
45
  return patterns.some((pattern) => globToRegExp(pattern).test(p));
46
46
  }
47
47
 
48
- function isValidConflictMode(v) {
49
- return v === 'keep' || v === 'overwrite';
50
- }
48
+ // 移除 conflict mode 验证函数,业务上强制使用 overwrite
51
49
 
52
50
  async function readUpdateRules(tmpDir) {
53
51
  const rulesPath = path.join(tmpDir, 'scaffold.update.json');
@@ -55,6 +53,7 @@ async function readUpdateRules(tmpDir) {
55
53
  return {
56
54
  schemaVersion: 1,
57
55
  neverOverwrite: [],
56
+ alwaysOverwrite: [],
58
57
  conflictCheck: [],
59
58
  defaultOverwrite: true
60
59
  };
@@ -63,6 +62,7 @@ async function readUpdateRules(tmpDir) {
63
62
  return {
64
63
  schemaVersion: typeof rules.schemaVersion === 'number' ? rules.schemaVersion : 1,
65
64
  neverOverwrite: Array.isArray(rules.neverOverwrite) ? rules.neverOverwrite : [],
65
+ alwaysOverwrite: Array.isArray(rules.alwaysOverwrite) ? rules.alwaysOverwrite : [],
66
66
  conflictCheck: Array.isArray(rules.conflictCheck) ? rules.conflictCheck : [],
67
67
  defaultOverwrite: typeof rules.defaultOverwrite === 'boolean' ? rules.defaultOverwrite : true
68
68
  };
@@ -138,6 +138,12 @@ async function planUpdateFromTemplate(tmpDir, targetDir, rules, conflictMode) {
138
138
  continue;
139
139
  }
140
140
 
141
+ // alwaysOverwrite 优先级最高,即使匹配 neverOverwrite 也要覆盖
142
+ if (matchesAny(relPosix, rules.alwaysOverwrite)) {
143
+ copied.push(relPosix);
144
+ continue;
145
+ }
146
+
141
147
  if (matchesAny(relPosix, rules.neverOverwrite) && destExists) {
142
148
  skipped.push(relPosix);
143
149
  continue;
@@ -154,6 +160,42 @@ async function planUpdateFromTemplate(tmpDir, targetDir, rules, conflictMode) {
154
160
  return { copied, skipped, conflicts, wouldOverwriteConflicts };
155
161
  }
156
162
 
163
+ async function mergePackageJson(oldPkgPath, newPkgPath, targetDir) {
164
+ const oldPkg = await fs.readJson(oldPkgPath);
165
+ const newPkg = await fs.readJson(newPkgPath);
166
+
167
+ // 备份旧版本
168
+ const backupPath = path.join(targetDir, `package.json.backup.${Date.now()}`);
169
+ await fs.copyFile(oldPkgPath, backupPath);
170
+ console.log(chalk.yellow(`📦 已备份旧版本 package.json 到: ${path.basename(backupPath)}`));
171
+
172
+ // 合并依赖:将旧版本独有的依赖添加到新版本
173
+ const depTypes = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
174
+ let addedCount = 0;
175
+
176
+ for (const depType of depTypes) {
177
+ if (oldPkg[depType]) {
178
+ if (!newPkg[depType]) {
179
+ newPkg[depType] = {};
180
+ }
181
+
182
+ for (const [pkg, version] of Object.entries(oldPkg[depType])) {
183
+ if (!newPkg[depType][pkg]) {
184
+ newPkg[depType][pkg] = version;
185
+ addedCount++;
186
+ console.log(chalk.green(` ✓ 添加依赖: ${pkg}@${version} (${depType})`));
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ if (addedCount > 0) {
193
+ console.log(chalk.green(`📦 已从旧版本合并 ${addedCount} 个依赖到新版本`));
194
+ }
195
+
196
+ return newPkg;
197
+ }
198
+
157
199
  async function applyUpdateFromTemplate(tmpDir, targetDir, rules, conflictMode) {
158
200
  const templateFiles = await listFilesRecursive(tmpDir);
159
201
  const copied = [];
@@ -169,30 +211,64 @@ async function applyUpdateFromTemplate(tmpDir, targetDir, rules, conflictMode) {
169
211
  const destPath = path.join(targetDir, relPath);
170
212
  const destExists = await fs.pathExists(destPath);
171
213
 
172
- if (matchesAny(relPosix, rules.conflictCheck) && destExists) {
214
+ // 特殊处理 package.json:备份 + 合并依赖
215
+ if (relPosix === 'package.json' && destExists) {
173
216
  const same = await filesEqual(destPath, srcPath);
174
217
  if (!same) {
175
218
  conflicts.push(relPosix);
176
- if (conflictMode !== 'overwrite') continue;
177
- await fs.ensureDir(path.dirname(destPath));
178
- await fs.copyFile(srcPath, destPath);
219
+ // package.json 始终使用新版本,但合并旧版本的依赖
220
+ const mergedPkg = await mergePackageJson(destPath, srcPath, targetDir);
221
+ await fs.writeJson(destPath, mergedPkg, { spaces: 2 });
179
222
  overwrittenConflicts.push(relPosix);
180
223
  copied.push(relPosix);
181
224
  continue;
182
225
  }
226
+ // 文件内容相同,无需操作
227
+ continue;
228
+ }
229
+
230
+ // 优先级 1: conflictCheck - 检测冲突文件
231
+ if (matchesAny(relPosix, rules.conflictCheck) && destExists) {
232
+ const same = await filesEqual(destPath, srcPath);
233
+ if (!same) {
234
+ conflicts.push(relPosix);
235
+ // 只有在 overwrite 模式下才覆盖冲突文件
236
+ if (conflictMode === 'overwrite') {
237
+ await fs.ensureDir(path.dirname(destPath));
238
+ await fs.copyFile(srcPath, destPath);
239
+ overwrittenConflicts.push(relPosix);
240
+ copied.push(relPosix);
241
+ } else {
242
+ // keep 模式:跳过冲突文件,保留本地版本
243
+ skipped.push(relPosix);
244
+ }
245
+ continue;
246
+ }
247
+ // 文件内容相同,无需操作
183
248
  continue;
184
249
  }
185
250
 
251
+ // 优先级 2: alwaysOverwrite - 强制覆盖(最高优先级)
252
+ if (matchesAny(relPosix, rules.alwaysOverwrite)) {
253
+ await fs.ensureDir(path.dirname(destPath));
254
+ await fs.copyFile(srcPath, destPath);
255
+ copied.push(relPosix);
256
+ continue;
257
+ }
258
+
259
+ // 优先级 3: neverOverwrite - 永不覆盖
186
260
  if (matchesAny(relPosix, rules.neverOverwrite) && destExists) {
187
261
  skipped.push(relPosix);
188
262
  continue;
189
263
  }
190
264
 
265
+ // 优先级 4: defaultOverwrite 规则
191
266
  if (destExists && rules.defaultOverwrite === false) {
192
267
  skipped.push(relPosix);
193
268
  continue;
194
269
  }
195
270
 
271
+ // 默认行为:复制文件(新文件或允许覆盖的文件)
196
272
  await fs.ensureDir(path.dirname(destPath));
197
273
  await fs.copyFile(srcPath, destPath);
198
274
  copied.push(relPosix);
@@ -252,7 +328,7 @@ function parseArgs(argv) {
252
328
  start: true,
253
329
  force: false,
254
330
  pm: null,
255
- conflict: 'keep'
331
+ conflict: 'overwrite' // 默认强制覆盖,用户可通过 --conflict keep 改为保留模式(不对外文档化)
256
332
  };
257
333
 
258
334
  for (let i = 0; i < args.length; i++) {
@@ -278,10 +354,7 @@ function parseArgs(argv) {
278
354
  if (a === '--pre') opts.pre = true;
279
355
  if (a === '--force') opts.force = true;
280
356
  if (a === '--pm') opts.pm = args[++i];
281
- if (a === '--conflict') {
282
- const v = args[++i];
283
- if (isValidConflictMode(v)) opts.conflict = v;
284
- }
357
+ // 移除 --conflict 参数,不对外暴露
285
358
  }
286
359
 
287
360
  return opts;
@@ -344,7 +417,7 @@ async function run() {
344
417
  await fs.remove(path.join(tmpDir, '.git'));
345
418
 
346
419
  const rules = await readUpdateRules(tmpDir);
347
- const conflictMode = isValidConflictMode(opts.conflict) ? opts.conflict : 'keep';
420
+ const conflictMode = isValidConflictMode(opts.conflict) ? opts.conflict : 'overwrite';
348
421
 
349
422
  if (opts.pre) {
350
423
  const plan = await planUpdateFromTemplate(tmpDir, targetDir, rules, conflictMode);
@@ -360,19 +433,29 @@ async function run() {
360
433
  const result = await applyUpdateFromTemplate(tmpDir, targetDir, rules, conflictMode);
361
434
  await fs.remove(tmpDir);
362
435
 
363
- if (result.conflicts.length > 0 && conflictMode !== 'overwrite') {
364
- console.log(chalk.yellow('\n⚠️ 发现冲突文件(已保留本地文件):'));
365
- result.conflicts.forEach((p) => console.log(chalk.yellow(`- ${p}`)));
366
- }
367
- if (result.overwrittenConflicts.length > 0 && conflictMode === 'overwrite') {
368
- console.log(chalk.yellow('\n⚠️ 发现冲突文件(已按配置覆盖):'));
369
- result.overwrittenConflicts.forEach((p) => console.log(chalk.yellow(`- ${p}`)));
436
+ // 输出更新结果摘要
437
+ console.log(chalk.cyan('\n📊 更新摘要:'));
438
+ console.log(chalk.green(`✓ 已更新文件:${result.copied.length} 个`));
439
+
440
+ if (result.conflicts.length > 0) {
441
+ if (conflictMode === 'keep') {
442
+ console.log(chalk.yellow(`⚠ 冲突文件(已保留本地):${result.conflicts.length} 个`));
443
+ result.conflicts.forEach((p) => console.log(chalk.yellow(` - ${p}`)));
444
+ } else {
445
+ console.log(chalk.red(`⚠ 冲突文件(已覆盖):${result.overwrittenConflicts.length} 个`));
446
+ result.overwrittenConflicts.forEach((p) => console.log(chalk.red(` - ${p}`)));
447
+ }
370
448
  }
449
+
371
450
  if (result.skipped.length > 0) {
372
- console.log(chalk.gray('\n⏭️ 跳过覆盖(已保留本地文件):'));
373
- result.skipped.forEach((p) => console.log(chalk.gray(`- ${p}`)));
451
+ console.log(chalk.gray(`⏭ 跳过文件(保留本地):${result.skipped.length} 个`));
452
+ if (result.skipped.length <= 10) {
453
+ result.skipped.forEach((p) => console.log(chalk.gray(` - ${p}`)));
454
+ } else {
455
+ result.skipped.slice(0, 5).forEach((p) => console.log(chalk.gray(` - ${p}`)));
456
+ console.log(chalk.gray(` ... 还有 ${result.skipped.length - 5} 个文件`));
457
+ }
374
458
  }
375
- console.log(chalk.green(`\n✅ 已写入/更新文件:${result.copied.length} 个`));
376
459
 
377
460
  // -----------------------------
378
461
  // package.json 处理
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axhub-make",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Axhub Make scaffolding tool",
5
5
  "bin": {
6
6
  "axhub-make": "./bin/index.js"
@@ -10,13 +10,18 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "chalk": "^4.1.2",
13
+ "class-variance-authority": "^0.7.1",
14
+ "clsx": "^2.1.1",
13
15
  "download-git-repo": "^3.0.2",
14
16
  "fs-extra": "^10.0.0",
15
- "inquirer": "^8.2.0"
17
+ "inquirer": "^8.2.0",
18
+ "tailwind-merge": "^3.4.0"
16
19
  },
17
20
  "author": "Lintendo",
18
21
  "license": "ISC",
19
22
  "devDependencies": {
20
- "execa": "^9.6.1"
23
+ "@tailwindcss/vite": "^4.1.18",
24
+ "execa": "^9.6.1",
25
+ "tailwindcss": "^4.1.18"
21
26
  }
22
27
  }
package/TEST.md DELETED
@@ -1,76 +0,0 @@
1
- # 测试指南
2
-
3
- ## 快速测试
4
-
5
- ```bash
6
- npm test
7
- # 或
8
- node test.js
9
- ```
10
-
11
- ## 测试覆盖
12
-
13
- 测试文件 `test.js` 覆盖以下功能:
14
-
15
- ### 1. 核心工具函数
16
- - ✅ `globToRegExp` - glob 模式转正则表达式
17
- - ✅ `matchesAny` - 文件路径匹配
18
- - ✅ `normalizeRelPath` - 路径标准化
19
-
20
- ### 2. 参数解析
21
- - ✅ 目录参数解析
22
- - ✅ 命令行选项(--no-install, --no-start, --pm, --conflict)
23
-
24
- ### 3. 文件操作
25
- - ✅ 临时目录创建和清理
26
- - ✅ 文件内容比较
27
-
28
- ## 手动测试
29
-
30
- ### 测试新项目创建
31
-
32
- ```bash
33
- # 在临时目录测试
34
- mkdir /tmp/test-axhub && cd /tmp/test-axhub
35
- npx axhub-make my-project --no-start
36
-
37
- # 验证项目结构
38
- ls -la my-project
39
- ```
40
-
41
- ### 测试项目更新
42
-
43
- ```bash
44
- # 在已有项目中测试
45
- cd existing-axhub-project
46
- npx axhub-make --no-start
47
-
48
- # 检查是否正确处理冲突文件
49
- ```
50
-
51
- ### 测试 GitHub 可访问性
52
-
53
- ```bash
54
- # 测试会自动检测 GitHub 连接并切换到 Gitee(如果需要)
55
- npx axhub-make test-project
56
- ```
57
-
58
- ### 测试冲突处理
59
-
60
- ```bash
61
- # 保持本地文件(默认)
62
- npx axhub-make --conflict keep
63
-
64
- # 覆盖冲突文件
65
- npx axhub-make --conflict overwrite
66
- ```
67
-
68
- ## 发布前检查清单
69
-
70
- - [ ] 运行 `npm test` 确保所有测试通过
71
- - [ ] 测试新项目创建
72
- - [ ] 测试项目更新
73
- - [ ] 测试 GitHub/Gitee 自动切换
74
- - [ ] 测试不同包管理器(npm, yarn, pnpm)
75
- - [ ] 检查 package.json 版本号
76
- - [ ] 更新 CHANGELOG