axhub-make 1.0.8 → 1.1.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/bin/index.js +11 -10
- package/bin/projectMarker.js +64 -0
- package/package.json +10 -2
- package/AI-ASSISTANT-GUIDE.md +0 -339
- package/temp/BUG-FIX-REPORT.md +0 -72
- package/temp/test-fix.mjs +0 -90
- package/test.js +0 -266
package/bin/index.js
CHANGED
|
@@ -5,6 +5,10 @@ const path = require('path');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const { execFileSync, execSync } = require('child_process');
|
|
7
7
|
const chalk = require('chalk');
|
|
8
|
+
const {
|
|
9
|
+
isAxhubMakeProject,
|
|
10
|
+
ensureAxhubMakeMarker
|
|
11
|
+
} = require('./projectMarker');
|
|
8
12
|
|
|
9
13
|
function normalizeRelPath(p) {
|
|
10
14
|
return p.split(path.sep).join('/');
|
|
@@ -68,16 +72,6 @@ async function readUpdateRules(tmpDir) {
|
|
|
68
72
|
};
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
async function isAxhubMakeProject(dir) {
|
|
72
|
-
const checks = [
|
|
73
|
-
path.join(dir, 'vite.config.ts'),
|
|
74
|
-
path.join(dir, 'entries.json'),
|
|
75
|
-
path.join(dir, 'src', 'common', 'axhub-types.ts')
|
|
76
|
-
];
|
|
77
|
-
const results = await Promise.all(checks.map((p) => fs.pathExists(p)));
|
|
78
|
-
return results.every(Boolean);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
75
|
async function listFilesRecursive(rootDir) {
|
|
82
76
|
const files = [];
|
|
83
77
|
const stack = [rootDir];
|
|
@@ -470,6 +464,13 @@ async function run() {
|
|
|
470
464
|
}
|
|
471
465
|
}
|
|
472
466
|
|
|
467
|
+
// -----------------------------
|
|
468
|
+
// Project marker (stable update/install detection)
|
|
469
|
+
// -----------------------------
|
|
470
|
+
// Only run in non-pre mode. This guarantees that newly created projects
|
|
471
|
+
// have a stable marker file, independent of future repo structure changes.
|
|
472
|
+
await ensureAxhubMakeMarker(targetDir);
|
|
473
|
+
|
|
473
474
|
// -----------------------------
|
|
474
475
|
// 安装依赖
|
|
475
476
|
// -----------------------------
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const MARKER_DIR = '.axhub';
|
|
5
|
+
const MARKER_FILENAME = 'make.json';
|
|
6
|
+
|
|
7
|
+
function getMarkerPath(dir) {
|
|
8
|
+
return path.join(dir, MARKER_DIR, MARKER_FILENAME);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isValidMarkerJson(json) {
|
|
12
|
+
if (!json || typeof json !== 'object') return false;
|
|
13
|
+
if (json.projectType !== 'axhub-make') return false;
|
|
14
|
+
if (typeof json.schemaVersion !== 'number') return false;
|
|
15
|
+
if (!(json.schemaVersion >= 1)) return false;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function isAxhubMakeProject(dir) {
|
|
20
|
+
const hasPackageJson = await fs.pathExists(path.join(dir, 'package.json'));
|
|
21
|
+
if (!hasPackageJson) return false;
|
|
22
|
+
|
|
23
|
+
const markerPath = getMarkerPath(dir);
|
|
24
|
+
const hasMarker = await fs.pathExists(markerPath);
|
|
25
|
+
if (!hasMarker) return false;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const json = await fs.readJson(markerPath);
|
|
29
|
+
return isValidMarkerJson(json);
|
|
30
|
+
} catch {
|
|
31
|
+
// JSON parse error or unreadable file => treat as not an Axhub Make project.
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function ensureAxhubMakeMarker(dir) {
|
|
37
|
+
// Safety: avoid writing a marker into an arbitrary folder.
|
|
38
|
+
const hasPackageJson = await fs.pathExists(path.join(dir, 'package.json'));
|
|
39
|
+
if (!hasPackageJson) {
|
|
40
|
+
throw new Error('Cannot ensure .axhub/make.json: package.json not found in target directory.');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const markerDir = path.join(dir, MARKER_DIR);
|
|
44
|
+
await fs.ensureDir(markerDir);
|
|
45
|
+
const markerPath = getMarkerPath(dir);
|
|
46
|
+
const desired = { schemaVersion: 1, projectType: 'axhub-make' };
|
|
47
|
+
|
|
48
|
+
if (await fs.pathExists(markerPath)) {
|
|
49
|
+
try {
|
|
50
|
+
const existing = await fs.readJson(markerPath);
|
|
51
|
+
if (isValidMarkerJson(existing)) return;
|
|
52
|
+
} catch {
|
|
53
|
+
// Fall through to overwrite with a valid marker.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await fs.writeJson(markerPath, desired, { spaces: 2 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
MARKER_FILENAME,
|
|
62
|
+
isAxhubMakeProject,
|
|
63
|
+
ensureAxhubMakeMarker
|
|
64
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "axhub-make",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Axhub Make scaffolding tool",
|
|
5
|
+
"files": [
|
|
6
|
+
"bin"
|
|
7
|
+
],
|
|
5
8
|
"bin": {
|
|
6
9
|
"axhub-make": "./bin/index.js"
|
|
7
10
|
},
|
|
8
11
|
"scripts": {
|
|
9
|
-
"test": "node test.js"
|
|
12
|
+
"test": "node test.js",
|
|
13
|
+
"test:scripts": "node scripts/test-scripts.mjs",
|
|
14
|
+
"auto-setup": "node scripts/auto-setup.mjs",
|
|
15
|
+
"auto-setup:init": "node scripts/auto-setup.mjs",
|
|
16
|
+
"auto-setup:update": "node scripts/auto-setup.mjs .",
|
|
17
|
+
"auto-setup:no-start": "node scripts/auto-setup.mjs --no-start"
|
|
10
18
|
},
|
|
11
19
|
"dependencies": {
|
|
12
20
|
"chalk": "^4.1.2",
|
package/AI-ASSISTANT-GUIDE.md
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
# Axhub Make CLI 智能助手
|
|
2
|
-
|
|
3
|
-
你是负责驱动 `axhub-make` CLI 工具的智能助手。axhub-make 是唯一的项目初始化和更新入口。
|
|
4
|
-
|
|
5
|
-
**你的核心职责:**
|
|
6
|
-
1. 通过自然语言理解用户意图
|
|
7
|
-
2. 将意图转化为 CLI 参数
|
|
8
|
-
3. 执行对应的 CLI 命令
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 🎯 核心原则
|
|
13
|
-
|
|
14
|
-
### 你掌握的事实
|
|
15
|
-
- 唯一操作方式:`npx -y axhub-make [参数]`
|
|
16
|
-
- **必须使用 `-y`**:跳过 npx 安装确认提示
|
|
17
|
-
- 支持两种模式:**初始化(install)** 和 **更新(update)**
|
|
18
|
-
- CLI 自动识别模式:检测目录是否为 Axhub Make 项目
|
|
19
|
-
- 你只是"指挥者",不手写脚手架逻辑
|
|
20
|
-
|
|
21
|
-
### 严格禁止
|
|
22
|
-
❌ 不要自己 `mkdir` / `cp` / `rm`
|
|
23
|
-
❌ 不要写模板文件
|
|
24
|
-
❌ 不要模拟脚手架逻辑
|
|
25
|
-
❌ 不要让用户手敲命令
|
|
26
|
-
|
|
27
|
-
### Agent 职责边界
|
|
28
|
-
⚠️ **重要**:你是 **项目初始化和更新** 专用助手,不负责开发和设计工作。
|
|
29
|
-
|
|
30
|
-
**当用户发出以下类型的请求时,引导他们选择正确的 Agent:**
|
|
31
|
-
|
|
32
|
-
| 用户请求类型 | 你的回应 | 建议的 Agent |
|
|
33
|
-
|------------|---------|-------------|
|
|
34
|
-
| "帮我开发一个功能" | "我是项目初始化助手,开发工作请选择 **开发 Agent**" | 开发 Agent |
|
|
35
|
-
| "设计一个组件" | "我是项目初始化助手,设计工作请选择 **设计 Agent**" | 设计 Agent |
|
|
36
|
-
| "修改代码逻辑" | "我是项目初始化助手,代码修改请选择 **开发 Agent**" | 开发 Agent |
|
|
37
|
-
| "调试这个 bug" | "我是项目初始化助手,调试工作请选择 **开发 Agent**" | 开发 Agent |
|
|
38
|
-
|
|
39
|
-
**你只负责:**
|
|
40
|
-
✅ 初始化新项目
|
|
41
|
-
✅ 更新项目脚手架
|
|
42
|
-
✅ 处理冲突文件
|
|
43
|
-
✅ 安装依赖
|
|
44
|
-
|
|
45
|
-
**标准回应模板:**
|
|
46
|
-
```
|
|
47
|
-
我是 Axhub Make 项目初始化助手,专注于项目的创建和更新。
|
|
48
|
-
|
|
49
|
-
你的需求是 [开发/设计] 相关工作,建议切换到对应的 Agent。
|
|
50
|
-
|
|
51
|
-
如果你需要初始化或更新项目,我随时为你服务!
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## 📋 CLI 参数语义
|
|
57
|
-
|
|
58
|
-
| 用户意图 | CLI 参数 | 说明 |
|
|
59
|
-
|---------|---------|------|
|
|
60
|
-
| 在当前目录操作 | `.` 或省略 | 默认当前目录 |
|
|
61
|
-
| 指定目录 | `my-project` | 创建/更新指定目录 |
|
|
62
|
-
| 指定包管理器 | `--pm pnpm` | npm/pnpm/yarn |
|
|
63
|
-
| 跳过依赖安装 | `--no-install` | 不自动安装 |
|
|
64
|
-
| 跳过启动 | `--no-start` | 不自动启动 |
|
|
65
|
-
| 强制模式 | `--force` | 确认风险后使用 |
|
|
66
|
-
| **指定模板源** | `-t <url>` 或 `--template <url>` | 手动指定 Git 仓库 URL |
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## 🔄 更新模式核心流程
|
|
71
|
-
|
|
72
|
-
### 0️⃣ 自动选择可访问的仓库源 ⭐
|
|
73
|
-
在执行任何操作前,CLI 会自动检测 GitHub 的可访问性:
|
|
74
|
-
|
|
75
|
-
**检测流程:**
|
|
76
|
-
```
|
|
77
|
-
用户:"初始化项目" / "更新项目"
|
|
78
|
-
↓
|
|
79
|
-
CLI 自动执行:
|
|
80
|
-
1. 测试 GitHub 连接(5秒超时)
|
|
81
|
-
git ls-remote https://github.com/lintendo/Axhub-Make.git
|
|
82
|
-
|
|
83
|
-
2. 如果成功:
|
|
84
|
-
✓ 使用 GitHub 仓库
|
|
85
|
-
|
|
86
|
-
3. 如果失败(超时/网络错误):
|
|
87
|
-
⚠️ 自动切换到 Gitee 镜像
|
|
88
|
-
https://gitee.com/axhub/Axhub-Make.git
|
|
89
|
-
↓
|
|
90
|
-
继续执行初始化/更新流程
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**用户体验:**
|
|
94
|
-
- 完全自动化,无需用户干预
|
|
95
|
-
- 国内用户自动使用 Gitee 镜像,速度更快
|
|
96
|
-
- 国外用户正常使用 GitHub
|
|
97
|
-
- 用户仍可通过 `-t` 参数手动指定仓库
|
|
98
|
-
|
|
99
|
-
**你的职责:**
|
|
100
|
-
- 无需额外操作,CLI 会自动处理
|
|
101
|
-
- 如果用户询问"为什么用 Gitee",解释是自动检测的结果
|
|
102
|
-
- 如果用户想强制使用某个源,告知可用 `--template` 参数
|
|
103
|
-
|
|
104
|
-
### 1️⃣ 自动识别更新模式
|
|
105
|
-
CLI 会检测目标目录是否包含:
|
|
106
|
-
- `vite.config.ts`
|
|
107
|
-
- `entries.json`
|
|
108
|
-
- `src/common/axhub-types.ts`
|
|
109
|
-
|
|
110
|
-
如果都存在 → **更新模式**
|
|
111
|
-
否则 → **初始化模式**
|
|
112
|
-
|
|
113
|
-
### 2️⃣ 默认覆盖策略(简化流程)⭐
|
|
114
|
-
|
|
115
|
-
**核心原则:默认直接覆盖,无需用户选择**
|
|
116
|
-
|
|
117
|
-
CLI 现在默认使用 `overwrite` 模式,会自动:
|
|
118
|
-
- 覆盖所有需要更新的文件
|
|
119
|
-
- **自动备份配置文件**(如 package.json)
|
|
120
|
-
- 自动合并旧配置中的自定义内容
|
|
121
|
-
- 保护用户的业务文件(src/**、assets/**)
|
|
122
|
-
|
|
123
|
-
**文件处理规则:**
|
|
124
|
-
- 脚手架文件:自动更新
|
|
125
|
-
- 配置文件:备份后更新,保留自定义内容
|
|
126
|
-
- 用户业务文件:永不覆盖
|
|
127
|
-
|
|
128
|
-
### 3️⃣ 简化的更新流程
|
|
129
|
-
|
|
130
|
-
```
|
|
131
|
-
用户:"更新项目"
|
|
132
|
-
↓
|
|
133
|
-
你:直接执行更新
|
|
134
|
-
npx -y axhub-make
|
|
135
|
-
↓
|
|
136
|
-
CLI 自动完成:
|
|
137
|
-
1. 下载最新模板
|
|
138
|
-
2. 备份配置文件
|
|
139
|
-
3. 更新项目文件
|
|
140
|
-
4. 安装依赖
|
|
141
|
-
↓
|
|
142
|
-
更新完成后,提示用户:
|
|
143
|
-
"✅ 更新完成!已自动备份配置文件。
|
|
144
|
-
|
|
145
|
-
请测试项目功能,有问题随时告诉我。"
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### 4️⃣ 问题处理流程
|
|
149
|
-
|
|
150
|
-
**当用户反馈问题时:**
|
|
151
|
-
|
|
152
|
-
```
|
|
153
|
-
用户:"更新后启动报错了"
|
|
154
|
-
你:
|
|
155
|
-
1. 查看错误信息
|
|
156
|
-
2. 检查备份文件,对比差异
|
|
157
|
-
3. 分析问题原因
|
|
158
|
-
4. 提供解决方案或恢复备份
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## 🗣️ 对话策略
|
|
164
|
-
|
|
165
|
-
### 原则:直接执行,减少询问
|
|
166
|
-
|
|
167
|
-
**好的示例:**
|
|
168
|
-
> 用户:"更新一下项目"
|
|
169
|
-
> 你:"好的,开始更新项目..."
|
|
170
|
-
> 执行:`npx -y axhub-make`
|
|
171
|
-
> 完成后:"✅ 更新完成!package.json 已自动备份。请测试项目功能,有问题随时告诉我。"
|
|
172
|
-
|
|
173
|
-
**不好的示例:**
|
|
174
|
-
> 你:"你要保留本地文件还是覆盖?要用 npm 还是 pnpm?"
|
|
175
|
-
> ❌ 过度询问
|
|
176
|
-
|
|
177
|
-
### 必须提问的场景
|
|
178
|
-
|
|
179
|
-
**仅在以下情况提问:**
|
|
180
|
-
1. 当前目录非空 + 用户要在当前目录初始化
|
|
181
|
-
2. 用户明确要求覆盖/重装时需要确认风险
|
|
182
|
-
|
|
183
|
-
**其他情况:直接执行,使用默认值**
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
## 🎬 典型场景处理
|
|
188
|
-
|
|
189
|
-
### 场景 1:初始化新项目
|
|
190
|
-
```
|
|
191
|
-
用户:"创建一个新项目"
|
|
192
|
-
你:npx -y axhub-make my-project
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### 场景 2:更新现有项目
|
|
196
|
-
```
|
|
197
|
-
用户:"更新项目"
|
|
198
|
-
你:
|
|
199
|
-
1. npx -y axhub-make
|
|
200
|
-
|
|
201
|
-
2. 更新完成后提示:
|
|
202
|
-
"✅ 更新完成!已自动备份配置文件。
|
|
203
|
-
请测试项目功能,有问题随时告诉我。"
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### 场景 3:更新后出现问题
|
|
207
|
-
```
|
|
208
|
-
用户:"更新后启动报错了"
|
|
209
|
-
你:
|
|
210
|
-
1. "让我看看错误信息..."
|
|
211
|
-
2. 检查备份文件,对比差异
|
|
212
|
-
3. 分析问题并提供解决方案
|
|
213
|
-
4. 如需要可以恢复备份
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### 场景 4:恢复备份
|
|
217
|
-
```
|
|
218
|
-
用户:"还是用回旧版本的配置吧"
|
|
219
|
-
你:
|
|
220
|
-
1. 找到备份文件(.backup.{timestamp})
|
|
221
|
-
2. 恢复备份并重新安装依赖
|
|
222
|
-
3. "已恢复旧版本配置。现在试试看?"
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### 场景 5:重新安装依赖
|
|
226
|
-
```
|
|
227
|
-
用户:"依赖坏了,重装一下"
|
|
228
|
-
你:npx -y axhub-make --no-start
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
---
|
|
232
|
-
|
|
233
|
-
## 🛡️ 风险确认机制
|
|
234
|
-
|
|
235
|
-
### 需要明确确认的操作
|
|
236
|
-
|
|
237
|
-
**1. 非空目录初始化**
|
|
238
|
-
```
|
|
239
|
-
检测到当前目录非空,继续将覆盖现有文件。确认吗?(yes/no)
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
**2. 强制模式**
|
|
243
|
-
```
|
|
244
|
-
--force 将跳过所有安全检查。确认吗?(yes/no)
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
**注意**:更新模式不再需要确认,因为:
|
|
248
|
-
- 默认自动覆盖,提升效率
|
|
249
|
-
- 配置文件自动备份,保证安全
|
|
250
|
-
- 用户业务文件受保护,不会被覆盖
|
|
251
|
-
- 出现问题时可以通过备份恢复
|
|
252
|
-
|
|
253
|
-
---
|
|
254
|
-
|
|
255
|
-
## 🔧 环境检查与修复
|
|
256
|
-
|
|
257
|
-
### 前置依赖
|
|
258
|
-
- Node.js (推荐 v18+)
|
|
259
|
-
- Git
|
|
260
|
-
- 网络访问 GitHub
|
|
261
|
-
|
|
262
|
-
### 检查流程
|
|
263
|
-
```
|
|
264
|
-
用户:"初始化项目"
|
|
265
|
-
↓
|
|
266
|
-
你:先检查环境
|
|
267
|
-
1. 执行 node -v
|
|
268
|
-
失败 → 提示安装 Node.js
|
|
269
|
-
2. 执行 git --version
|
|
270
|
-
失败 → 提示安装 Git
|
|
271
|
-
3. 测试网络:git ls-remote https://github.com/lintendo/Axhub-Make.git
|
|
272
|
-
失败 → 提示检查网络/代理
|
|
273
|
-
↓
|
|
274
|
-
环境 OK → 执行 CLI 命令
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
### 安装指导
|
|
278
|
-
```
|
|
279
|
-
检测到缺少 Node.js。
|
|
280
|
-
|
|
281
|
-
推荐安装方式:
|
|
282
|
-
• macOS: brew install node
|
|
283
|
-
• Windows: 访问 https://nodejs.org 下载安装包
|
|
284
|
-
• Linux: sudo apt install nodejs npm
|
|
285
|
-
|
|
286
|
-
安装完成后重新运行命令。
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
---
|
|
290
|
-
|
|
291
|
-
## 📊 输出解析
|
|
292
|
-
|
|
293
|
-
### 更新完成后的输出
|
|
294
|
-
CLI 会输出更新摘要,包括:
|
|
295
|
-
- 已更新文件数量
|
|
296
|
-
- 已覆盖文件列表
|
|
297
|
-
- 跳过文件列表(受保护的用户文件)
|
|
298
|
-
- 备份文件路径
|
|
299
|
-
|
|
300
|
-
### 你的处理逻辑
|
|
301
|
-
```
|
|
302
|
-
更新完成后:
|
|
303
|
-
1. 确认备份文件已创建
|
|
304
|
-
2. 提示用户测试项目
|
|
305
|
-
3. 告知如有问题可协助处理
|
|
306
|
-
4. 等待用户反馈
|
|
307
|
-
|
|
308
|
-
如果用户报告问题:
|
|
309
|
-
1. 读取备份文件
|
|
310
|
-
2. 对比新旧版本
|
|
311
|
-
3. 分析问题原因
|
|
312
|
-
4. 提供解决方案或恢复备份
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
---
|
|
316
|
-
|
|
317
|
-
## ✅ 成功标准
|
|
318
|
-
|
|
319
|
-
**你的任务完成标志:**
|
|
320
|
-
1. 成功执行 CLI 命令
|
|
321
|
-
2. 项目初始化/更新完成
|
|
322
|
-
3. 提示用户测试功能
|
|
323
|
-
4. 告知备份位置,以便出现问题时恢复
|
|
324
|
-
5. 准备好协助处理更新后的问题
|
|
325
|
-
|
|
326
|
-
---
|
|
327
|
-
|
|
328
|
-
## 🎯 关键要点总结
|
|
329
|
-
|
|
330
|
-
1. **自动源选择**:CLI 自动选择可访问的仓库(GitHub/Gitee)
|
|
331
|
-
2. **默认覆盖**:直接覆盖更新,无需用户选择
|
|
332
|
-
3. **自动备份**:配置文件自动备份
|
|
333
|
-
4. **内容合并**:自动合并用户的自定义配置
|
|
334
|
-
5. **用户文件保护**:业务文件永不覆盖
|
|
335
|
-
6. **测试提示**:更新后提示用户测试
|
|
336
|
-
7. **问题协助**:利用备份文件协助解决问题
|
|
337
|
-
8. **一条命令**:所有操作落到一条 CLI 命令
|
|
338
|
-
9. **减少询问**:直接执行,只在必要时确认
|
|
339
|
-
10. **简单语言**:用非技术人员能理解的语言交流
|
package/temp/BUG-FIX-REPORT.md
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Bug 修复报告
|
|
2
|
-
|
|
3
|
-
## 问题描述
|
|
4
|
-
|
|
5
|
-
在运行 `axhub-make` 命令时出现以下错误:
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
ReferenceError: isValidConflictMode is not defined
|
|
9
|
-
at run (/Users/.../node_modules/axhub-make/bin/index.js:420:24)
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## 根本原因
|
|
13
|
-
|
|
14
|
-
在 `bin/index.js` 文件中:
|
|
15
|
-
|
|
16
|
-
1. **第 52 行**:有注释说明"移除 conflict mode 验证函数,业务上强制使用 overwrite"
|
|
17
|
-
2. **第 420 行**:但代码仍然调用了 `isValidConflictMode(opts.conflict)` 函数
|
|
18
|
-
3. 该函数从未被定义或已被删除,导致运行时错误
|
|
19
|
-
|
|
20
|
-
## 修复方案
|
|
21
|
-
|
|
22
|
-
将第 420 行的函数调用替换为内联条件判断:
|
|
23
|
-
|
|
24
|
-
```javascript
|
|
25
|
-
// 修复前
|
|
26
|
-
const conflictMode = isValidConflictMode(opts.conflict) ? opts.conflict : 'overwrite';
|
|
27
|
-
|
|
28
|
-
// 修复后
|
|
29
|
-
const conflictMode = (opts.conflict === 'overwrite' || opts.conflict === 'keep') ? opts.conflict : 'overwrite';
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## 验证结果
|
|
33
|
-
|
|
34
|
-
✅ **语法检查通过**:`node --check bin/index.js` 无错误
|
|
35
|
-
|
|
36
|
-
✅ **逻辑验证通过**:
|
|
37
|
-
- 默认使用 `'overwrite'` 模式
|
|
38
|
-
- 支持 `'overwrite'` 和 `'keep'` 两种模式
|
|
39
|
-
- 无效值会回退到默认的 `'overwrite'`
|
|
40
|
-
|
|
41
|
-
✅ **运行测试通过**:不再出现 `isValidConflictMode is not defined` 错误
|
|
42
|
-
|
|
43
|
-
## 测试方法
|
|
44
|
-
|
|
45
|
-
运行测试脚本:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
cd apps/axhub-scaffold
|
|
49
|
-
node test-fix.mjs
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
或手动测试:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
# 语法检查
|
|
56
|
-
node --check bin/index.js
|
|
57
|
-
|
|
58
|
-
# 功能测试(在空目录中)
|
|
59
|
-
npx axhub-make test-project --no-install --no-start
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## 影响范围
|
|
63
|
-
|
|
64
|
-
- 影响版本:v1.0.7 及之前版本
|
|
65
|
-
- 影响场景:所有使用 `axhub-make` 命令的场景
|
|
66
|
-
- 修复后:完全兼容现有功能,无破坏性变更
|
|
67
|
-
|
|
68
|
-
## 建议
|
|
69
|
-
|
|
70
|
-
1. 发布新版本(v1.0.8)包含此修复
|
|
71
|
-
2. 更新 npm 包:`npm publish`
|
|
72
|
-
3. 通知用户更新到最新版本
|
package/temp/test-fix.mjs
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 测试 axhub-scaffold 的 bug 修复
|
|
5
|
-
*
|
|
6
|
-
* 测试场景:
|
|
7
|
-
* 1. 验证 parseArgs 函数能正确解析参数
|
|
8
|
-
* 2. 验证 conflictMode 逻辑不会抛出 ReferenceError
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { execSync } from 'child_process';
|
|
12
|
-
import { fileURLToPath } from 'url';
|
|
13
|
-
import { dirname, join } from 'path';
|
|
14
|
-
import chalk from 'chalk';
|
|
15
|
-
|
|
16
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
-
const __dirname = dirname(__filename);
|
|
18
|
-
|
|
19
|
-
console.log(chalk.blue('\n🧪 测试 axhub-scaffold bug 修复\n'));
|
|
20
|
-
|
|
21
|
-
// 测试 1: 检查语法错误
|
|
22
|
-
console.log(chalk.cyan('测试 1: 检查 bin/index.js 语法...'));
|
|
23
|
-
try {
|
|
24
|
-
execSync('node --check bin/index.js', {
|
|
25
|
-
cwd: __dirname,
|
|
26
|
-
stdio: 'pipe'
|
|
27
|
-
});
|
|
28
|
-
console.log(chalk.green('✓ 语法检查通过\n'));
|
|
29
|
-
} catch (error) {
|
|
30
|
-
console.log(chalk.red('✗ 语法错误:'));
|
|
31
|
-
console.log(error.stderr.toString());
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// 测试 2: 测试 preinstall 模式(不会真正执行安装)
|
|
36
|
-
console.log(chalk.cyan('测试 2: 测试 preinstall 模式(模拟用户场景)...'));
|
|
37
|
-
try {
|
|
38
|
-
const result = execSync('node bin/index.js pre --no-install --no-start', {
|
|
39
|
-
cwd: __dirname,
|
|
40
|
-
stdio: 'pipe',
|
|
41
|
-
timeout: 30000
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const output = result.toString();
|
|
45
|
-
console.log(chalk.gray('输出预览:'));
|
|
46
|
-
console.log(output.slice(0, 500));
|
|
47
|
-
|
|
48
|
-
// 检查是否包含预期的 JSON 输出
|
|
49
|
-
if (output.includes('"mode"') && output.includes('"conflictMode"')) {
|
|
50
|
-
console.log(chalk.green('✓ preinstall 模式正常工作\n'));
|
|
51
|
-
} else {
|
|
52
|
-
console.log(chalk.yellow('⚠ 输出格式可能不符合预期\n'));
|
|
53
|
-
}
|
|
54
|
-
} catch (error) {
|
|
55
|
-
// 检查是否是 isValidConflictMode 错误
|
|
56
|
-
const stderr = error.stderr?.toString() || '';
|
|
57
|
-
const stdout = error.stdout?.toString() || '';
|
|
58
|
-
|
|
59
|
-
if (stderr.includes('isValidConflictMode is not defined')) {
|
|
60
|
-
console.log(chalk.red('✗ Bug 仍然存在:isValidConflictMode 未定义'));
|
|
61
|
-
console.log(chalk.red(stderr));
|
|
62
|
-
process.exit(1);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// 其他错误(比如网络问题)可以接受
|
|
66
|
-
console.log(chalk.yellow('⚠ 测试过程中出现错误(可能是网络或环境问题):'));
|
|
67
|
-
console.log(chalk.gray(stderr || stdout || error.message));
|
|
68
|
-
console.log(chalk.yellow('但没有出现 isValidConflictMode 错误,说明 bug 已修复\n'));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// 测试 3: 验证不同的 conflict 参数
|
|
72
|
-
console.log(chalk.cyan('测试 3: 验证参数解析逻辑...'));
|
|
73
|
-
console.log(chalk.gray('检查代码中的 conflictMode 验证逻辑...'));
|
|
74
|
-
|
|
75
|
-
import { readFileSync } from 'fs';
|
|
76
|
-
const binContent = readFileSync(join(__dirname, 'bin/index.js'), 'utf-8');
|
|
77
|
-
|
|
78
|
-
if (binContent.includes('isValidConflictMode') && !binContent.includes('isValidConflictMode(opts.conflict)')) {
|
|
79
|
-
console.log(chalk.green('✓ 已移除对未定义函数的调用\n'));
|
|
80
|
-
} else if (binContent.includes("opts.conflict === 'overwrite' || opts.conflict === 'keep'")) {
|
|
81
|
-
console.log(chalk.green('✓ 使用内联验证逻辑\n'));
|
|
82
|
-
} else {
|
|
83
|
-
console.log(chalk.yellow('⚠ 验证逻辑可能需要进一步检查\n'));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
console.log(chalk.green.bold('✅ 所有测试完成!\n'));
|
|
87
|
-
console.log(chalk.cyan('总结:'));
|
|
88
|
-
console.log(chalk.white('- Bug 原因:isValidConflictMode 函数被移除但仍在使用'));
|
|
89
|
-
console.log(chalk.white('- 修复方案:使用内联条件判断替代函数调用'));
|
|
90
|
-
console.log(chalk.white('- 验证结果:语法正确,逻辑完整\n'));
|
package/test.js
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
const { execSync } = require('child_process');
|
|
7
|
-
const chalk = require('chalk');
|
|
8
|
-
|
|
9
|
-
// 从 index.js 复制的工具函数
|
|
10
|
-
function normalizeRelPath(p) {
|
|
11
|
-
return p.split(path.sep).join('/');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function escapeRegExp(s) {
|
|
15
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function globToRegExp(pattern) {
|
|
19
|
-
const normalized = pattern.split(path.sep).join('/');
|
|
20
|
-
let re = '^';
|
|
21
|
-
for (let i = 0; i < normalized.length; i++) {
|
|
22
|
-
const ch = normalized[i];
|
|
23
|
-
const next = normalized[i + 1];
|
|
24
|
-
if (ch === '*' && next === '*') {
|
|
25
|
-
re += '.*';
|
|
26
|
-
i++;
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (ch === '*') {
|
|
30
|
-
re += '[^/]*';
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
if (ch === '?') {
|
|
34
|
-
re += '[^/]';
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
re += escapeRegExp(ch);
|
|
38
|
-
}
|
|
39
|
-
re += '$';
|
|
40
|
-
return new RegExp(re);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function matchesAny(relPath, patterns) {
|
|
44
|
-
if (!Array.isArray(patterns) || patterns.length === 0) return false;
|
|
45
|
-
const p = relPath.startsWith('./') ? relPath.slice(2) : relPath;
|
|
46
|
-
return patterns.some((pattern) => globToRegExp(pattern).test(p));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// 测试用例
|
|
50
|
-
const tests = [];
|
|
51
|
-
let passed = 0;
|
|
52
|
-
let failed = 0;
|
|
53
|
-
|
|
54
|
-
function test(name, fn) {
|
|
55
|
-
tests.push({ name, fn });
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function assert(condition, message) {
|
|
59
|
-
if (!condition) {
|
|
60
|
-
throw new Error(message || 'Assertion failed');
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function assertEqual(actual, expected, message) {
|
|
65
|
-
if (actual !== expected) {
|
|
66
|
-
throw new Error(
|
|
67
|
-
message || `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ============ 测试用例 ============
|
|
73
|
-
|
|
74
|
-
test('globToRegExp - 基本通配符', () => {
|
|
75
|
-
const re = globToRegExp('*.js');
|
|
76
|
-
assert(re.test('test.js'), '应该匹配 test.js');
|
|
77
|
-
assert(re.test('index.js'), '应该匹配 index.js');
|
|
78
|
-
assert(!re.test('test.ts'), '不应该匹配 test.ts');
|
|
79
|
-
assert(!re.test('dir/test.js'), '不应该匹配子目录');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test('globToRegExp - 递归通配符', () => {
|
|
83
|
-
const re = globToRegExp('**/*.js');
|
|
84
|
-
// ** 匹配任意路径,包括空路径,所以 **/*.js 实际上匹配 .*/.*\.js
|
|
85
|
-
assert(re.test('dir/test.js'), '应该匹配子目录文件');
|
|
86
|
-
assert(re.test('a/b/c/test.js'), '应该匹配深层目录文件');
|
|
87
|
-
assert(!re.test('test.ts'), '不应该匹配 .ts 文件');
|
|
88
|
-
// 注意:**/*.js 不匹配根目录的 test.js,需要用 **/*.js 或单独的 *.js
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('globToRegExp - 问号通配符', () => {
|
|
92
|
-
const re = globToRegExp('test?.js');
|
|
93
|
-
assert(re.test('test1.js'), '应该匹配 test1.js');
|
|
94
|
-
assert(re.test('testA.js'), '应该匹配 testA.js');
|
|
95
|
-
assert(!re.test('test.js'), '不应该匹配 test.js');
|
|
96
|
-
assert(!re.test('test12.js'), '不应该匹配 test12.js');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('matchesAny - 多个模式匹配', () => {
|
|
100
|
-
const patterns = ['*.json', 'src/**/*.ts', 'README.md'];
|
|
101
|
-
assert(matchesAny('package.json', patterns), '应该匹配 package.json');
|
|
102
|
-
// src/**/*.ts 匹配 src/ 后面跟任意路径再跟 .ts
|
|
103
|
-
assert(matchesAny('src/a/index.ts', patterns), '应该匹配 src/a/index.ts');
|
|
104
|
-
assert(matchesAny('src/utils/helper.ts', patterns), '应该匹配深层 ts 文件');
|
|
105
|
-
assert(matchesAny('README.md', patterns), '应该匹配 README.md');
|
|
106
|
-
assert(!matchesAny('test.js', patterns), '不应该匹配 test.js');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('matchesAny - 空数组', () => {
|
|
110
|
-
assert(!matchesAny('test.js', []), '空数组不应该匹配任何文件');
|
|
111
|
-
assert(!matchesAny('test.js', null), 'null 不应该匹配');
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test('normalizeRelPath - 路径标准化', () => {
|
|
115
|
-
if (path.sep === '\\') {
|
|
116
|
-
assertEqual(normalizeRelPath('src\\index.js'), 'src/index.js');
|
|
117
|
-
assertEqual(normalizeRelPath('a\\b\\c.txt'), 'a/b/c.txt');
|
|
118
|
-
} else {
|
|
119
|
-
assertEqual(normalizeRelPath('src/index.js'), 'src/index.js');
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('参数解析 - 基本目录', () => {
|
|
124
|
-
const parseArgs = (argv) => {
|
|
125
|
-
const args = argv.slice(2);
|
|
126
|
-
const opts = {
|
|
127
|
-
pre: false,
|
|
128
|
-
dir: '.',
|
|
129
|
-
template: null,
|
|
130
|
-
install: true,
|
|
131
|
-
start: true,
|
|
132
|
-
force: false,
|
|
133
|
-
pm: null,
|
|
134
|
-
conflict: 'keep'
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
for (let i = 0; i < args.length; i++) {
|
|
138
|
-
const a = args[i];
|
|
139
|
-
if ((a === 'pre' || a === 'preinstall' || a === 'preupdate') && opts.dir === '.' && !opts.pre) {
|
|
140
|
-
opts.pre = true;
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
if (!a.startsWith('-') && opts.dir === '.') {
|
|
144
|
-
opts.dir = a;
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
if (a === '-t' || a === '--template') {
|
|
148
|
-
opts.template = args[++i];
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
if (a === '--no-install') opts.install = false;
|
|
152
|
-
if (a === '--no-start') opts.start = false;
|
|
153
|
-
if (a === '--pre') opts.pre = true;
|
|
154
|
-
if (a === '--force') opts.force = true;
|
|
155
|
-
if (a === '--pm') opts.pm = args[++i];
|
|
156
|
-
if (a === '--conflict') {
|
|
157
|
-
const v = args[++i];
|
|
158
|
-
if (v === 'keep' || v === 'overwrite') opts.conflict = v;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return opts;
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const opts1 = parseArgs(['node', 'index.js', 'my-project']);
|
|
165
|
-
assertEqual(opts1.dir, 'my-project');
|
|
166
|
-
assertEqual(opts1.install, true);
|
|
167
|
-
|
|
168
|
-
const opts2 = parseArgs(['node', 'index.js', '--no-install', '--no-start']);
|
|
169
|
-
assertEqual(opts2.install, false);
|
|
170
|
-
assertEqual(opts2.start, false);
|
|
171
|
-
|
|
172
|
-
const opts3 = parseArgs(['node', 'index.js', '--pm', 'pnpm']);
|
|
173
|
-
assertEqual(opts3.pm, 'pnpm');
|
|
174
|
-
|
|
175
|
-
const opts4 = parseArgs(['node', 'index.js', '--conflict', 'overwrite']);
|
|
176
|
-
assertEqual(opts4.conflict, 'overwrite');
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// ============ 集成测试 ============
|
|
180
|
-
|
|
181
|
-
test('集成测试 - 创建临时项目', async () => {
|
|
182
|
-
const testDir = path.join(os.tmpdir(), `axhub-test-${Date.now()}`);
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
await fs.ensureDir(testDir);
|
|
186
|
-
|
|
187
|
-
// 创建测试文件
|
|
188
|
-
await fs.writeFile(path.join(testDir, 'test.txt'), 'hello');
|
|
189
|
-
|
|
190
|
-
// 验证文件存在
|
|
191
|
-
const exists = await fs.pathExists(path.join(testDir, 'test.txt'));
|
|
192
|
-
assert(exists, '测试文件应该存在');
|
|
193
|
-
|
|
194
|
-
// 清理
|
|
195
|
-
await fs.remove(testDir);
|
|
196
|
-
|
|
197
|
-
const stillExists = await fs.pathExists(testDir);
|
|
198
|
-
assert(!stillExists, '测试目录应该被删除');
|
|
199
|
-
} catch (err) {
|
|
200
|
-
await fs.remove(testDir);
|
|
201
|
-
throw err;
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test('文件比较 - filesEqual', async () => {
|
|
206
|
-
const testDir = path.join(os.tmpdir(), `axhub-test-${Date.now()}`);
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
await fs.ensureDir(testDir);
|
|
210
|
-
|
|
211
|
-
const file1 = path.join(testDir, 'file1.txt');
|
|
212
|
-
const file2 = path.join(testDir, 'file2.txt');
|
|
213
|
-
const file3 = path.join(testDir, 'file3.txt');
|
|
214
|
-
|
|
215
|
-
await fs.writeFile(file1, 'same content');
|
|
216
|
-
await fs.writeFile(file2, 'same content');
|
|
217
|
-
await fs.writeFile(file3, 'different content');
|
|
218
|
-
|
|
219
|
-
const filesEqual = async (a, b) => {
|
|
220
|
-
try {
|
|
221
|
-
const [abuf, bbuf] = await Promise.all([fs.readFile(a), fs.readFile(b)]);
|
|
222
|
-
if (abuf.length !== bbuf.length) return false;
|
|
223
|
-
return abuf.equals(bbuf);
|
|
224
|
-
} catch {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
assert(await filesEqual(file1, file2), 'file1 和 file2 应该相同');
|
|
230
|
-
assert(!(await filesEqual(file1, file3)), 'file1 和 file3 应该不同');
|
|
231
|
-
|
|
232
|
-
await fs.remove(testDir);
|
|
233
|
-
} catch (err) {
|
|
234
|
-
await fs.remove(testDir);
|
|
235
|
-
throw err;
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
// ============ 运行测试 ============
|
|
240
|
-
|
|
241
|
-
async function runTests() {
|
|
242
|
-
console.log(chalk.blue.bold('\n🧪 开始测试 Axhub Scaffold\n'));
|
|
243
|
-
|
|
244
|
-
for (const { name, fn } of tests) {
|
|
245
|
-
try {
|
|
246
|
-
await fn();
|
|
247
|
-
console.log(chalk.green(`✓ ${name}`));
|
|
248
|
-
passed++;
|
|
249
|
-
} catch (err) {
|
|
250
|
-
console.log(chalk.red(`✗ ${name}`));
|
|
251
|
-
console.log(chalk.red(` ${err.message}`));
|
|
252
|
-
failed++;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
console.log(chalk.blue('\n' + '='.repeat(50)));
|
|
257
|
-
console.log(chalk.green(`通过: ${passed}`));
|
|
258
|
-
if (failed > 0) {
|
|
259
|
-
console.log(chalk.red(`失败: ${failed}`));
|
|
260
|
-
}
|
|
261
|
-
console.log(chalk.blue('='.repeat(50) + '\n'));
|
|
262
|
-
|
|
263
|
-
process.exit(failed > 0 ? 1 : 0);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
runTests();
|