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