@zjex/git-workflow 0.2.24 → 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 +24 -4
- package/.husky/pre-commit +14 -0
- package/README.md +72 -1066
- package/dist/index.js +103 -13
- 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 +11 -2
- 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/config.ts +1 -0
- package/tests/ai-service.test.ts +237 -2
- 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/update.test.ts +402 -0
- package/vitest.config.ts +3 -0
- package/zjex-logo.svg +22 -0
- package/zjex-optimized.svg +34 -0
- package/zjex.svg +1 -0
package/src/ai-service.ts
CHANGED
|
@@ -60,11 +60,66 @@ function getGitDiff(): string {
|
|
|
60
60
|
/**
|
|
61
61
|
* 构建 AI prompt
|
|
62
62
|
*/
|
|
63
|
-
function buildPrompt(diff: string, language: string): string {
|
|
63
|
+
function buildPrompt(diff: string, language: string, detailedDescription: boolean = false): string {
|
|
64
64
|
const isZh = language === "zh-CN";
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
if (detailedDescription) {
|
|
67
|
+
// 详细模式:生成包含修改点的完整 commit message
|
|
68
|
+
const systemPrompt = isZh
|
|
69
|
+
? `你是一个专业的 Git commit message 生成助手。请根据提供的 git diff 生成符合 Conventional Commits 规范的详细 commit message。
|
|
70
|
+
|
|
71
|
+
格式要求:
|
|
72
|
+
1. 第一行:<type>(<scope>): <subject>
|
|
73
|
+
2. 空行
|
|
74
|
+
3. 详细描述:列出主要修改点,每个修改点一行,以 "- " 开头
|
|
75
|
+
|
|
76
|
+
规则:
|
|
77
|
+
- type 必须是以下之一:feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
78
|
+
- scope 是可选的,表示影响范围
|
|
79
|
+
- subject 用中文描述,简洁明了,不超过 50 字
|
|
80
|
+
- 详细描述要列出 3-6 个主要修改点,每个修改点简洁明了
|
|
81
|
+
- 如果修改较少,可以只列出 2-3 个修改点
|
|
82
|
+
- 不要有其他解释或多余内容
|
|
83
|
+
|
|
84
|
+
示例:
|
|
85
|
+
feat(auth): 添加用户登录功能
|
|
86
|
+
|
|
87
|
+
- 实现用户名密码登录接口
|
|
88
|
+
- 添加登录状态验证中间件
|
|
89
|
+
- 完善登录错误处理逻辑
|
|
90
|
+
- 更新用户认证相关文档`
|
|
91
|
+
: `You are a professional Git commit message generator. Generate a detailed commit message following Conventional Commits specification based on the provided git diff.
|
|
92
|
+
|
|
93
|
+
Format requirements:
|
|
94
|
+
1. First line: <type>(<scope>): <subject>
|
|
95
|
+
2. Empty line
|
|
96
|
+
3. Detailed description: List main changes, one per line, starting with "- "
|
|
97
|
+
|
|
98
|
+
Rules:
|
|
99
|
+
- type must be one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
100
|
+
- scope is optional, indicates the affected area
|
|
101
|
+
- subject should be concise, no more than 50 characters
|
|
102
|
+
- Detailed description should list 3-6 main changes, each change should be concise
|
|
103
|
+
- If changes are minimal, list 2-3 changes
|
|
104
|
+
- No explanations or extra content
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
feat(auth): add user login functionality
|
|
108
|
+
|
|
109
|
+
- Implement username/password login API
|
|
110
|
+
- Add login status validation middleware
|
|
111
|
+
- Improve login error handling logic
|
|
112
|
+
- Update user authentication documentation`;
|
|
113
|
+
|
|
114
|
+
const userPrompt = isZh
|
|
115
|
+
? `请根据以下 git diff 生成详细的 commit message(包含修改点列表):\n\n${diff}`
|
|
116
|
+
: `Generate a detailed commit message (including change list) based on the following git diff:\n\n${diff}`;
|
|
117
|
+
|
|
118
|
+
return `${systemPrompt}\n\n${userPrompt}`;
|
|
119
|
+
} else {
|
|
120
|
+
// 简洁模式:只生成标题
|
|
121
|
+
const systemPrompt = isZh
|
|
122
|
+
? `你是一个专业的 Git commit message 生成助手。请根据提供的 git diff 生成符合 Conventional Commits 规范的 commit message。
|
|
68
123
|
|
|
69
124
|
规则:
|
|
70
125
|
1. 格式:<type>(<scope>): <subject>
|
|
@@ -78,7 +133,7 @@ function buildPrompt(diff: string, language: string): string {
|
|
|
78
133
|
- feat(auth): 添加用户登录功能
|
|
79
134
|
- fix(api): 修复数据获取失败的问题
|
|
80
135
|
- docs(readme): 更新安装说明`
|
|
81
|
-
|
|
136
|
+
: `You are a professional Git commit message generator. Generate a commit message following Conventional Commits specification based on the provided git diff.
|
|
82
137
|
|
|
83
138
|
Rules:
|
|
84
139
|
1. Format: <type>(<scope>): <subject>
|
|
@@ -93,11 +148,12 @@ Examples:
|
|
|
93
148
|
- fix(api): resolve data fetching failure
|
|
94
149
|
- docs(readme): update installation guide`;
|
|
95
150
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
151
|
+
const userPrompt = isZh
|
|
152
|
+
? `请根据以下 git diff 生成 commit message:\n\n${diff}`
|
|
153
|
+
: `Generate a commit message based on the following git diff:\n\n${diff}`;
|
|
99
154
|
|
|
100
|
-
|
|
155
|
+
return `${systemPrompt}\n\n${userPrompt}`;
|
|
156
|
+
}
|
|
101
157
|
}
|
|
102
158
|
|
|
103
159
|
/**
|
|
@@ -233,6 +289,27 @@ async function callOllamaAPI(
|
|
|
233
289
|
}
|
|
234
290
|
}
|
|
235
291
|
|
|
292
|
+
/**
|
|
293
|
+
* 清理AI生成的commit message
|
|
294
|
+
* 移除重复行和多余的空行
|
|
295
|
+
*/
|
|
296
|
+
function cleanAIResponse(response: string): string {
|
|
297
|
+
const lines = response.split('\n').map(line => line.trim()).filter(line => line);
|
|
298
|
+
|
|
299
|
+
// 移除重复的行
|
|
300
|
+
const uniqueLines = [];
|
|
301
|
+
const seen = new Set();
|
|
302
|
+
|
|
303
|
+
for (const line of lines) {
|
|
304
|
+
if (!seen.has(line)) {
|
|
305
|
+
seen.add(line);
|
|
306
|
+
uniqueLines.push(line);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return uniqueLines.join('\n');
|
|
311
|
+
}
|
|
312
|
+
|
|
236
313
|
/**
|
|
237
314
|
* 生成 AI commit message
|
|
238
315
|
*/
|
|
@@ -242,7 +319,8 @@ export async function generateAICommitMessage(
|
|
|
242
319
|
const aiConfig = config.aiCommit || {};
|
|
243
320
|
const provider = aiConfig.provider || "github";
|
|
244
321
|
const language = aiConfig.language || "zh-CN";
|
|
245
|
-
const
|
|
322
|
+
const detailedDescription = aiConfig.detailedDescription !== false; // 默认启用详细描述
|
|
323
|
+
const maxTokens = aiConfig.maxTokens || (detailedDescription ? 400 : 200);
|
|
246
324
|
|
|
247
325
|
// 获取 git diff
|
|
248
326
|
const diff = getGitDiff();
|
|
@@ -251,12 +329,12 @@ export async function generateAICommitMessage(
|
|
|
251
329
|
}
|
|
252
330
|
|
|
253
331
|
// 限制 diff 长度,避免超过 token 限制
|
|
254
|
-
const maxDiffLength = 4000;
|
|
332
|
+
const maxDiffLength = detailedDescription ? 6000 : 4000;
|
|
255
333
|
const truncatedDiff =
|
|
256
334
|
diff.length > maxDiffLength ? diff.slice(0, maxDiffLength) + "\n..." : diff;
|
|
257
335
|
|
|
258
336
|
// 构建 prompt
|
|
259
|
-
const prompt = buildPrompt(truncatedDiff, language);
|
|
337
|
+
const prompt = buildPrompt(truncatedDiff, language, detailedDescription);
|
|
260
338
|
|
|
261
339
|
// 根据提供商调用对应的 API
|
|
262
340
|
const providerInfo = AI_PROVIDERS[provider];
|
|
@@ -276,18 +354,26 @@ export async function generateAICommitMessage(
|
|
|
276
354
|
}
|
|
277
355
|
|
|
278
356
|
// 调用 API
|
|
357
|
+
let response: string;
|
|
279
358
|
switch (provider) {
|
|
280
359
|
case "github":
|
|
281
|
-
|
|
360
|
+
response = await callGitHubAPI(prompt, apiKey, model, maxTokens);
|
|
361
|
+
break;
|
|
282
362
|
case "openai":
|
|
283
|
-
|
|
363
|
+
response = await callOpenAIAPI(prompt, apiKey, model, maxTokens);
|
|
364
|
+
break;
|
|
284
365
|
case "claude":
|
|
285
|
-
|
|
366
|
+
response = await callClaudeAPI(prompt, apiKey, model, maxTokens);
|
|
367
|
+
break;
|
|
286
368
|
case "ollama":
|
|
287
|
-
|
|
369
|
+
response = await callOllamaAPI(prompt, model, maxTokens);
|
|
370
|
+
break;
|
|
288
371
|
default:
|
|
289
372
|
throw new Error(`不支持的 AI 提供商: ${provider}`);
|
|
290
373
|
}
|
|
374
|
+
|
|
375
|
+
// 清理AI响应,移除重复内容
|
|
376
|
+
return cleanAIResponse(response);
|
|
291
377
|
}
|
|
292
378
|
|
|
293
379
|
/**
|
package/src/commands/init.ts
CHANGED
|
@@ -266,11 +266,29 @@ export async function init(): Promise<void> {
|
|
|
266
266
|
theme,
|
|
267
267
|
});
|
|
268
268
|
|
|
269
|
+
const detailedDescription = await select({
|
|
270
|
+
message: "是否生成详细的修改点描述?",
|
|
271
|
+
choices: [
|
|
272
|
+
{
|
|
273
|
+
name: "是(包含修改点列表,推荐)",
|
|
274
|
+
value: true,
|
|
275
|
+
description: "如:feat(auth): 添加用户登录功能\n\n- 实现用户名密码登录接口\n- 添加登录状态验证中间件"
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "否(仅生成标题)",
|
|
279
|
+
value: false,
|
|
280
|
+
description: "如:feat(auth): 添加用户登录功能"
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
theme,
|
|
284
|
+
});
|
|
285
|
+
|
|
269
286
|
config.aiCommit = {
|
|
270
287
|
enabled: true,
|
|
271
288
|
provider: aiProvider as "github" | "openai" | "claude" | "ollama",
|
|
272
289
|
apiKey: apiKey || undefined,
|
|
273
290
|
language: language as "zh-CN" | "en-US",
|
|
291
|
+
detailedDescription,
|
|
274
292
|
};
|
|
275
293
|
|
|
276
294
|
// 根据提供商设置默认模型
|
package/src/config.ts
CHANGED
package/tests/ai-service.test.ts
CHANGED
|
@@ -121,7 +121,7 @@ describe("AI Service 模块测试", () => {
|
|
|
121
121
|
provider: "github",
|
|
122
122
|
apiKey: "test-key",
|
|
123
123
|
language: "zh-CN",
|
|
124
|
-
maxTokens
|
|
124
|
+
// 不设置 maxTokens,让它自动计算
|
|
125
125
|
},
|
|
126
126
|
};
|
|
127
127
|
|
|
@@ -310,7 +310,7 @@ describe("AI Service 模块测试", () => {
|
|
|
310
310
|
});
|
|
311
311
|
|
|
312
312
|
it("应该截断过长的 diff", async () => {
|
|
313
|
-
const longDiff = "a".repeat(
|
|
313
|
+
const longDiff = "a".repeat(7000); // 超过详细模式的 6000 限制
|
|
314
314
|
vi.mocked(execOutput).mockReturnValue(longDiff);
|
|
315
315
|
|
|
316
316
|
const mockFetch = vi.mocked(fetch);
|
|
@@ -343,6 +343,7 @@ describe("AI Service 模块测试", () => {
|
|
|
343
343
|
aiCommit: {
|
|
344
344
|
...mockConfig.aiCommit!,
|
|
345
345
|
language: "en-US" as const,
|
|
346
|
+
detailedDescription: false, // 使用简洁模式测试语言
|
|
346
347
|
},
|
|
347
348
|
};
|
|
348
349
|
|
|
@@ -437,6 +438,240 @@ describe("AI Service 模块测试", () => {
|
|
|
437
438
|
|
|
438
439
|
expect(body.max_tokens).toBe(500);
|
|
439
440
|
});
|
|
441
|
+
|
|
442
|
+
it("默认应该启用详细描述模式", async () => {
|
|
443
|
+
const defaultConfig = {
|
|
444
|
+
...mockConfig,
|
|
445
|
+
aiCommit: {
|
|
446
|
+
...mockConfig.aiCommit!,
|
|
447
|
+
// 不设置 detailedDescription 和 maxTokens,测试默认行为
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
// 移除 maxTokens 设置
|
|
451
|
+
delete defaultConfig.aiCommit!.maxTokens;
|
|
452
|
+
|
|
453
|
+
vi.mocked(execOutput).mockReturnValue("diff --git a/file.ts b/file.ts");
|
|
454
|
+
|
|
455
|
+
const mockFetch = vi.mocked(fetch);
|
|
456
|
+
mockFetch.mockResolvedValue({
|
|
457
|
+
ok: true,
|
|
458
|
+
json: async () => ({
|
|
459
|
+
choices: [
|
|
460
|
+
{
|
|
461
|
+
message: {
|
|
462
|
+
content: "feat(auth): 添加用户登录功能\n\n- 实现用户名密码登录接口\n- 添加登录状态验证中间件",
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
}),
|
|
467
|
+
} as Response);
|
|
468
|
+
|
|
469
|
+
const result = await generateAICommitMessage(defaultConfig);
|
|
470
|
+
|
|
471
|
+
expect(result).toContain("feat(auth): 添加用户登录功能");
|
|
472
|
+
expect(result).toContain("- 实现用户名密码登录接口");
|
|
473
|
+
|
|
474
|
+
// 验证使用了详细描述的 prompt
|
|
475
|
+
const callArgs = mockFetch.mock.calls[0][1] as RequestInit;
|
|
476
|
+
const body = JSON.parse(callArgs.body as string);
|
|
477
|
+
const prompt = body.messages[0].content;
|
|
478
|
+
|
|
479
|
+
expect(prompt).toContain("详细描述:列出主要修改点");
|
|
480
|
+
expect(body.max_tokens).toBe(400); // 详细模式的 token 数
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("详细描述模式应该生成包含修改点的 commit message", async () => {
|
|
484
|
+
const detailedConfig = {
|
|
485
|
+
...mockConfig,
|
|
486
|
+
aiCommit: {
|
|
487
|
+
...mockConfig.aiCommit!,
|
|
488
|
+
detailedDescription: true,
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
vi.mocked(execOutput).mockReturnValue("diff --git a/file.ts b/file.ts");
|
|
493
|
+
|
|
494
|
+
const mockFetch = vi.mocked(fetch);
|
|
495
|
+
mockFetch.mockResolvedValue({
|
|
496
|
+
ok: true,
|
|
497
|
+
json: async () => ({
|
|
498
|
+
choices: [
|
|
499
|
+
{
|
|
500
|
+
message: {
|
|
501
|
+
content: "feat(auth): 添加用户登录功能\n\n- 实现用户名密码登录接口\n- 添加登录状态验证中间件\n- 完善登录错误处理逻辑",
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
}),
|
|
506
|
+
} as Response);
|
|
507
|
+
|
|
508
|
+
const result = await generateAICommitMessage(detailedConfig);
|
|
509
|
+
|
|
510
|
+
expect(result).toContain("feat(auth): 添加用户登录功能");
|
|
511
|
+
expect(result).toContain("- 实现用户名密码登录接口");
|
|
512
|
+
expect(result).toContain("- 添加登录状态验证中间件");
|
|
513
|
+
expect(result).toContain("- 完善登录错误处理逻辑");
|
|
514
|
+
|
|
515
|
+
// 验证 prompt 包含详细描述的指令
|
|
516
|
+
const callArgs = mockFetch.mock.calls[0][1] as RequestInit;
|
|
517
|
+
const body = JSON.parse(callArgs.body as string);
|
|
518
|
+
const prompt = body.messages[0].content;
|
|
519
|
+
|
|
520
|
+
expect(prompt).toContain("详细描述:列出主要修改点");
|
|
521
|
+
expect(prompt).toContain("以 \"- \" 开头");
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it("详细描述模式应该使用更大的 maxTokens", async () => {
|
|
525
|
+
const detailedConfig = {
|
|
526
|
+
...mockConfig,
|
|
527
|
+
aiCommit: {
|
|
528
|
+
...mockConfig.aiCommit!,
|
|
529
|
+
// 不设置 detailedDescription 和 maxTokens,使用默认值
|
|
530
|
+
},
|
|
531
|
+
};
|
|
532
|
+
// 移除 maxTokens 设置
|
|
533
|
+
delete detailedConfig.aiCommit!.maxTokens;
|
|
534
|
+
|
|
535
|
+
vi.mocked(execOutput).mockReturnValue("diff --git a/file.ts b/file.ts");
|
|
536
|
+
|
|
537
|
+
const mockFetch = vi.mocked(fetch);
|
|
538
|
+
mockFetch.mockResolvedValue({
|
|
539
|
+
ok: true,
|
|
540
|
+
json: async () => ({
|
|
541
|
+
choices: [
|
|
542
|
+
{
|
|
543
|
+
message: {
|
|
544
|
+
content: "feat: add feature\n\n- change 1\n- change 2",
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
],
|
|
548
|
+
}),
|
|
549
|
+
} as Response);
|
|
550
|
+
|
|
551
|
+
await generateAICommitMessage(detailedConfig);
|
|
552
|
+
|
|
553
|
+
const callArgs = mockFetch.mock.calls[0][1] as RequestInit;
|
|
554
|
+
const body = JSON.parse(callArgs.body as string);
|
|
555
|
+
|
|
556
|
+
// 默认启用详细描述模式,应该使用 400 tokens
|
|
557
|
+
expect(body.max_tokens).toBe(400);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("详细描述模式应该允许更长的 diff", async () => {
|
|
561
|
+
const detailedConfig = {
|
|
562
|
+
...mockConfig,
|
|
563
|
+
aiCommit: {
|
|
564
|
+
...mockConfig.aiCommit!,
|
|
565
|
+
// 不设置 detailedDescription,使用默认值 true
|
|
566
|
+
},
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
const longDiff = "a".repeat(5500); // 超过简洁模式的 4000 限制,但在详细模式的 6000 限制内
|
|
570
|
+
vi.mocked(execOutput).mockReturnValue(longDiff);
|
|
571
|
+
|
|
572
|
+
const mockFetch = vi.mocked(fetch);
|
|
573
|
+
mockFetch.mockResolvedValue({
|
|
574
|
+
ok: true,
|
|
575
|
+
json: async () => ({
|
|
576
|
+
choices: [
|
|
577
|
+
{
|
|
578
|
+
message: {
|
|
579
|
+
content: "feat: update",
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
],
|
|
583
|
+
}),
|
|
584
|
+
} as Response);
|
|
585
|
+
|
|
586
|
+
await generateAICommitMessage(detailedConfig);
|
|
587
|
+
|
|
588
|
+
const callArgs = mockFetch.mock.calls[0][1] as RequestInit;
|
|
589
|
+
const body = JSON.parse(callArgs.body as string);
|
|
590
|
+
const prompt = body.messages[0].content;
|
|
591
|
+
|
|
592
|
+
// 在详细模式下,5500 字符的 diff 不应该被截断
|
|
593
|
+
expect(prompt).toContain(longDiff);
|
|
594
|
+
expect(prompt).not.toContain("...");
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it("简洁模式应该生成简短的 commit message", async () => {
|
|
598
|
+
const simpleConfig = {
|
|
599
|
+
...mockConfig,
|
|
600
|
+
aiCommit: {
|
|
601
|
+
...mockConfig.aiCommit!,
|
|
602
|
+
detailedDescription: false,
|
|
603
|
+
},
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
vi.mocked(execOutput).mockReturnValue("diff --git a/file.ts b/file.ts");
|
|
607
|
+
|
|
608
|
+
const mockFetch = vi.mocked(fetch);
|
|
609
|
+
mockFetch.mockResolvedValue({
|
|
610
|
+
ok: true,
|
|
611
|
+
json: async () => ({
|
|
612
|
+
choices: [
|
|
613
|
+
{
|
|
614
|
+
message: {
|
|
615
|
+
content: "feat(auth): 添加用户登录功能",
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
],
|
|
619
|
+
}),
|
|
620
|
+
} as Response);
|
|
621
|
+
|
|
622
|
+
const result = await generateAICommitMessage(simpleConfig);
|
|
623
|
+
|
|
624
|
+
expect(result).toBe("feat(auth): 添加用户登录功能");
|
|
625
|
+
|
|
626
|
+
// 验证 prompt 不包含详细描述的指令
|
|
627
|
+
const callArgs = mockFetch.mock.calls[0][1] as RequestInit;
|
|
628
|
+
const body = JSON.parse(callArgs.body as string);
|
|
629
|
+
const prompt = body.messages[0].content;
|
|
630
|
+
|
|
631
|
+
expect(prompt).not.toContain("详细描述:列出主要修改点");
|
|
632
|
+
expect(prompt).toContain("只返回一条 commit message");
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it("英文详细描述模式应该使用英文指令", async () => {
|
|
636
|
+
const enDetailedConfig = {
|
|
637
|
+
...mockConfig,
|
|
638
|
+
aiCommit: {
|
|
639
|
+
...mockConfig.aiCommit!,
|
|
640
|
+
language: "en-US" as const,
|
|
641
|
+
detailedDescription: true,
|
|
642
|
+
},
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
vi.mocked(execOutput).mockReturnValue("diff --git a/file.ts b/file.ts");
|
|
646
|
+
|
|
647
|
+
const mockFetch = vi.mocked(fetch);
|
|
648
|
+
mockFetch.mockResolvedValue({
|
|
649
|
+
ok: true,
|
|
650
|
+
json: async () => ({
|
|
651
|
+
choices: [
|
|
652
|
+
{
|
|
653
|
+
message: {
|
|
654
|
+
content: "feat(auth): add user login functionality\n\n- Implement username/password login API\n- Add login status validation middleware",
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
],
|
|
658
|
+
}),
|
|
659
|
+
} as Response);
|
|
660
|
+
|
|
661
|
+
const result = await generateAICommitMessage(enDetailedConfig);
|
|
662
|
+
|
|
663
|
+
expect(result).toContain("feat(auth): add user login functionality");
|
|
664
|
+
expect(result).toContain("- Implement username/password login API");
|
|
665
|
+
|
|
666
|
+
// 验证 prompt 使用英文指令
|
|
667
|
+
const callArgs = mockFetch.mock.calls[0][1] as RequestInit;
|
|
668
|
+
const body = JSON.parse(callArgs.body as string);
|
|
669
|
+
const prompt = body.messages[0].content;
|
|
670
|
+
|
|
671
|
+
expect(prompt).toContain("Detailed description: List main changes");
|
|
672
|
+
expect(prompt).toContain("starting with \"- \"");
|
|
673
|
+
expect(prompt).not.toContain("详细描述");
|
|
674
|
+
});
|
|
440
675
|
});
|
|
441
676
|
|
|
442
677
|
describe("Ollama 特殊情况", () => {
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { showHelp } from "../src/commands/help.js";
|
|
3
|
+
import { TODAY } from "../src/utils.js";
|
|
4
|
+
|
|
5
|
+
describe("Help 模块测试", () => {
|
|
6
|
+
describe("showHelp 函数", () => {
|
|
7
|
+
it("应该返回完整的帮助信息", () => {
|
|
8
|
+
const helpText = showHelp();
|
|
9
|
+
|
|
10
|
+
expect(helpText).toContain("分支命令:");
|
|
11
|
+
expect(helpText).toContain("Tag 命令:");
|
|
12
|
+
expect(helpText).toContain("发布命令:");
|
|
13
|
+
expect(helpText).toContain("配置命令:");
|
|
14
|
+
expect(helpText).toContain("更新命令:");
|
|
15
|
+
expect(helpText).toContain("清理命令:");
|
|
16
|
+
expect(helpText).toContain("Stash 命令:");
|
|
17
|
+
expect(helpText).toContain("Commit 命令:");
|
|
18
|
+
expect(helpText).toContain("示例:");
|
|
19
|
+
expect(helpText).toContain("分支命名格式:");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("应该包含所有主要命令", () => {
|
|
23
|
+
const helpText = showHelp();
|
|
24
|
+
|
|
25
|
+
// 分支命令
|
|
26
|
+
expect(helpText).toContain("gw feature");
|
|
27
|
+
expect(helpText).toContain("gw feat");
|
|
28
|
+
expect(helpText).toContain("gw f");
|
|
29
|
+
expect(helpText).toContain("gw hotfix");
|
|
30
|
+
expect(helpText).toContain("gw fix");
|
|
31
|
+
expect(helpText).toContain("gw h");
|
|
32
|
+
expect(helpText).toContain("gw delete");
|
|
33
|
+
expect(helpText).toContain("gw del");
|
|
34
|
+
expect(helpText).toContain("gw d");
|
|
35
|
+
|
|
36
|
+
// Tag 命令
|
|
37
|
+
expect(helpText).toContain("gw tags");
|
|
38
|
+
expect(helpText).toContain("gw ts");
|
|
39
|
+
expect(helpText).toContain("gw tag");
|
|
40
|
+
expect(helpText).toContain("gw t");
|
|
41
|
+
expect(helpText).toContain("gw tag:delete");
|
|
42
|
+
expect(helpText).toContain("gw td");
|
|
43
|
+
expect(helpText).toContain("gw tag:update");
|
|
44
|
+
expect(helpText).toContain("gw tu");
|
|
45
|
+
|
|
46
|
+
// 其他命令
|
|
47
|
+
expect(helpText).toContain("gw release");
|
|
48
|
+
expect(helpText).toContain("gw r");
|
|
49
|
+
expect(helpText).toContain("gw init");
|
|
50
|
+
expect(helpText).toContain("gw update");
|
|
51
|
+
expect(helpText).toContain("gw upt");
|
|
52
|
+
expect(helpText).toContain("gw clean");
|
|
53
|
+
expect(helpText).toContain("gw stash");
|
|
54
|
+
expect(helpText).toContain("gw s");
|
|
55
|
+
expect(helpText).toContain("gw st");
|
|
56
|
+
expect(helpText).toContain("gw commit");
|
|
57
|
+
expect(helpText).toContain("gw c");
|
|
58
|
+
expect(helpText).toContain("gw cm");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("应该包含命令描述", () => {
|
|
62
|
+
const helpText = showHelp();
|
|
63
|
+
|
|
64
|
+
expect(helpText).toContain("创建 feature 分支");
|
|
65
|
+
expect(helpText).toContain("创建 hotfix 分支");
|
|
66
|
+
expect(helpText).toContain("删除本地/远程分支");
|
|
67
|
+
expect(helpText).toContain("列出所有 tag");
|
|
68
|
+
expect(helpText).toContain("交互式选择版本类型并创建 tag");
|
|
69
|
+
expect(helpText).toContain("删除 tag");
|
|
70
|
+
expect(helpText).toContain("重命名 tag");
|
|
71
|
+
expect(helpText).toContain("交互式选择版本号并更新 package.json");
|
|
72
|
+
expect(helpText).toContain("初始化配置文件");
|
|
73
|
+
expect(helpText).toContain("检查并更新到最新版本");
|
|
74
|
+
expect(helpText).toContain("清理缓存文件");
|
|
75
|
+
expect(helpText).toContain("交互式管理 stash");
|
|
76
|
+
expect(helpText).toContain("交互式提交");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("应该包含使用示例", () => {
|
|
80
|
+
const helpText = showHelp();
|
|
81
|
+
|
|
82
|
+
expect(helpText).toContain("gw f --base develop");
|
|
83
|
+
expect(helpText).toContain("gw h --base release");
|
|
84
|
+
expect(helpText).toContain("gw d feature/xxx");
|
|
85
|
+
expect(helpText).toContain("gw ts v");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("应该包含分支命名格式说明", () => {
|
|
89
|
+
const helpText = showHelp();
|
|
90
|
+
|
|
91
|
+
expect(helpText).toContain("feature/20260111-<Story ID>-<描述>");
|
|
92
|
+
expect(helpText).toContain("hotfix/20260111-<Issue ID>-<描述>");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("应该包含配置文件说明", () => {
|
|
96
|
+
const helpText = showHelp();
|
|
97
|
+
|
|
98
|
+
expect(helpText).toContain("全局配置: ~/.gwrc.json");
|
|
99
|
+
expect(helpText).toContain("项目配置: .gwrc.json");
|
|
100
|
+
expect(helpText).toContain("所有项目生效");
|
|
101
|
+
expect(helpText).toContain("仅当前项目");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("应该包含别名说明", () => {
|
|
105
|
+
const helpText = showHelp();
|
|
106
|
+
|
|
107
|
+
expect(helpText).toContain("同上 (别名)");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("应该包含参数说明", () => {
|
|
111
|
+
const helpText = showHelp();
|
|
112
|
+
|
|
113
|
+
expect(helpText).toContain("[--base <branch>]");
|
|
114
|
+
expect(helpText).toContain("[branch]");
|
|
115
|
+
expect(helpText).toContain("[prefix]");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("应该是非空字符串", () => {
|
|
119
|
+
const helpText = showHelp();
|
|
120
|
+
|
|
121
|
+
expect(typeof helpText).toBe("string");
|
|
122
|
+
expect(helpText.length).toBeGreaterThan(0);
|
|
123
|
+
expect(helpText.trim().length).toBeGreaterThan(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("应该包含实际的日期格式", () => {
|
|
127
|
+
const helpText = showHelp();
|
|
128
|
+
|
|
129
|
+
// 检查是否包含实际的日期格式(YYYYMMDD)
|
|
130
|
+
expect(helpText).toMatch(/feature\/\d{8}-<Story ID>-<描述>/);
|
|
131
|
+
expect(helpText).toMatch(/hotfix\/\d{8}-<Issue ID>-<描述>/);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|