@zjex/git-workflow 0.3.9 → 0.3.10
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/dist/index.js +56 -4
- package/package.json +1 -1
- package/src/ai-service.ts +66 -33
- package/src/index.ts +66 -2
- package/tests/clean.test.ts +293 -0
- package/tests/COVERAGE_REPORT.md +0 -222
- package/tests/QUICK_START.md +0 -242
- package/tests/TEST_SUMMARY.md +0 -330
package/dist/index.js
CHANGED
|
@@ -2132,7 +2132,12 @@ async function generateAICommitMessage(config2) {
|
|
|
2132
2132
|
}
|
|
2133
2133
|
const maxDiffLength = detailedDescription ? 6e3 : 4e3;
|
|
2134
2134
|
const truncatedDiff = diff.length > maxDiffLength ? diff.slice(0, maxDiffLength) + "\n..." : diff;
|
|
2135
|
-
const prompt = buildPrompt(
|
|
2135
|
+
const prompt = buildPrompt(
|
|
2136
|
+
truncatedDiff,
|
|
2137
|
+
language,
|
|
2138
|
+
detailedDescription,
|
|
2139
|
+
useEmoji
|
|
2140
|
+
);
|
|
2136
2141
|
const providerInfo = AI_PROVIDERS[provider];
|
|
2137
2142
|
if (!providerInfo) {
|
|
2138
2143
|
throw new Error(`\u4E0D\u652F\u6301\u7684 AI \u63D0\u4F9B\u5546: ${provider}`);
|
|
@@ -2944,7 +2949,7 @@ process.on("SIGTERM", () => {
|
|
|
2944
2949
|
console.log("");
|
|
2945
2950
|
process.exit(0);
|
|
2946
2951
|
});
|
|
2947
|
-
var version = true ? "0.3.
|
|
2952
|
+
var version = true ? "0.3.10" : "0.0.0-dev";
|
|
2948
2953
|
async function mainMenu() {
|
|
2949
2954
|
console.log(
|
|
2950
2955
|
colors.green(`
|
|
@@ -3137,11 +3142,58 @@ cli.command("log", "\u4EA4\u4E92\u5F0FGit\u65E5\u5FD7\u67E5\u770B (\u5206\u9875\
|
|
|
3137
3142
|
if (options.limit) logOptions.limit = parseInt(options.limit);
|
|
3138
3143
|
return log(logOptions);
|
|
3139
3144
|
});
|
|
3140
|
-
cli.command("clean", "\u6E05\u7406\u7F13\u5B58\u6587\u4EF6").action(async () => {
|
|
3145
|
+
cli.command("clean", "\u6E05\u7406\u7F13\u5B58\u548C\u4E34\u65F6\u6587\u4EF6").action(async () => {
|
|
3141
3146
|
const { clearUpdateCache: clearUpdateCache3 } = await Promise.resolve().then(() => (init_update_notifier(), update_notifier_exports));
|
|
3147
|
+
const { existsSync: existsSync5, unlinkSync: unlinkSync4, readdirSync } = await import("fs");
|
|
3148
|
+
const { homedir: homedir5, tmpdir: tmpdir2 } = await import("os");
|
|
3149
|
+
const { join: join6 } = await import("path");
|
|
3150
|
+
const { select: select9 } = await import("@inquirer/prompts");
|
|
3151
|
+
let cleanedCount = 0;
|
|
3152
|
+
let deletedGlobalConfig = false;
|
|
3153
|
+
const globalConfig = join6(homedir5(), ".gwrc.json");
|
|
3154
|
+
const hasGlobalConfig = existsSync5(globalConfig);
|
|
3155
|
+
if (hasGlobalConfig) {
|
|
3156
|
+
const shouldDeleteConfig = await select9({
|
|
3157
|
+
message: "\u68C0\u6D4B\u5230\u5168\u5C40\u914D\u7F6E\u6587\u4EF6\uFF0C\u662F\u5426\u5220\u9664\uFF1F",
|
|
3158
|
+
choices: [
|
|
3159
|
+
{ name: "\u5426\uFF0C\u4FDD\u7559\u914D\u7F6E\u6587\u4EF6", value: false },
|
|
3160
|
+
{ name: "\u662F\uFF0C\u5220\u9664\u914D\u7F6E\u6587\u4EF6", value: true }
|
|
3161
|
+
],
|
|
3162
|
+
theme
|
|
3163
|
+
});
|
|
3164
|
+
if (shouldDeleteConfig) {
|
|
3165
|
+
try {
|
|
3166
|
+
unlinkSync4(globalConfig);
|
|
3167
|
+
cleanedCount++;
|
|
3168
|
+
deletedGlobalConfig = true;
|
|
3169
|
+
} catch {
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3142
3173
|
clearUpdateCache3();
|
|
3174
|
+
cleanedCount++;
|
|
3175
|
+
try {
|
|
3176
|
+
const tmpDir = tmpdir2();
|
|
3177
|
+
const files = readdirSync(tmpDir);
|
|
3178
|
+
const gwTmpFiles = files.filter((f) => f.startsWith(".gw-commit-msg-"));
|
|
3179
|
+
for (const file of gwTmpFiles) {
|
|
3180
|
+
try {
|
|
3181
|
+
unlinkSync4(join6(tmpDir, file));
|
|
3182
|
+
cleanedCount++;
|
|
3183
|
+
} catch {
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
} catch {
|
|
3187
|
+
}
|
|
3143
3188
|
console.log("");
|
|
3144
|
-
console.log(colors.green(
|
|
3189
|
+
console.log(colors.green(`\u2714 \u5DF2\u6E05\u7406 ${cleanedCount} \u4E2A\u6587\u4EF6`));
|
|
3190
|
+
if (deletedGlobalConfig) {
|
|
3191
|
+
console.log("");
|
|
3192
|
+
console.log(colors.yellow("\u26A0\uFE0F \u5168\u5C40\u914D\u7F6E\u6587\u4EF6\u5DF2\u5220\u9664"));
|
|
3193
|
+
console.log(
|
|
3194
|
+
colors.dim(` \u5982\u9700\u91CD\u65B0\u914D\u7F6E\uFF0C\u8BF7\u8FD0\u884C: ${colors.cyan("gw init")}`)
|
|
3195
|
+
);
|
|
3196
|
+
}
|
|
3145
3197
|
console.log("");
|
|
3146
3198
|
});
|
|
3147
3199
|
cli.option("-v, --version", "\u663E\u793A\u7248\u672C\u53F7");
|
package/package.json
CHANGED
package/src/ai-service.ts
CHANGED
|
@@ -60,7 +60,12 @@ function getGitDiff(): string {
|
|
|
60
60
|
/**
|
|
61
61
|
* 构建 AI prompt
|
|
62
62
|
*/
|
|
63
|
-
function buildPrompt(
|
|
63
|
+
function buildPrompt(
|
|
64
|
+
diff: string,
|
|
65
|
+
language: string,
|
|
66
|
+
detailedDescription: boolean = false,
|
|
67
|
+
useEmoji: boolean = true
|
|
68
|
+
): string {
|
|
64
69
|
const isZh = language === "zh-CN";
|
|
65
70
|
|
|
66
71
|
if (detailedDescription) {
|
|
@@ -69,7 +74,7 @@ function buildPrompt(diff: string, language: string, detailedDescription: boolea
|
|
|
69
74
|
? `你是一个专业的 Git commit message 生成助手。请根据提供的 git diff 生成符合 Conventional Commits 规范的详细 commit message。
|
|
70
75
|
|
|
71
76
|
格式要求:
|
|
72
|
-
1. 第一行:${useEmoji ?
|
|
77
|
+
1. 第一行:${useEmoji ? "<emoji> " : ""}<type>(<scope>): <subject>
|
|
73
78
|
2. 空行
|
|
74
79
|
3. 详细描述:列出主要修改点,每个修改点一行,以 "- " 开头
|
|
75
80
|
|
|
@@ -80,7 +85,9 @@ function buildPrompt(diff: string, language: string, detailedDescription: boolea
|
|
|
80
85
|
- 详细描述要列出 3-6 个主要修改点,每个修改点简洁明了
|
|
81
86
|
- 如果修改较少,可以只列出 2-3 个修改点
|
|
82
87
|
- 不要有其他解释或多余内容
|
|
83
|
-
${
|
|
88
|
+
${
|
|
89
|
+
useEmoji
|
|
90
|
+
? `
|
|
84
91
|
Emoji 映射规则:
|
|
85
92
|
- feat: ✨ (新功能)
|
|
86
93
|
- fix: 🐛 (修复Bug)
|
|
@@ -92,10 +99,12 @@ Emoji 映射规则:
|
|
|
92
99
|
- build: 📦 (构建)
|
|
93
100
|
- ci: 👷 (CI/CD)
|
|
94
101
|
- chore: 🔧 (其他杂项)
|
|
95
|
-
- revert: ⏪ (回滚)`
|
|
102
|
+
- revert: ⏪ (回滚)`
|
|
103
|
+
: ""
|
|
104
|
+
}
|
|
96
105
|
|
|
97
106
|
示例:
|
|
98
|
-
${useEmoji ?
|
|
107
|
+
${useEmoji ? "✨ " : ""}feat(auth): 添加用户登录功能
|
|
99
108
|
|
|
100
109
|
- 实现用户名密码登录接口
|
|
101
110
|
- 添加登录状态验证中间件
|
|
@@ -104,7 +113,7 @@ ${useEmoji ? '✨ ' : ''}feat(auth): 添加用户登录功能
|
|
|
104
113
|
: `You are a professional Git commit message generator. Generate a detailed commit message following Conventional Commits specification based on the provided git diff.
|
|
105
114
|
|
|
106
115
|
Format requirements:
|
|
107
|
-
1. First line: ${useEmoji ?
|
|
116
|
+
1. First line: ${useEmoji ? "<emoji> " : ""}<type>(<scope>): <subject>
|
|
108
117
|
2. Empty line
|
|
109
118
|
3. Detailed description: List main changes, one per line, starting with "- "
|
|
110
119
|
|
|
@@ -115,7 +124,9 @@ Rules:
|
|
|
115
124
|
- Detailed description should list 3-6 main changes, each change should be concise
|
|
116
125
|
- If changes are minimal, list 2-3 changes
|
|
117
126
|
- No explanations or extra content
|
|
118
|
-
${
|
|
127
|
+
${
|
|
128
|
+
useEmoji
|
|
129
|
+
? `
|
|
119
130
|
Emoji mapping rules:
|
|
120
131
|
- feat: ✨ (new feature)
|
|
121
132
|
- fix: 🐛 (bug fix)
|
|
@@ -127,10 +138,12 @@ Emoji mapping rules:
|
|
|
127
138
|
- build: 📦 (build)
|
|
128
139
|
- ci: 👷 (CI/CD)
|
|
129
140
|
- chore: 🔧 (chore)
|
|
130
|
-
- revert: ⏪ (revert)`
|
|
141
|
+
- revert: ⏪ (revert)`
|
|
142
|
+
: ""
|
|
143
|
+
}
|
|
131
144
|
|
|
132
145
|
Example:
|
|
133
|
-
${useEmoji ?
|
|
146
|
+
${useEmoji ? "✨ " : ""}feat(auth): add user login functionality
|
|
134
147
|
|
|
135
148
|
- Implement username/password login API
|
|
136
149
|
- Add login status validation middleware
|
|
@@ -148,13 +161,15 @@ ${useEmoji ? '✨ ' : ''}feat(auth): add user login functionality
|
|
|
148
161
|
? `你是一个专业的 Git commit message 生成助手。请根据提供的 git diff 生成符合 Conventional Commits 规范的 commit message。
|
|
149
162
|
|
|
150
163
|
规则:
|
|
151
|
-
1. 格式:${useEmoji ?
|
|
164
|
+
1. 格式:${useEmoji ? "<emoji> " : ""}<type>(<scope>): <subject>
|
|
152
165
|
2. type 必须是以下之一:feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
153
166
|
3. scope 是可选的,表示影响范围
|
|
154
167
|
4. subject 用中文描述,简洁明了,不超过 50 字
|
|
155
168
|
5. 只返回一条 commit message,即使有多个文件改动也要总结成一条
|
|
156
169
|
6. 不要有其他解释或多余内容
|
|
157
|
-
${
|
|
170
|
+
${
|
|
171
|
+
useEmoji
|
|
172
|
+
? `
|
|
158
173
|
Emoji 映射规则:
|
|
159
174
|
- feat: ✨ (新功能)
|
|
160
175
|
- fix: 🐛 (修复Bug)
|
|
@@ -166,22 +181,26 @@ Emoji 映射规则:
|
|
|
166
181
|
- build: 📦 (构建)
|
|
167
182
|
- ci: 👷 (CI/CD)
|
|
168
183
|
- chore: 🔧 (其他杂项)
|
|
169
|
-
- revert: ⏪ (回滚)`
|
|
184
|
+
- revert: ⏪ (回滚)`
|
|
185
|
+
: ""
|
|
186
|
+
}
|
|
170
187
|
|
|
171
188
|
示例:
|
|
172
|
-
- ${useEmoji ?
|
|
173
|
-
- ${useEmoji ?
|
|
174
|
-
- ${useEmoji ?
|
|
189
|
+
- ${useEmoji ? "✨ " : ""}feat(auth): 添加用户登录功能
|
|
190
|
+
- ${useEmoji ? "🐛 " : ""}fix(api): 修复数据获取失败的问题
|
|
191
|
+
- ${useEmoji ? "📝 " : ""}docs(readme): 更新安装说明`
|
|
175
192
|
: `You are a professional Git commit message generator. Generate a commit message following Conventional Commits specification based on the provided git diff.
|
|
176
193
|
|
|
177
194
|
Rules:
|
|
178
|
-
1. Format: ${useEmoji ?
|
|
195
|
+
1. Format: ${useEmoji ? "<emoji> " : ""}<type>(<scope>): <subject>
|
|
179
196
|
2. type must be one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
180
197
|
3. scope is optional, indicates the affected area
|
|
181
198
|
4. subject should be concise, no more than 50 characters
|
|
182
199
|
5. Return only ONE commit message, even if multiple files are changed, summarize into one message
|
|
183
200
|
6. No explanations or extra content
|
|
184
|
-
${
|
|
201
|
+
${
|
|
202
|
+
useEmoji
|
|
203
|
+
? `
|
|
185
204
|
Emoji mapping rules:
|
|
186
205
|
- feat: ✨ (new feature)
|
|
187
206
|
- fix: 🐛 (bug fix)
|
|
@@ -193,12 +212,14 @@ Emoji mapping rules:
|
|
|
193
212
|
- build: 📦 (build)
|
|
194
213
|
- ci: 👷 (CI/CD)
|
|
195
214
|
- chore: 🔧 (chore)
|
|
196
|
-
- revert: ⏪ (revert)`
|
|
215
|
+
- revert: ⏪ (revert)`
|
|
216
|
+
: ""
|
|
217
|
+
}
|
|
197
218
|
|
|
198
219
|
Examples:
|
|
199
|
-
- ${useEmoji ?
|
|
200
|
-
- ${useEmoji ?
|
|
201
|
-
- ${useEmoji ?
|
|
220
|
+
- ${useEmoji ? "✨ " : ""}feat(auth): add user login functionality
|
|
221
|
+
- ${useEmoji ? "🐛 " : ""}fix(api): resolve data fetching failure
|
|
222
|
+
- ${useEmoji ? "📝 " : ""}docs(readme): update installation guide`;
|
|
202
223
|
|
|
203
224
|
const userPrompt = isZh
|
|
204
225
|
? `请根据以下 git diff 生成 commit message:\n\n${diff}`
|
|
@@ -347,25 +368,29 @@ async function callOllamaAPI(
|
|
|
347
368
|
*/
|
|
348
369
|
function cleanAIResponse(response: string): string {
|
|
349
370
|
// 移除开头的特殊字符(如 ...、```、等)
|
|
350
|
-
let cleaned = response.replace(/^[.\s`~-]+/,
|
|
351
|
-
|
|
371
|
+
let cleaned = response.replace(/^[.\s`~-]+/, "").trim();
|
|
372
|
+
|
|
352
373
|
// 移除结尾的特殊字符
|
|
353
|
-
cleaned = cleaned.replace(/[.\s`~-]+$/,
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
374
|
+
cleaned = cleaned.replace(/[.\s`~-]+$/, "").trim();
|
|
375
|
+
|
|
376
|
+
// 分割成行并过滤空行
|
|
377
|
+
const lines = cleaned
|
|
378
|
+
.split("\n")
|
|
379
|
+
.map((line) => line.trim())
|
|
380
|
+
.filter((line) => line);
|
|
381
|
+
|
|
357
382
|
// 移除重复的行
|
|
358
383
|
const uniqueLines = [];
|
|
359
384
|
const seen = new Set();
|
|
360
|
-
|
|
385
|
+
|
|
361
386
|
for (const line of lines) {
|
|
362
387
|
if (!seen.has(line)) {
|
|
363
388
|
seen.add(line);
|
|
364
389
|
uniqueLines.push(line);
|
|
365
390
|
}
|
|
366
391
|
}
|
|
367
|
-
|
|
368
|
-
return uniqueLines.join(
|
|
392
|
+
|
|
393
|
+
return uniqueLines.join("\n");
|
|
369
394
|
}
|
|
370
395
|
|
|
371
396
|
/**
|
|
@@ -379,9 +404,12 @@ export async function generateAICommitMessage(
|
|
|
379
404
|
const language = aiConfig.language || "zh-CN";
|
|
380
405
|
const detailedDescription = aiConfig.detailedDescription !== false; // 默认启用详细描述
|
|
381
406
|
const maxTokens = aiConfig.maxTokens || (detailedDescription ? 400 : 200);
|
|
382
|
-
|
|
407
|
+
|
|
383
408
|
// AI emoji配置:优先使用aiCommit.useEmoji,如果未设置则使用全局useEmoji,默认true
|
|
384
|
-
const useEmoji =
|
|
409
|
+
const useEmoji =
|
|
410
|
+
aiConfig.useEmoji !== undefined
|
|
411
|
+
? aiConfig.useEmoji
|
|
412
|
+
: config.useEmoji !== false;
|
|
385
413
|
|
|
386
414
|
// 获取 git diff
|
|
387
415
|
const diff = getGitDiff();
|
|
@@ -395,7 +423,12 @@ export async function generateAICommitMessage(
|
|
|
395
423
|
diff.length > maxDiffLength ? diff.slice(0, maxDiffLength) + "\n..." : diff;
|
|
396
424
|
|
|
397
425
|
// 构建 prompt
|
|
398
|
-
const prompt = buildPrompt(
|
|
426
|
+
const prompt = buildPrompt(
|
|
427
|
+
truncatedDiff,
|
|
428
|
+
language,
|
|
429
|
+
detailedDescription,
|
|
430
|
+
useEmoji
|
|
431
|
+
);
|
|
399
432
|
|
|
400
433
|
// 根据提供商调用对应的 API
|
|
401
434
|
const providerInfo = AI_PROVIDERS[provider];
|
package/src/index.ts
CHANGED
|
@@ -364,11 +364,75 @@ cli
|
|
|
364
364
|
return log(logOptions);
|
|
365
365
|
});
|
|
366
366
|
|
|
367
|
-
cli.command("clean", "
|
|
367
|
+
cli.command("clean", "清理缓存和临时文件").action(async () => {
|
|
368
368
|
const { clearUpdateCache } = await import("./update-notifier.js");
|
|
369
|
+
const { existsSync, unlinkSync, readdirSync } = await import("fs");
|
|
370
|
+
const { homedir, tmpdir } = await import("os");
|
|
371
|
+
const { join } = await import("path");
|
|
372
|
+
const { select } = await import("@inquirer/prompts");
|
|
373
|
+
|
|
374
|
+
let cleanedCount = 0;
|
|
375
|
+
let deletedGlobalConfig = false;
|
|
376
|
+
|
|
377
|
+
// 检查全局配置文件是否存在
|
|
378
|
+
const globalConfig = join(homedir(), ".gwrc.json");
|
|
379
|
+
const hasGlobalConfig = existsSync(globalConfig);
|
|
380
|
+
|
|
381
|
+
// 如果有全局配置文件,询问是否删除
|
|
382
|
+
if (hasGlobalConfig) {
|
|
383
|
+
const shouldDeleteConfig = await select({
|
|
384
|
+
message: "检测到全局配置文件,是否删除?",
|
|
385
|
+
choices: [
|
|
386
|
+
{ name: "否,保留配置文件", value: false },
|
|
387
|
+
{ name: "是,删除配置文件", value: true },
|
|
388
|
+
],
|
|
389
|
+
theme,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
if (shouldDeleteConfig) {
|
|
393
|
+
try {
|
|
394
|
+
unlinkSync(globalConfig);
|
|
395
|
+
cleanedCount++;
|
|
396
|
+
deletedGlobalConfig = true;
|
|
397
|
+
} catch {
|
|
398
|
+
// 静默失败
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 1. 清理更新缓存
|
|
369
404
|
clearUpdateCache();
|
|
405
|
+
cleanedCount++;
|
|
406
|
+
|
|
407
|
+
// 2. 清理临时 commit 消息文件
|
|
408
|
+
try {
|
|
409
|
+
const tmpDir = tmpdir();
|
|
410
|
+
const files = readdirSync(tmpDir);
|
|
411
|
+
const gwTmpFiles = files.filter((f) => f.startsWith(".gw-commit-msg-"));
|
|
412
|
+
|
|
413
|
+
for (const file of gwTmpFiles) {
|
|
414
|
+
try {
|
|
415
|
+
unlinkSync(join(tmpDir, file));
|
|
416
|
+
cleanedCount++;
|
|
417
|
+
} catch {
|
|
418
|
+
// 静默失败
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
} catch {
|
|
422
|
+
// 静默失败
|
|
423
|
+
}
|
|
424
|
+
|
|
370
425
|
console.log("");
|
|
371
|
-
console.log(colors.green(
|
|
426
|
+
console.log(colors.green(`✔ 已清理 ${cleanedCount} 个文件`));
|
|
427
|
+
|
|
428
|
+
if (deletedGlobalConfig) {
|
|
429
|
+
console.log("");
|
|
430
|
+
console.log(colors.yellow("⚠️ 全局配置文件已删除"));
|
|
431
|
+
console.log(
|
|
432
|
+
colors.dim(` 如需重新配置,请运行: ${colors.cyan("gw init")}`)
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
372
436
|
console.log("");
|
|
373
437
|
});
|
|
374
438
|
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { existsSync, unlinkSync, readdirSync, writeFileSync } from "fs";
|
|
3
|
+
import { homedir, tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
|
|
6
|
+
// Mock 所有外部依赖
|
|
7
|
+
vi.mock("fs", () => ({
|
|
8
|
+
existsSync: vi.fn(),
|
|
9
|
+
unlinkSync: vi.fn(),
|
|
10
|
+
readdirSync: vi.fn(),
|
|
11
|
+
writeFileSync: vi.fn(),
|
|
12
|
+
readFileSync: vi.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock("os", () => ({
|
|
16
|
+
homedir: vi.fn(),
|
|
17
|
+
tmpdir: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock("path", () => ({
|
|
21
|
+
join: vi.fn((...args) => args.join("/")),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
vi.mock("@inquirer/prompts", () => ({
|
|
25
|
+
select: vi.fn(),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
vi.mock("../src/update-notifier.js", () => ({
|
|
29
|
+
clearUpdateCache: vi.fn(),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
describe("Clean 命令测试", () => {
|
|
33
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
34
|
+
const mockUnlinkSync = vi.mocked(unlinkSync);
|
|
35
|
+
const mockReaddirSync = vi.mocked(readdirSync);
|
|
36
|
+
const mockHomedir = vi.mocked(homedir);
|
|
37
|
+
const mockTmpdir = vi.mocked(tmpdir);
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
mockHomedir.mockReturnValue("/home/user");
|
|
42
|
+
mockTmpdir.mockReturnValue("/tmp");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
vi.restoreAllMocks();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("清理更新缓存", () => {
|
|
50
|
+
it("应该清理更新缓存文件", async () => {
|
|
51
|
+
const { clearUpdateCache } = await import("../src/update-notifier.js");
|
|
52
|
+
|
|
53
|
+
clearUpdateCache();
|
|
54
|
+
|
|
55
|
+
expect(clearUpdateCache).toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("清理全局配置文件", () => {
|
|
60
|
+
it("没有全局配置文件时不应该询问", async () => {
|
|
61
|
+
mockExistsSync.mockReturnValue(false);
|
|
62
|
+
mockReaddirSync.mockReturnValue([]);
|
|
63
|
+
|
|
64
|
+
const { select } = await import("@inquirer/prompts");
|
|
65
|
+
|
|
66
|
+
expect(mockExistsSync).not.toHaveBeenCalledWith("/home/user/.gwrc.json");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("有全局配置文件时应该询问是否删除", async () => {
|
|
70
|
+
const globalConfig = "/home/user/.gwrc.json";
|
|
71
|
+
mockExistsSync.mockReturnValue(true);
|
|
72
|
+
mockReaddirSync.mockReturnValue([]);
|
|
73
|
+
|
|
74
|
+
const { select } = await import("@inquirer/prompts");
|
|
75
|
+
vi.mocked(select).mockResolvedValue(false);
|
|
76
|
+
|
|
77
|
+
// 模拟检查全局配置文件
|
|
78
|
+
const hasGlobalConfig = mockExistsSync(globalConfig);
|
|
79
|
+
|
|
80
|
+
expect(hasGlobalConfig).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("选择删除时应该删除全局配置文件", async () => {
|
|
84
|
+
const globalConfig = "/home/user/.gwrc.json";
|
|
85
|
+
mockExistsSync.mockReturnValue(true);
|
|
86
|
+
mockReaddirSync.mockReturnValue([]);
|
|
87
|
+
|
|
88
|
+
const { select } = await import("@inquirer/prompts");
|
|
89
|
+
vi.mocked(select).mockResolvedValue(true);
|
|
90
|
+
|
|
91
|
+
// 模拟删除操作
|
|
92
|
+
if (mockExistsSync(globalConfig)) {
|
|
93
|
+
mockUnlinkSync(globalConfig);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
expect(mockUnlinkSync).toHaveBeenCalledWith(globalConfig);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("选择不删除时应该保留全局配置文件", async () => {
|
|
100
|
+
const globalConfig = "/home/user/.gwrc.json";
|
|
101
|
+
mockExistsSync.mockReturnValue(true);
|
|
102
|
+
mockReaddirSync.mockReturnValue([]);
|
|
103
|
+
|
|
104
|
+
const { select } = await import("@inquirer/prompts");
|
|
105
|
+
vi.mocked(select).mockResolvedValue(false);
|
|
106
|
+
|
|
107
|
+
// 模拟不删除的情况
|
|
108
|
+
const shouldDelete = await vi.mocked(select)();
|
|
109
|
+
|
|
110
|
+
if (!shouldDelete) {
|
|
111
|
+
expect(mockUnlinkSync).not.toHaveBeenCalledWith(globalConfig);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("清理临时 commit 文件", () => {
|
|
117
|
+
it("应该清理所有临时 commit 文件", () => {
|
|
118
|
+
mockReaddirSync.mockReturnValue([
|
|
119
|
+
".gw-commit-msg-123456",
|
|
120
|
+
".gw-commit-msg-789012",
|
|
121
|
+
"other-file.txt",
|
|
122
|
+
] as any);
|
|
123
|
+
|
|
124
|
+
const tmpDir = mockTmpdir();
|
|
125
|
+
const files = mockReaddirSync(tmpDir);
|
|
126
|
+
const gwTmpFiles = files.filter((f: string) =>
|
|
127
|
+
f.startsWith(".gw-commit-msg-")
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(gwTmpFiles).toHaveLength(2);
|
|
131
|
+
expect(gwTmpFiles).toContain(".gw-commit-msg-123456");
|
|
132
|
+
expect(gwTmpFiles).toContain(".gw-commit-msg-789012");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("应该忽略非 gw 临时文件", () => {
|
|
136
|
+
mockReaddirSync.mockReturnValue([
|
|
137
|
+
".gw-commit-msg-123456",
|
|
138
|
+
"other-temp-file",
|
|
139
|
+
".other-file",
|
|
140
|
+
] as any);
|
|
141
|
+
|
|
142
|
+
const tmpDir = mockTmpdir();
|
|
143
|
+
const files = mockReaddirSync(tmpDir);
|
|
144
|
+
const gwTmpFiles = files.filter((f: string) =>
|
|
145
|
+
f.startsWith(".gw-commit-msg-")
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
expect(gwTmpFiles).toHaveLength(1);
|
|
149
|
+
expect(gwTmpFiles).toContain(".gw-commit-msg-123456");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("没有临时文件时不应该报错", () => {
|
|
153
|
+
mockReaddirSync.mockReturnValue([]);
|
|
154
|
+
|
|
155
|
+
const tmpDir = mockTmpdir();
|
|
156
|
+
const files = mockReaddirSync(tmpDir);
|
|
157
|
+
const gwTmpFiles = files.filter((f: string) =>
|
|
158
|
+
f.startsWith(".gw-commit-msg-")
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
expect(gwTmpFiles).toHaveLength(0);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("应该删除找到的临时文件", () => {
|
|
165
|
+
mockReaddirSync.mockReturnValue([
|
|
166
|
+
".gw-commit-msg-123456",
|
|
167
|
+
".gw-commit-msg-789012",
|
|
168
|
+
] as any);
|
|
169
|
+
|
|
170
|
+
const tmpDir = mockTmpdir();
|
|
171
|
+
const files = mockReaddirSync(tmpDir);
|
|
172
|
+
const gwTmpFiles = files.filter((f: string) =>
|
|
173
|
+
f.startsWith(".gw-commit-msg-")
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
gwTmpFiles.forEach((file: string) => {
|
|
177
|
+
mockUnlinkSync(join(tmpDir, file));
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
expect(mockUnlinkSync).toHaveBeenCalledTimes(2);
|
|
181
|
+
expect(mockUnlinkSync).toHaveBeenCalledWith("/tmp/.gw-commit-msg-123456");
|
|
182
|
+
expect(mockUnlinkSync).toHaveBeenCalledWith("/tmp/.gw-commit-msg-789012");
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("清理统计", () => {
|
|
187
|
+
it("应该正确统计清理的文件数量", () => {
|
|
188
|
+
let cleanedCount = 0;
|
|
189
|
+
|
|
190
|
+
// 清理更新缓存
|
|
191
|
+
cleanedCount++;
|
|
192
|
+
|
|
193
|
+
// 清理全局配置
|
|
194
|
+
mockExistsSync.mockReturnValue(true);
|
|
195
|
+
if (mockExistsSync("/home/user/.gwrc.json")) {
|
|
196
|
+
cleanedCount++;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 清理临时文件
|
|
200
|
+
mockReaddirSync.mockReturnValue([
|
|
201
|
+
".gw-commit-msg-123456",
|
|
202
|
+
".gw-commit-msg-789012",
|
|
203
|
+
] as any);
|
|
204
|
+
const gwTmpFiles = mockReaddirSync("/tmp").filter((f: string) =>
|
|
205
|
+
f.startsWith(".gw-commit-msg-")
|
|
206
|
+
);
|
|
207
|
+
cleanedCount += gwTmpFiles.length;
|
|
208
|
+
|
|
209
|
+
expect(cleanedCount).toBe(4); // 1 缓存 + 1 配置 + 2 临时文件
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("没有全局配置时应该统计正确", () => {
|
|
213
|
+
let cleanedCount = 0;
|
|
214
|
+
|
|
215
|
+
// 清理更新缓存
|
|
216
|
+
cleanedCount++;
|
|
217
|
+
|
|
218
|
+
// 没有全局配置
|
|
219
|
+
mockExistsSync.mockReturnValue(false);
|
|
220
|
+
|
|
221
|
+
// 清理临时文件
|
|
222
|
+
mockReaddirSync.mockReturnValue([".gw-commit-msg-123456"] as any);
|
|
223
|
+
const gwTmpFiles = mockReaddirSync("/tmp").filter((f: string) =>
|
|
224
|
+
f.startsWith(".gw-commit-msg-")
|
|
225
|
+
);
|
|
226
|
+
cleanedCount += gwTmpFiles.length;
|
|
227
|
+
|
|
228
|
+
expect(cleanedCount).toBe(2); // 1 缓存 + 1 临时文件
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("没有任何文件时应该只清理缓存", () => {
|
|
232
|
+
let cleanedCount = 0;
|
|
233
|
+
|
|
234
|
+
// 清理更新缓存
|
|
235
|
+
cleanedCount++;
|
|
236
|
+
|
|
237
|
+
// 没有全局配置
|
|
238
|
+
mockExistsSync.mockReturnValue(false);
|
|
239
|
+
|
|
240
|
+
// 没有临时文件
|
|
241
|
+
mockReaddirSync.mockReturnValue([]);
|
|
242
|
+
|
|
243
|
+
expect(cleanedCount).toBe(1); // 只有缓存
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("错误处理", () => {
|
|
248
|
+
it("删除文件失败时应该静默处理", () => {
|
|
249
|
+
mockUnlinkSync.mockImplementation(() => {
|
|
250
|
+
throw new Error("Permission denied");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
expect(() => {
|
|
254
|
+
try {
|
|
255
|
+
mockUnlinkSync("/home/user/.gwrc.json");
|
|
256
|
+
} catch {
|
|
257
|
+
// 静默失败
|
|
258
|
+
}
|
|
259
|
+
}).not.toThrow();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("读取目录失败时应该静默处理", () => {
|
|
263
|
+
mockReaddirSync.mockImplementation(() => {
|
|
264
|
+
throw new Error("Directory not found");
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(() => {
|
|
268
|
+
try {
|
|
269
|
+
mockReaddirSync("/tmp");
|
|
270
|
+
} catch {
|
|
271
|
+
// 静默失败
|
|
272
|
+
}
|
|
273
|
+
}).not.toThrow();
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe("文件路径", () => {
|
|
278
|
+
it("应该使用正确的全局配置路径", () => {
|
|
279
|
+
const globalConfig = join(mockHomedir(), ".gwrc.json");
|
|
280
|
+
expect(globalConfig).toBe("/home/user/.gwrc.json");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("应该使用正确的临时目录路径", () => {
|
|
284
|
+
const tmpDir = mockTmpdir();
|
|
285
|
+
expect(tmpDir).toBe("/tmp");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("应该正确拼接临时文件路径", () => {
|
|
289
|
+
const tmpFile = join(mockTmpdir(), ".gw-commit-msg-123456");
|
|
290
|
+
expect(tmpFile).toBe("/tmp/.gw-commit-msg-123456");
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
});
|