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.
- package/AI-ASSISTANT-GUIDE.md +105 -268
- package/bin/index.js +106 -23
- package/package.json +8 -3
- package/TEST.md +0 -76
package/AI-ASSISTANT-GUIDE.md
CHANGED
|
@@ -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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
解析输出:{ mode: "update", conflicts: [...] }
|
|
135
|
+
你:直接执行更新
|
|
136
|
+
npx -y axhub-make
|
|
171
137
|
↓
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
184
|
-
|
|
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
|
|
171
|
+
> 你:"好的,开始更新项目..."
|
|
172
|
+
> 执行:`npx -y axhub-make`
|
|
173
|
+
> 完成后:"✅ 更新完成!package.json 已自动备份。请测试项目功能,有问题随时告诉我。"
|
|
197
174
|
|
|
198
175
|
**不好的示例:**
|
|
199
|
-
> 你:"
|
|
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
|
|
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
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
### 场景
|
|
208
|
+
### 场景 3:更新后出现问题
|
|
264
209
|
```
|
|
265
|
-
用户:"
|
|
210
|
+
用户:"更新后启动报错了"
|
|
266
211
|
你:
|
|
267
|
-
1.
|
|
268
|
-
2.
|
|
269
|
-
|
|
212
|
+
1. "让我看看错误信息..."
|
|
213
|
+
2. 检查备份文件,对比差异
|
|
214
|
+
3. 分析问题并提供解决方案
|
|
215
|
+
4. 如需要可以恢复备份
|
|
270
216
|
```
|
|
271
217
|
|
|
272
|
-
### 场景
|
|
218
|
+
### 场景 4:恢复备份
|
|
273
219
|
```
|
|
274
|
-
用户:"
|
|
220
|
+
用户:"还是用回旧版本的配置吧"
|
|
275
221
|
你:
|
|
276
|
-
1.
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
### 场景
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
249
|
+
**注意**:更新模式不再需要确认,因为:
|
|
250
|
+
- 默认自动覆盖,提升效率
|
|
251
|
+
- 配置文件自动备份,保证安全
|
|
252
|
+
- 用户业务文件受保护,不会被覆盖
|
|
253
|
+
- 出现问题时可以通过备份恢复
|
|
377
254
|
|
|
378
255
|
---
|
|
379
256
|
|
|
@@ -415,65 +292,26 @@ CLI 会检测目标目录是否包含:
|
|
|
415
292
|
|
|
416
293
|
## 📊 输出解析
|
|
417
294
|
|
|
418
|
-
###
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
494
|
-
2.
|
|
495
|
-
3.
|
|
496
|
-
4.
|
|
497
|
-
5.
|
|
498
|
-
6.
|
|
499
|
-
7.
|
|
500
|
-
8.
|
|
501
|
-
9.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
await
|
|
178
|
-
await fs.
|
|
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
|
-
|
|
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 : '
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (result.
|
|
368
|
-
|
|
369
|
-
|
|
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(
|
|
373
|
-
result.skipped.
|
|
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.
|
|
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
|
-
"
|
|
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
|