@zjex/git-workflow 0.2.23 → 0.3.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/.github/workflows/deploy-docs.yml +68 -0
- package/.github/workflows/test.yml +53 -0
- package/.husky/pre-commit +19 -0
- package/README.md +74 -1013
- package/TESTING.md +436 -0
- package/dist/index.js +104 -14
- package/docs/.vitepress/cache/deps/_metadata.json +52 -0
- package/docs/.vitepress/cache/deps/chunk-2CLQ7TTZ.js +9719 -0
- package/docs/.vitepress/cache/deps/chunk-2CLQ7TTZ.js.map +7 -0
- package/docs/.vitepress/cache/deps/chunk-LE5NDSFD.js +12824 -0
- package/docs/.vitepress/cache/deps/chunk-LE5NDSFD.js.map +7 -0
- package/docs/.vitepress/cache/deps/package.json +3 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4505 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +583 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1352 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1665 -0
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js +1813 -0
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
- package/docs/.vitepress/cache/deps/vue.js +347 -0
- package/docs/.vitepress/cache/deps/vue.js.map +7 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-2CLQ7TTZ.js +9719 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-2CLQ7TTZ.js.map +7 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-LE5NDSFD.js +12824 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-LE5NDSFD.js.map +7 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/package.json +3 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vue_devtools-api.js +4505 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vue_devtools-api.js.map +7 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_core.js +583 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_core.js.map +7 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_integrations_useFocusTrap.js +1352 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___mark__js_src_vanilla__js.js +1665 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___mark__js_src_vanilla__js.js.map +7 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___minisearch.js +1813 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___minisearch.js.map +7 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vue.js +347 -0
- package/docs/.vitepress/cache/deps_temp_44e2fb0f/vue.js.map +7 -0
- package/docs/.vitepress/config.ts +167 -0
- package/docs/.vitepress/theme/custom.css +39 -0
- package/docs/.vitepress/theme/index.ts +4 -0
- package/docs/README.md +82 -0
- package/docs/commands/branch.md +468 -0
- package/docs/commands/commit.md +554 -0
- package/docs/commands/config.md +346 -0
- package/docs/commands/index.md +312 -0
- package/docs/commands/interactive.md +384 -0
- package/docs/commands/release.md +300 -0
- package/docs/commands/stash.md +309 -0
- package/docs/commands/tag.md +278 -0
- package/docs/commands/update.md +347 -0
- package/docs/config/ai-config.md +160 -0
- package/docs/config/branch-config.md +133 -0
- package/docs/config/commit-config.md +185 -0
- package/docs/config/config-file.md +776 -0
- package/docs/config/examples.md +279 -0
- package/docs/config/index.md +478 -0
- package/docs/guide/ai-commit.md +576 -0
- package/docs/guide/basic-usage.md +522 -0
- package/docs/guide/best-practices.md +426 -0
- package/docs/guide/branch-management.md +712 -0
- package/docs/guide/getting-started.md +294 -0
- package/docs/guide/index.md +168 -0
- package/docs/guide/installation.md +449 -0
- package/docs/guide/release-management.md +744 -0
- package/docs/guide/stash-management.md +608 -0
- package/docs/guide/tag-management.md +614 -0
- package/docs/index.md +205 -0
- package/docs/public/favicon.svg +21 -0
- package/docs/public/hero-logo.svg +43 -0
- package/docs/public/logo.svg +20 -0
- package/package.json +19 -3
- package/scripts/publish.js +55 -8
- package/scripts/publish.sh +20 -2
- package/scripts/release.sh +20 -2
- package/scripts/update-test-count.js +55 -0
- package/src/ai-service.ts +101 -15
- package/src/commands/init.ts +18 -0
- package/src/commands/tag.ts +1 -1
- package/src/config.ts +1 -0
- package/tests/COVERAGE_REPORT.md +222 -0
- package/tests/QUICK_START.md +242 -0
- package/tests/README.md +119 -0
- package/tests/TEST_SUMMARY.md +330 -0
- package/tests/ai-service.test.ts +705 -0
- package/tests/branch.test.ts +255 -0
- package/tests/commit.test.ts +85 -0
- package/tests/config.test.ts +311 -0
- package/tests/help.test.ts +134 -0
- package/tests/init.test.ts +582 -0
- package/tests/release.test.ts +333 -0
- package/tests/setup.ts +21 -0
- package/tests/stash.test.ts +376 -0
- package/tests/tag.test.ts +396 -0
- package/tests/update-notifier.test.ts +384 -0
- package/tests/update.test.ts +402 -0
- package/tests/utils.test.ts +229 -0
- package/vitest.config.ts +22 -0
- package/zjex-logo.svg +22 -0
- package/zjex-optimized.svg +34 -0
- package/zjex.svg +1 -0
package/TESTING.md
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# 测试体系说明
|
|
2
|
+
|
|
3
|
+
## 为什么需要测试?
|
|
4
|
+
|
|
5
|
+
在开发 CLI 工具时,测试能够:
|
|
6
|
+
|
|
7
|
+
1. **防止回归** - 确保新功能不会破坏现有功能
|
|
8
|
+
2. **提高信心** - 重构代码时更有底气
|
|
9
|
+
3. **文档作用** - 测试用例本身就是最好的使用示例
|
|
10
|
+
4. **快速反馈** - 比手动测试快得多
|
|
11
|
+
|
|
12
|
+
## 测试框架选择
|
|
13
|
+
|
|
14
|
+
我们选择 **Vitest** 的原因:
|
|
15
|
+
|
|
16
|
+
- ✅ 快速 - 基于 Vite,启动和运行都很快
|
|
17
|
+
- ✅ 兼容 Jest - API 与 Jest 几乎完全兼容
|
|
18
|
+
- ✅ ESM 原生支持 - 无需额外配置
|
|
19
|
+
- ✅ TypeScript 支持 - 开箱即用
|
|
20
|
+
- ✅ UI 界面 - 提供可视化测试界面
|
|
21
|
+
- ✅ 覆盖率报告 - 内置覆盖率工具
|
|
22
|
+
|
|
23
|
+
## 测试结构
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
tests/
|
|
27
|
+
├── utils.test.ts # 工具函数测试
|
|
28
|
+
├── tag.test.ts # Tag 功能测试
|
|
29
|
+
├── commit.test.ts # Commit 功能测试
|
|
30
|
+
└── README.md # 测试指南
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 当前测试覆盖
|
|
34
|
+
|
|
35
|
+
### Tag 功能 ✅
|
|
36
|
+
|
|
37
|
+
- [x] 前缀提取(v, g, release-, 无前缀)
|
|
38
|
+
- [x] Tag 分组逻辑
|
|
39
|
+
- [x] 显示逻辑(最多 5 个)
|
|
40
|
+
- [x] 列宽计算
|
|
41
|
+
- [x] 省略号显示
|
|
42
|
+
|
|
43
|
+
### Commit 功能 ✅
|
|
44
|
+
|
|
45
|
+
- [x] 提交类型定义
|
|
46
|
+
- [x] 提交消息格式
|
|
47
|
+
- [x] Refactor 对齐处理
|
|
48
|
+
- [x] Emoji 使用
|
|
49
|
+
|
|
50
|
+
### Utils 功能 ✅
|
|
51
|
+
|
|
52
|
+
- [x] 字符串操作
|
|
53
|
+
- [x] 基本工具函数
|
|
54
|
+
|
|
55
|
+
## 运行测试
|
|
56
|
+
|
|
57
|
+
### 基本命令
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# 运行所有测试(单次)
|
|
61
|
+
npm test
|
|
62
|
+
|
|
63
|
+
# 监听模式(开发时使用)
|
|
64
|
+
npm run test:watch
|
|
65
|
+
|
|
66
|
+
# 可视化界面
|
|
67
|
+
npm run test:ui
|
|
68
|
+
|
|
69
|
+
# 生成覆盖率报告
|
|
70
|
+
npm run test:coverage
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 运行特定测试
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# 只运行 tag 相关测试
|
|
77
|
+
npx vitest tests/tag.test.ts
|
|
78
|
+
|
|
79
|
+
# 只运行匹配的测试
|
|
80
|
+
npx vitest -t "前缀提取"
|
|
81
|
+
|
|
82
|
+
# 运行并查看详细输出
|
|
83
|
+
npx vitest --reporter=verbose
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 编写测试
|
|
87
|
+
|
|
88
|
+
### 基本示例
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { describe, it, expect } from "vitest";
|
|
92
|
+
|
|
93
|
+
describe("功能模块", () => {
|
|
94
|
+
it("应该做某事", () => {
|
|
95
|
+
const result = someFunction();
|
|
96
|
+
expect(result).toBe(expectedValue);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 测试分组
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
describe("Tag 功能", () => {
|
|
105
|
+
describe("前缀提取", () => {
|
|
106
|
+
it("应该提取 v 前缀", () => {
|
|
107
|
+
// 测试代码
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("应该处理无前缀", () => {
|
|
111
|
+
// 测试代码
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("Tag 分组", () => {
|
|
116
|
+
it("应该按前缀分组", () => {
|
|
117
|
+
// 测试代码
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Mock 外部依赖
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { vi } from "vitest";
|
|
127
|
+
import { execSync } from "child_process";
|
|
128
|
+
|
|
129
|
+
// Mock 整个模块
|
|
130
|
+
vi.mock("child_process");
|
|
131
|
+
|
|
132
|
+
it("应该调用 git 命令", () => {
|
|
133
|
+
const mockExecSync = vi.mocked(execSync);
|
|
134
|
+
mockExecSync.mockReturnValue("v0.1.0\nv0.2.0");
|
|
135
|
+
|
|
136
|
+
// 测试代码...
|
|
137
|
+
|
|
138
|
+
expect(mockExecSync).toHaveBeenCalledWith("git tag -l");
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 测试异步代码
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
it("应该异步获取数据", async () => {
|
|
146
|
+
const result = await fetchData();
|
|
147
|
+
expect(result).toBeDefined();
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## CI/CD 集成
|
|
152
|
+
|
|
153
|
+
### GitHub Actions
|
|
154
|
+
|
|
155
|
+
已配置 `.github/workflows/test.yml`:
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
name: Test
|
|
159
|
+
|
|
160
|
+
on:
|
|
161
|
+
push:
|
|
162
|
+
branches: [main, dev]
|
|
163
|
+
pull_request:
|
|
164
|
+
branches: [main, dev]
|
|
165
|
+
|
|
166
|
+
jobs:
|
|
167
|
+
test:
|
|
168
|
+
runs-on: ubuntu-latest
|
|
169
|
+
strategy:
|
|
170
|
+
matrix:
|
|
171
|
+
node-version: [18.x, 20.x]
|
|
172
|
+
steps:
|
|
173
|
+
- uses: actions/checkout@v4
|
|
174
|
+
- name: Setup Node.js
|
|
175
|
+
uses: actions/setup-node@v4
|
|
176
|
+
with:
|
|
177
|
+
node-version: ${{ matrix.node-version }}
|
|
178
|
+
- name: Install dependencies
|
|
179
|
+
run: npm ci
|
|
180
|
+
- name: Run tests
|
|
181
|
+
run: npm test
|
|
182
|
+
- name: Build
|
|
183
|
+
run: npm run build
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Pre-commit Hook
|
|
187
|
+
|
|
188
|
+
已配置 `.husky/pre-commit`,每次提交前自动运行测试:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
#!/usr/bin/env sh
|
|
192
|
+
. "$(dirname -- "$0")/_/husky.sh"
|
|
193
|
+
|
|
194
|
+
# 运行测试
|
|
195
|
+
npm test
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
如果测试失败,提交会被阻止。
|
|
199
|
+
|
|
200
|
+
## 测试最佳实践
|
|
201
|
+
|
|
202
|
+
### 1. 测试应该独立
|
|
203
|
+
|
|
204
|
+
每个测试不应该依赖其他测试的结果:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// ❌ 不好 - 依赖全局状态
|
|
208
|
+
let result;
|
|
209
|
+
it("测试 1", () => {
|
|
210
|
+
result = doSomething();
|
|
211
|
+
});
|
|
212
|
+
it("测试 2", () => {
|
|
213
|
+
expect(result).toBe(expected); // 依赖测试 1
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// ✅ 好 - 每个测试独立
|
|
217
|
+
it("测试 1", () => {
|
|
218
|
+
const result = doSomething();
|
|
219
|
+
expect(result).toBe(expected);
|
|
220
|
+
});
|
|
221
|
+
it("测试 2", () => {
|
|
222
|
+
const result = doSomething();
|
|
223
|
+
expect(result).toBe(expected);
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 2. 使用描述性的测试名称
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// ❌ 不好
|
|
231
|
+
it("测试 1", () => {});
|
|
232
|
+
|
|
233
|
+
// ✅ 好
|
|
234
|
+
it("应该正确提取 v 前缀", () => {});
|
|
235
|
+
it("无前缀时应该返回 (无前缀)", () => {});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 3. 测试边界情况
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
describe("Tag 显示", () => {
|
|
242
|
+
it("应该显示少于 5 个的所有 tag", () => {
|
|
243
|
+
// 测试 1-4 个 tag
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("应该只显示最后 5 个 tag", () => {
|
|
247
|
+
// 测试正好 5 个 tag
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("超过 5 个时应该显示省略号", () => {
|
|
251
|
+
// 测试 6+ 个 tag
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("应该处理空数组", () => {
|
|
255
|
+
// 测试 0 个 tag
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### 4. 使用 beforeEach/afterEach
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { beforeEach, afterEach } from "vitest";
|
|
264
|
+
|
|
265
|
+
describe("测试套件", () => {
|
|
266
|
+
beforeEach(() => {
|
|
267
|
+
// 每个测试前执行
|
|
268
|
+
vi.clearAllMocks();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
afterEach(() => {
|
|
272
|
+
// 每个测试后执行
|
|
273
|
+
vi.restoreAllMocks();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("测试 1", () => {});
|
|
277
|
+
it("测试 2", () => {});
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### 5. 测试覆盖率目标
|
|
282
|
+
|
|
283
|
+
- **核心功能** - 100% 覆盖
|
|
284
|
+
- **工具函数** - 90%+ 覆盖
|
|
285
|
+
- **UI 交互** - 关键路径覆盖
|
|
286
|
+
|
|
287
|
+
## 添加新功能时的测试流程
|
|
288
|
+
|
|
289
|
+
1. **先写测试(TDD)**
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// 1. 写测试
|
|
293
|
+
it("应该支持新功能", () => {
|
|
294
|
+
const result = newFeature();
|
|
295
|
+
expect(result).toBe(expected);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// 2. 运行测试(应该失败)
|
|
299
|
+
// npm test
|
|
300
|
+
|
|
301
|
+
// 3. 实现功能
|
|
302
|
+
function newFeature() {
|
|
303
|
+
// 实现代码
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 4. 运行测试(应该通过)
|
|
307
|
+
// npm test
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
2. **或先实现后测试**
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// 1. 实现功能
|
|
314
|
+
function newFeature() {
|
|
315
|
+
// 实现代码
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 2. 写测试
|
|
319
|
+
it("应该支持新功能", () => {
|
|
320
|
+
const result = newFeature();
|
|
321
|
+
expect(result).toBe(expected);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// 3. 运行测试
|
|
325
|
+
// npm test
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
3. **提交前检查**
|
|
329
|
+
```bash
|
|
330
|
+
# 运行所有测试
|
|
331
|
+
npm test
|
|
332
|
+
# 检查覆盖率
|
|
333
|
+
npm run test:coverage
|
|
334
|
+
# 构建验证
|
|
335
|
+
npm run build
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## 调试测试
|
|
339
|
+
|
|
340
|
+
### 使用 console.log
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
it("调试测试", () => {
|
|
344
|
+
const result = someFunction();
|
|
345
|
+
console.log("result:", result); // 会在测试输出中显示
|
|
346
|
+
expect(result).toBe(expected);
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### 使用 test.only
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// 只运行这一个测试
|
|
354
|
+
it.only("调试这个测试", () => {
|
|
355
|
+
// 测试代码
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### 使用 UI 界面
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
npm run test:ui
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
在浏览器中查看测试结果,可以:
|
|
366
|
+
|
|
367
|
+
- 查看测试树
|
|
368
|
+
- 查看失败详情
|
|
369
|
+
- 重新运行单个测试
|
|
370
|
+
- 查看覆盖率
|
|
371
|
+
|
|
372
|
+
## 常见问题
|
|
373
|
+
|
|
374
|
+
### Q: 测试运行很慢怎么办?
|
|
375
|
+
|
|
376
|
+
A: 使用监听模式,只运行改动的测试:
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
npm run test:watch
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Q: 如何跳过某个测试?
|
|
383
|
+
|
|
384
|
+
A: 使用 `it.skip`:
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
it.skip("暂时跳过这个测试", () => {
|
|
388
|
+
// 测试代码
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Q: 如何测试 CLI 交互?
|
|
393
|
+
|
|
394
|
+
A: Mock inquirer 的 prompt:
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
import { vi } from "vitest";
|
|
398
|
+
import { select } from "@inquirer/prompts";
|
|
399
|
+
|
|
400
|
+
vi.mock("@inquirer/prompts");
|
|
401
|
+
|
|
402
|
+
it("应该选择正确的选项", async () => {
|
|
403
|
+
vi.mocked(select).mockResolvedValue("option1");
|
|
404
|
+
// 测试代码
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Q: 如何测试 git 命令?
|
|
409
|
+
|
|
410
|
+
A: Mock child_process:
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
import { vi } from "vitest";
|
|
414
|
+
import { execSync } from "child_process";
|
|
415
|
+
|
|
416
|
+
vi.mock("child_process");
|
|
417
|
+
|
|
418
|
+
it("应该执行 git 命令", () => {
|
|
419
|
+
vi.mocked(execSync).mockReturnValue("success");
|
|
420
|
+
// 测试代码
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## 未来计划
|
|
425
|
+
|
|
426
|
+
- [ ] 增加集成测试
|
|
427
|
+
- [ ] 增加 E2E 测试
|
|
428
|
+
- [ ] 提高测试覆盖率到 90%+
|
|
429
|
+
- [ ] 添加性能测试
|
|
430
|
+
- [ ] 添加快照测试
|
|
431
|
+
|
|
432
|
+
## 参考资料
|
|
433
|
+
|
|
434
|
+
- [Vitest 官方文档](https://vitest.dev/)
|
|
435
|
+
- [Testing Best Practices](https://github.com/goldbergyoni/javascript-testing-best-practices)
|
|
436
|
+
- [Jest Cheat Sheet](https://github.com/sapegin/jest-cheat-sheet)
|
package/dist/index.js
CHANGED
|
@@ -674,7 +674,7 @@ async function listTags(prefix) {
|
|
|
674
674
|
}
|
|
675
675
|
const grouped = /* @__PURE__ */ new Map();
|
|
676
676
|
tags.forEach((tag) => {
|
|
677
|
-
const prefix2 = tag.replace(/[0-9].*/, "") || "\u65E0\u524D\u7F00";
|
|
677
|
+
const prefix2 = tag.replace(/[0-9].*/, "") || "(\u65E0\u524D\u7F00)";
|
|
678
678
|
if (!grouped.has(prefix2)) {
|
|
679
679
|
grouped.set(prefix2, []);
|
|
680
680
|
}
|
|
@@ -1442,11 +1442,28 @@ async function init() {
|
|
|
1442
1442
|
],
|
|
1443
1443
|
theme
|
|
1444
1444
|
});
|
|
1445
|
+
const detailedDescription = await select4({
|
|
1446
|
+
message: "\u662F\u5426\u751F\u6210\u8BE6\u7EC6\u7684\u4FEE\u6539\u70B9\u63CF\u8FF0?",
|
|
1447
|
+
choices: [
|
|
1448
|
+
{
|
|
1449
|
+
name: "\u662F\uFF08\u5305\u542B\u4FEE\u6539\u70B9\u5217\u8868\uFF0C\u63A8\u8350\uFF09",
|
|
1450
|
+
value: true,
|
|
1451
|
+
description: "\u5982\uFF1Afeat(auth): \u6DFB\u52A0\u7528\u6237\u767B\u5F55\u529F\u80FD\n\n- \u5B9E\u73B0\u7528\u6237\u540D\u5BC6\u7801\u767B\u5F55\u63A5\u53E3\n- \u6DFB\u52A0\u767B\u5F55\u72B6\u6001\u9A8C\u8BC1\u4E2D\u95F4\u4EF6"
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
name: "\u5426\uFF08\u4EC5\u751F\u6210\u6807\u9898\uFF09",
|
|
1455
|
+
value: false,
|
|
1456
|
+
description: "\u5982\uFF1Afeat(auth): \u6DFB\u52A0\u7528\u6237\u767B\u5F55\u529F\u80FD"
|
|
1457
|
+
}
|
|
1458
|
+
],
|
|
1459
|
+
theme
|
|
1460
|
+
});
|
|
1445
1461
|
config2.aiCommit = {
|
|
1446
1462
|
enabled: true,
|
|
1447
1463
|
provider: aiProvider,
|
|
1448
1464
|
apiKey: apiKey || void 0,
|
|
1449
|
-
language
|
|
1465
|
+
language,
|
|
1466
|
+
detailedDescription
|
|
1450
1467
|
};
|
|
1451
1468
|
const defaultModels = {
|
|
1452
1469
|
github: "gpt-4o-mini",
|
|
@@ -1790,9 +1807,62 @@ function getGitDiff() {
|
|
|
1790
1807
|
return "";
|
|
1791
1808
|
}
|
|
1792
1809
|
}
|
|
1793
|
-
function buildPrompt(diff, language) {
|
|
1810
|
+
function buildPrompt(diff, language, detailedDescription = false) {
|
|
1794
1811
|
const isZh = language === "zh-CN";
|
|
1795
|
-
|
|
1812
|
+
if (detailedDescription) {
|
|
1813
|
+
const systemPrompt = isZh ? `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684 Git commit message \u751F\u6210\u52A9\u624B\u3002\u8BF7\u6839\u636E\u63D0\u4F9B\u7684 git diff \u751F\u6210\u7B26\u5408 Conventional Commits \u89C4\u8303\u7684\u8BE6\u7EC6 commit message\u3002
|
|
1814
|
+
|
|
1815
|
+
\u683C\u5F0F\u8981\u6C42\uFF1A
|
|
1816
|
+
1. \u7B2C\u4E00\u884C\uFF1A<type>(<scope>): <subject>
|
|
1817
|
+
2. \u7A7A\u884C
|
|
1818
|
+
3. \u8BE6\u7EC6\u63CF\u8FF0\uFF1A\u5217\u51FA\u4E3B\u8981\u4FEE\u6539\u70B9\uFF0C\u6BCF\u4E2A\u4FEE\u6539\u70B9\u4E00\u884C\uFF0C\u4EE5 "- " \u5F00\u5934
|
|
1819
|
+
|
|
1820
|
+
\u89C4\u5219\uFF1A
|
|
1821
|
+
- type \u5FC5\u987B\u662F\u4EE5\u4E0B\u4E4B\u4E00\uFF1Afeat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
1822
|
+
- scope \u662F\u53EF\u9009\u7684\uFF0C\u8868\u793A\u5F71\u54CD\u8303\u56F4
|
|
1823
|
+
- subject \u7528\u4E2D\u6587\u63CF\u8FF0\uFF0C\u7B80\u6D01\u660E\u4E86\uFF0C\u4E0D\u8D85\u8FC7 50 \u5B57
|
|
1824
|
+
- \u8BE6\u7EC6\u63CF\u8FF0\u8981\u5217\u51FA 3-6 \u4E2A\u4E3B\u8981\u4FEE\u6539\u70B9\uFF0C\u6BCF\u4E2A\u4FEE\u6539\u70B9\u7B80\u6D01\u660E\u4E86
|
|
1825
|
+
- \u5982\u679C\u4FEE\u6539\u8F83\u5C11\uFF0C\u53EF\u4EE5\u53EA\u5217\u51FA 2-3 \u4E2A\u4FEE\u6539\u70B9
|
|
1826
|
+
- \u4E0D\u8981\u6709\u5176\u4ED6\u89E3\u91CA\u6216\u591A\u4F59\u5185\u5BB9
|
|
1827
|
+
|
|
1828
|
+
\u793A\u4F8B\uFF1A
|
|
1829
|
+
feat(auth): \u6DFB\u52A0\u7528\u6237\u767B\u5F55\u529F\u80FD
|
|
1830
|
+
|
|
1831
|
+
- \u5B9E\u73B0\u7528\u6237\u540D\u5BC6\u7801\u767B\u5F55\u63A5\u53E3
|
|
1832
|
+
- \u6DFB\u52A0\u767B\u5F55\u72B6\u6001\u9A8C\u8BC1\u4E2D\u95F4\u4EF6
|
|
1833
|
+
- \u5B8C\u5584\u767B\u5F55\u9519\u8BEF\u5904\u7406\u903B\u8F91
|
|
1834
|
+
- \u66F4\u65B0\u7528\u6237\u8BA4\u8BC1\u76F8\u5173\u6587\u6863` : `You are a professional Git commit message generator. Generate a detailed commit message following Conventional Commits specification based on the provided git diff.
|
|
1835
|
+
|
|
1836
|
+
Format requirements:
|
|
1837
|
+
1. First line: <type>(<scope>): <subject>
|
|
1838
|
+
2. Empty line
|
|
1839
|
+
3. Detailed description: List main changes, one per line, starting with "- "
|
|
1840
|
+
|
|
1841
|
+
Rules:
|
|
1842
|
+
- type must be one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
1843
|
+
- scope is optional, indicates the affected area
|
|
1844
|
+
- subject should be concise, no more than 50 characters
|
|
1845
|
+
- Detailed description should list 3-6 main changes, each change should be concise
|
|
1846
|
+
- If changes are minimal, list 2-3 changes
|
|
1847
|
+
- No explanations or extra content
|
|
1848
|
+
|
|
1849
|
+
Example:
|
|
1850
|
+
feat(auth): add user login functionality
|
|
1851
|
+
|
|
1852
|
+
- Implement username/password login API
|
|
1853
|
+
- Add login status validation middleware
|
|
1854
|
+
- Improve login error handling logic
|
|
1855
|
+
- Update user authentication documentation`;
|
|
1856
|
+
const userPrompt = isZh ? `\u8BF7\u6839\u636E\u4EE5\u4E0B git diff \u751F\u6210\u8BE6\u7EC6\u7684 commit message\uFF08\u5305\u542B\u4FEE\u6539\u70B9\u5217\u8868\uFF09\uFF1A
|
|
1857
|
+
|
|
1858
|
+
${diff}` : `Generate a detailed commit message (including change list) based on the following git diff:
|
|
1859
|
+
|
|
1860
|
+
${diff}`;
|
|
1861
|
+
return `${systemPrompt}
|
|
1862
|
+
|
|
1863
|
+
${userPrompt}`;
|
|
1864
|
+
} else {
|
|
1865
|
+
const systemPrompt = isZh ? `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684 Git commit message \u751F\u6210\u52A9\u624B\u3002\u8BF7\u6839\u636E\u63D0\u4F9B\u7684 git diff \u751F\u6210\u7B26\u5408 Conventional Commits \u89C4\u8303\u7684 commit message\u3002
|
|
1796
1866
|
|
|
1797
1867
|
\u89C4\u5219\uFF1A
|
|
1798
1868
|
1. \u683C\u5F0F\uFF1A<type>(<scope>): <subject>
|
|
@@ -1819,14 +1889,15 @@ Examples:
|
|
|
1819
1889
|
- feat(auth): add user login functionality
|
|
1820
1890
|
- fix(api): resolve data fetching failure
|
|
1821
1891
|
- docs(readme): update installation guide`;
|
|
1822
|
-
|
|
1892
|
+
const userPrompt = isZh ? `\u8BF7\u6839\u636E\u4EE5\u4E0B git diff \u751F\u6210 commit message\uFF1A
|
|
1823
1893
|
|
|
1824
1894
|
${diff}` : `Generate a commit message based on the following git diff:
|
|
1825
1895
|
|
|
1826
1896
|
${diff}`;
|
|
1827
|
-
|
|
1897
|
+
return `${systemPrompt}
|
|
1828
1898
|
|
|
1829
1899
|
${userPrompt}`;
|
|
1900
|
+
}
|
|
1830
1901
|
}
|
|
1831
1902
|
async function callGitHubAPI(prompt, apiKey, model, maxTokens) {
|
|
1832
1903
|
const response = await fetch(AI_PROVIDERS.github.endpoint, {
|
|
@@ -1921,18 +1992,31 @@ async function callOllamaAPI(prompt, model, maxTokens) {
|
|
|
1921
1992
|
);
|
|
1922
1993
|
}
|
|
1923
1994
|
}
|
|
1995
|
+
function cleanAIResponse(response) {
|
|
1996
|
+
const lines = response.split("\n").map((line) => line.trim()).filter((line) => line);
|
|
1997
|
+
const uniqueLines = [];
|
|
1998
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1999
|
+
for (const line of lines) {
|
|
2000
|
+
if (!seen.has(line)) {
|
|
2001
|
+
seen.add(line);
|
|
2002
|
+
uniqueLines.push(line);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
return uniqueLines.join("\n");
|
|
2006
|
+
}
|
|
1924
2007
|
async function generateAICommitMessage(config2) {
|
|
1925
2008
|
const aiConfig = config2.aiCommit || {};
|
|
1926
2009
|
const provider = aiConfig.provider || "github";
|
|
1927
2010
|
const language = aiConfig.language || "zh-CN";
|
|
1928
|
-
const
|
|
2011
|
+
const detailedDescription = aiConfig.detailedDescription !== false;
|
|
2012
|
+
const maxTokens = aiConfig.maxTokens || (detailedDescription ? 400 : 200);
|
|
1929
2013
|
const diff = getGitDiff();
|
|
1930
2014
|
if (!diff) {
|
|
1931
2015
|
throw new Error("\u6CA1\u6709\u68C0\u6D4B\u5230\u4EE3\u7801\u66F4\u6539");
|
|
1932
2016
|
}
|
|
1933
|
-
const maxDiffLength = 4e3;
|
|
2017
|
+
const maxDiffLength = detailedDescription ? 6e3 : 4e3;
|
|
1934
2018
|
const truncatedDiff = diff.length > maxDiffLength ? diff.slice(0, maxDiffLength) + "\n..." : diff;
|
|
1935
|
-
const prompt = buildPrompt(truncatedDiff, language);
|
|
2019
|
+
const prompt = buildPrompt(truncatedDiff, language, detailedDescription);
|
|
1936
2020
|
const providerInfo = AI_PROVIDERS[provider];
|
|
1937
2021
|
if (!providerInfo) {
|
|
1938
2022
|
throw new Error(`\u4E0D\u652F\u6301\u7684 AI \u63D0\u4F9B\u5546: ${provider}`);
|
|
@@ -1944,18 +2028,24 @@ async function generateAICommitMessage(config2) {
|
|
|
1944
2028
|
`${providerInfo.name} \u9700\u8981 API key\u3002\u8BF7\u8FD0\u884C 'gw init' \u914D\u7F6E AI commit\uFF0C\u6216\u5728 .gwrc.json \u4E2D\u8BBE\u7F6E aiCommit.apiKey`
|
|
1945
2029
|
);
|
|
1946
2030
|
}
|
|
2031
|
+
let response;
|
|
1947
2032
|
switch (provider) {
|
|
1948
2033
|
case "github":
|
|
1949
|
-
|
|
2034
|
+
response = await callGitHubAPI(prompt, apiKey, model, maxTokens);
|
|
2035
|
+
break;
|
|
1950
2036
|
case "openai":
|
|
1951
|
-
|
|
2037
|
+
response = await callOpenAIAPI(prompt, apiKey, model, maxTokens);
|
|
2038
|
+
break;
|
|
1952
2039
|
case "claude":
|
|
1953
|
-
|
|
2040
|
+
response = await callClaudeAPI(prompt, apiKey, model, maxTokens);
|
|
2041
|
+
break;
|
|
1954
2042
|
case "ollama":
|
|
1955
|
-
|
|
2043
|
+
response = await callOllamaAPI(prompt, model, maxTokens);
|
|
2044
|
+
break;
|
|
1956
2045
|
default:
|
|
1957
2046
|
throw new Error(`\u4E0D\u652F\u6301\u7684 AI \u63D0\u4F9B\u5546: ${provider}`);
|
|
1958
2047
|
}
|
|
2048
|
+
return cleanAIResponse(response);
|
|
1959
2049
|
}
|
|
1960
2050
|
function isAICommitAvailable(config2) {
|
|
1961
2051
|
const aiConfig = config2.aiCommit;
|
|
@@ -2496,7 +2586,7 @@ process.on("SIGTERM", () => {
|
|
|
2496
2586
|
console.log("");
|
|
2497
2587
|
process.exit(0);
|
|
2498
2588
|
});
|
|
2499
|
-
var version = true ? "0.
|
|
2589
|
+
var version = true ? "0.3.0" : "0.0.0-dev";
|
|
2500
2590
|
async function mainMenu() {
|
|
2501
2591
|
console.log(
|
|
2502
2592
|
colors.green(`
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hash": "cb9fc065",
|
|
3
|
+
"configHash": "e91271f8",
|
|
4
|
+
"lockfileHash": "eae41063",
|
|
5
|
+
"browserHash": "fa434d50",
|
|
6
|
+
"optimized": {
|
|
7
|
+
"vue": {
|
|
8
|
+
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
|
|
9
|
+
"file": "vue.js",
|
|
10
|
+
"fileHash": "69cff841",
|
|
11
|
+
"needsInterop": false
|
|
12
|
+
},
|
|
13
|
+
"vitepress > @vue/devtools-api": {
|
|
14
|
+
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
|
|
15
|
+
"file": "vitepress___@vue_devtools-api.js",
|
|
16
|
+
"fileHash": "5ad8b965",
|
|
17
|
+
"needsInterop": false
|
|
18
|
+
},
|
|
19
|
+
"vitepress > @vueuse/core": {
|
|
20
|
+
"src": "../../../../node_modules/@vueuse/core/index.mjs",
|
|
21
|
+
"file": "vitepress___@vueuse_core.js",
|
|
22
|
+
"fileHash": "9696f0e0",
|
|
23
|
+
"needsInterop": false
|
|
24
|
+
},
|
|
25
|
+
"vitepress > @vueuse/integrations/useFocusTrap": {
|
|
26
|
+
"src": "../../../../node_modules/@vueuse/integrations/useFocusTrap.mjs",
|
|
27
|
+
"file": "vitepress___@vueuse_integrations_useFocusTrap.js",
|
|
28
|
+
"fileHash": "6cc36968",
|
|
29
|
+
"needsInterop": false
|
|
30
|
+
},
|
|
31
|
+
"vitepress > mark.js/src/vanilla.js": {
|
|
32
|
+
"src": "../../../../node_modules/mark.js/src/vanilla.js",
|
|
33
|
+
"file": "vitepress___mark__js_src_vanilla__js.js",
|
|
34
|
+
"fileHash": "08475884",
|
|
35
|
+
"needsInterop": false
|
|
36
|
+
},
|
|
37
|
+
"vitepress > minisearch": {
|
|
38
|
+
"src": "../../../../node_modules/minisearch/dist/es/index.js",
|
|
39
|
+
"file": "vitepress___minisearch.js",
|
|
40
|
+
"fileHash": "1cb3d774",
|
|
41
|
+
"needsInterop": false
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"chunks": {
|
|
45
|
+
"chunk-2CLQ7TTZ": {
|
|
46
|
+
"file": "chunk-2CLQ7TTZ.js"
|
|
47
|
+
},
|
|
48
|
+
"chunk-LE5NDSFD": {
|
|
49
|
+
"file": "chunk-LE5NDSFD.js"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|