@zjex/git-workflow 0.3.8 → 0.3.9

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/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  <a href="https://github.com/iamzjt-front-end/git-workflow"><img src="https://img.shields.io/github/stars/iamzjt-front-end/git-workflow?style=flat&colorA=18181B&colorB=F59E0B" alt="github stars"></a>
13
13
  <a href="https://github.com/iamzjt-front-end/git-workflow/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@zjex/git-workflow?style=flat&colorA=18181B&colorB=10B981" alt="license"></a>
14
14
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18-339933?style=flat&logo=node.js&logoColor=white&colorA=18181B" alt="node version"></a>
15
- <a href="https://github.com/iamzjt-front-end/git-workflow/actions"><img src="https://img.shields.io/badge/tests-267%20passed-success?style=flat&colorA=18181B" alt="tests"></a>
15
+ <a href="https://github.com/iamzjt-front-end/git-workflow/actions"><img src="https://img.shields.io/badge/tests-290%20passed-success?style=flat&colorA=18181B" alt="tests"></a>
16
16
  <a href="https://github.com/iamzjt-front-end/git-workflow/issues"><img src="https://img.shields.io/github/issues/iamzjt-front-end/git-workflow?style=flat&colorA=18181B&colorB=EC4899" alt="issues"></a>
17
17
  </p>
18
18
 
package/dist/index.js CHANGED
@@ -179,11 +179,10 @@ function showSimpleNotification(current, latest) {
179
179
  )} \u2192 ${colors.green(latest)} ${colors.dim("\u8FD0\u884C")} ${colors.cyan(
180
180
  "gw update"
181
181
  )} ${colors.dim("\u66F4\u65B0")}`;
182
- console.log("");
183
182
  console.log(
184
183
  boxen(message, {
185
184
  padding: { top: 0, bottom: 0, left: 2, right: 2 },
186
- margin: { top: 0, bottom: 1, left: 0, right: 0 },
185
+ margin: { top: 0, bottom: 0, left: 0, right: 0 },
187
186
  borderStyle: "round",
188
187
  borderColor: "yellow",
189
188
  align: "center"
@@ -431,12 +430,22 @@ async function getBranchName(type) {
431
430
  console.log(colors.red(`${idLabel}\u4E0D\u80FD\u4E3A\u7A7A`));
432
431
  return null;
433
432
  }
434
- const description = await input({ message: "\u8BF7\u8F93\u5165\u63CF\u8FF0:", theme });
435
- if (!description) {
433
+ const requireDescription = type === "feature" ? config2.featureRequireDescription ?? false : config2.hotfixRequireDescription ?? false;
434
+ const descMessage = requireDescription ? "\u8BF7\u8F93\u5165\u63CF\u8FF0:" : "\u8BF7\u8F93\u5165\u63CF\u8FF0 (\u53EF\u8DF3\u8FC7):";
435
+ const description = await input({ message: descMessage, theme });
436
+ if (requireDescription && !description) {
436
437
  console.log(colors.red("\u63CF\u8FF0\u4E0D\u80FD\u4E3A\u7A7A"));
437
438
  return null;
438
439
  }
439
- return id ? `${branchPrefix}/${TODAY}-${id}-${description}` : `${branchPrefix}/${TODAY}-${description}`;
440
+ if (id && description) {
441
+ return `${branchPrefix}/${TODAY}-${id}-${description}`;
442
+ } else if (id) {
443
+ return `${branchPrefix}/${TODAY}-${id}`;
444
+ } else if (description) {
445
+ return `${branchPrefix}/${TODAY}-${description}`;
446
+ } else {
447
+ return `${branchPrefix}/${TODAY}`;
448
+ }
440
449
  }
441
450
  async function createBranch(type, baseBranchArg) {
442
451
  const config2 = getConfig();
@@ -1327,6 +1336,25 @@ async function init() {
1327
1336
  });
1328
1337
  config2.hotfixIdLabel = hotfixIdLabel;
1329
1338
  divider();
1339
+ const featureRequireDescription = await select4({
1340
+ message: "Feature \u5206\u652F\u662F\u5426\u8981\u6C42\u5FC5\u586B\u63CF\u8FF0?",
1341
+ choices: [
1342
+ { name: "\u5426", value: false },
1343
+ { name: "\u662F", value: true }
1344
+ ],
1345
+ theme
1346
+ });
1347
+ config2.featureRequireDescription = featureRequireDescription;
1348
+ const hotfixRequireDescription = await select4({
1349
+ message: "Hotfix \u5206\u652F\u662F\u5426\u8981\u6C42\u5FC5\u586B\u63CF\u8FF0?",
1350
+ choices: [
1351
+ { name: "\u5426", value: false },
1352
+ { name: "\u662F", value: true }
1353
+ ],
1354
+ theme
1355
+ });
1356
+ config2.hotfixRequireDescription = hotfixRequireDescription;
1357
+ divider();
1330
1358
  const defaultTagPrefix = await input3({
1331
1359
  message: "\u9ED8\u8BA4 Tag \u524D\u7F00 (\u7559\u7A7A\u5219\u6BCF\u6B21\u9009\u62E9):",
1332
1360
  theme
@@ -2916,7 +2944,7 @@ process.on("SIGTERM", () => {
2916
2944
  console.log("");
2917
2945
  process.exit(0);
2918
2946
  });
2919
- var version = true ? "0.3.8" : "0.0.0-dev";
2947
+ var version = true ? "0.3.9" : "0.0.0-dev";
2920
2948
  async function mainMenu() {
2921
2949
  console.log(
2922
2950
  colors.green(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zjex/git-workflow",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "🚀 极简的 Git 工作流 CLI 工具,让分支管理和版本发布变得轻松愉快",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,15 +32,32 @@ export async function getBranchName(type: BranchType): Promise<string | null> {
32
32
  return null;
33
33
  }
34
34
 
35
- const description = await input({ message: "请输入描述:", theme });
36
- if (!description) {
35
+ // 描述是否必填,默认非必填
36
+ const requireDescription =
37
+ type === "feature"
38
+ ? config.featureRequireDescription ?? false
39
+ : config.hotfixRequireDescription ?? false;
40
+ const descMessage = requireDescription
41
+ ? "请输入描述:"
42
+ : "请输入描述 (可跳过):";
43
+
44
+ const description = await input({ message: descMessage, theme });
45
+
46
+ if (requireDescription && !description) {
37
47
  console.log(colors.red("描述不能为空"));
38
48
  return null;
39
49
  }
40
50
 
41
- return id
42
- ? `${branchPrefix}/${TODAY}-${id}-${description}`
43
- : `${branchPrefix}/${TODAY}-${description}`;
51
+ // 构建分支名
52
+ if (id && description) {
53
+ return `${branchPrefix}/${TODAY}-${id}-${description}`;
54
+ } else if (id) {
55
+ return `${branchPrefix}/${TODAY}-${id}`;
56
+ } else if (description) {
57
+ return `${branchPrefix}/${TODAY}-${description}`;
58
+ } else {
59
+ return `${branchPrefix}/${TODAY}`;
60
+ }
44
61
  }
45
62
 
46
63
  export async function createBranch(
@@ -118,6 +118,29 @@ export async function init(): Promise<void> {
118
118
 
119
119
  divider();
120
120
 
121
+ // 描述必填配置
122
+ const featureRequireDescription = await select({
123
+ message: "Feature 分支是否要求必填描述?",
124
+ choices: [
125
+ { name: "否", value: false },
126
+ { name: "是", value: true },
127
+ ],
128
+ theme,
129
+ });
130
+ config.featureRequireDescription = featureRequireDescription;
131
+
132
+ const hotfixRequireDescription = await select({
133
+ message: "Hotfix 分支是否要求必填描述?",
134
+ choices: [
135
+ { name: "否", value: false },
136
+ { name: "是", value: true },
137
+ ],
138
+ theme,
139
+ });
140
+ config.hotfixRequireDescription = hotfixRequireDescription;
141
+
142
+ divider();
143
+
121
144
  // Tag 配置
122
145
  const defaultTagPrefix = await input({
123
146
  message: "默认 Tag 前缀 (留空则每次选择):",
@@ -269,15 +292,16 @@ export async function init(): Promise<void> {
269
292
  const detailedDescription = await select({
270
293
  message: "是否生成详细的修改点描述?",
271
294
  choices: [
272
- {
273
- name: "是(包含修改点列表,推荐)",
295
+ {
296
+ name: "是(包含修改点列表,推荐)",
274
297
  value: true,
275
- description: "如:feat(auth): 添加用户登录功能\n\n- 实现用户名密码登录接口\n- 添加登录状态验证中间件"
298
+ description:
299
+ "如:feat(auth): 添加用户登录功能\n\n- 实现用户名密码登录接口\n- 添加登录状态验证中间件",
276
300
  },
277
- {
278
- name: "否(仅生成标题)",
301
+ {
302
+ name: "否(仅生成标题)",
279
303
  value: false,
280
- description: "如:feat(auth): 添加用户登录功能"
304
+ description: "如:feat(auth): 添加用户登录功能",
281
305
  },
282
306
  ],
283
307
  theme,
@@ -286,20 +310,20 @@ export async function init(): Promise<void> {
286
310
  const aiUseEmoji = await select({
287
311
  message: "AI 生成的 commit message 是否包含 emoji?",
288
312
  choices: [
289
- {
290
- name: "是(推荐)",
313
+ {
314
+ name: "是(推荐)",
291
315
  value: true,
292
- description: "如:✨ feat(auth): 添加用户登录功能"
316
+ description: "如:✨ feat(auth): 添加用户登录功能",
293
317
  },
294
- {
295
- name: "否",
318
+ {
319
+ name: "否",
296
320
  value: false,
297
- description: "如:feat(auth): 添加用户登录功能"
321
+ description: "如:feat(auth): 添加用户登录功能",
298
322
  },
299
- {
300
- name: "跟随全局设置",
323
+ {
324
+ name: "跟随全局设置",
301
325
  value: undefined,
302
- description: `当前全局设置:${useEmoji ? '启用' : '禁用'} emoji`
326
+ description: `当前全局设置:${useEmoji ? "启用" : "禁用"} emoji`,
303
327
  },
304
328
  ],
305
329
  theme,
package/src/config.ts CHANGED
@@ -12,6 +12,10 @@ export interface GwConfig {
12
12
  hotfixPrefix: string;
13
13
  // 是否要求必填 ID,默认 false
14
14
  requireId: boolean;
15
+ // feature 分支是否要求必填描述,默认 false
16
+ featureRequireDescription?: boolean;
17
+ // hotfix 分支是否要求必填描述,默认 false
18
+ hotfixRequireDescription?: boolean;
15
19
  // ID 标签名称
16
20
  featureIdLabel: string;
17
21
  hotfixIdLabel: string;
@@ -151,11 +151,10 @@ function showSimpleNotification(current: string, latest: string): void {
151
151
  "gw update"
152
152
  )} ${colors.dim("更新")}`;
153
153
 
154
- console.log("");
155
154
  console.log(
156
155
  boxen(message, {
157
156
  padding: { top: 0, bottom: 0, left: 2, right: 2 },
158
- margin: { top: 0, bottom: 1, left: 0, right: 0 },
157
+ margin: { top: 0, bottom: 0, left: 0, right: 0 },
159
158
  borderStyle: "round",
160
159
  borderColor: "yellow",
161
160
  align: "center",
@@ -253,3 +253,119 @@ describe("Branch 功能测试", () => {
253
253
  });
254
254
  });
255
255
  });
256
+
257
+ describe("描述必填配置", () => {
258
+ it("featureRequireDescription 为 true 时描述不能为空", () => {
259
+ const featureRequireDescription = true;
260
+ const description = "";
261
+
262
+ if (featureRequireDescription && !description) {
263
+ expect(description).toBe("");
264
+ }
265
+ });
266
+
267
+ it("featureRequireDescription 为 false 时描述可以为空", () => {
268
+ const featureRequireDescription = false;
269
+ const description = "";
270
+
271
+ if (!featureRequireDescription) {
272
+ expect(description).toBe("");
273
+ }
274
+ });
275
+
276
+ it("hotfixRequireDescription 为 true 时描述不能为空", () => {
277
+ const hotfixRequireDescription = true;
278
+ const description = "";
279
+
280
+ if (hotfixRequireDescription && !description) {
281
+ expect(description).toBe("");
282
+ }
283
+ });
284
+
285
+ it("hotfixRequireDescription 为 false 时描述可以为空", () => {
286
+ const hotfixRequireDescription = false;
287
+ const description = "";
288
+
289
+ if (!hotfixRequireDescription) {
290
+ expect(description).toBe("");
291
+ }
292
+ });
293
+
294
+ it("应该生成只有 ID 的 feature 分支名(描述为空)", () => {
295
+ const prefix = "feature";
296
+ const id = "PROJ-123";
297
+ const description = "";
298
+ const branchName = id ? `${prefix}/${TODAY}-${id}` : `${prefix}/${TODAY}`;
299
+
300
+ expect(branchName).toMatch(/^feature\/\d{8}-PROJ-123$/);
301
+ });
302
+
303
+ it("应该生成只有描述的 feature 分支名(ID 为空)", () => {
304
+ const prefix = "feature";
305
+ const id = "";
306
+ const description = "add-login";
307
+ const branchName = description
308
+ ? `${prefix}/${TODAY}-${description}`
309
+ : `${prefix}/${TODAY}`;
310
+
311
+ expect(branchName).toMatch(/^feature\/\d{8}-add-login$/);
312
+ });
313
+
314
+ it("应该生成只有日期的 feature 分支名(ID 和描述都为空)", () => {
315
+ const prefix = "feature";
316
+ const id = "";
317
+ const description = "";
318
+ const branchName = `${prefix}/${TODAY}`;
319
+
320
+ expect(branchName).toMatch(/^feature\/\d{8}$/);
321
+ });
322
+
323
+ it("应该生成只有 ID 的 hotfix 分支名(描述为空)", () => {
324
+ const prefix = "hotfix";
325
+ const id = "BUG-456";
326
+ const description = "";
327
+ const branchName = id ? `${prefix}/${TODAY}-${id}` : `${prefix}/${TODAY}`;
328
+
329
+ expect(branchName).toMatch(/^hotfix\/\d{8}-BUG-456$/);
330
+ });
331
+
332
+ it("应该生成只有描述的 hotfix 分支名(ID 为空)", () => {
333
+ const prefix = "hotfix";
334
+ const id = "";
335
+ const description = "fix-crash";
336
+ const branchName = description
337
+ ? `${prefix}/${TODAY}-${description}`
338
+ : `${prefix}/${TODAY}`;
339
+
340
+ expect(branchName).toMatch(/^hotfix\/\d{8}-fix-crash$/);
341
+ });
342
+
343
+ it("应该生成只有日期的 hotfix 分支名(ID 和描述都为空)", () => {
344
+ const prefix = "hotfix";
345
+ const id = "";
346
+ const description = "";
347
+ const branchName = `${prefix}/${TODAY}`;
348
+
349
+ expect(branchName).toMatch(/^hotfix\/\d{8}$/);
350
+ });
351
+
352
+ it("feature 和 hotfix 可以有不同的描述必填配置", () => {
353
+ const featureRequireDescription = true;
354
+ const hotfixRequireDescription = false;
355
+
356
+ expect(featureRequireDescription).toBe(true);
357
+ expect(hotfixRequireDescription).toBe(false);
358
+ expect(featureRequireDescription).not.toBe(hotfixRequireDescription);
359
+ });
360
+
361
+ it("描述必填配置默认应该为 false", () => {
362
+ const featureRequireDescription = undefined;
363
+ const hotfixRequireDescription = undefined;
364
+
365
+ const featureRequired = featureRequireDescription ?? false;
366
+ const hotfixRequired = hotfixRequireDescription ?? false;
367
+
368
+ expect(featureRequired).toBe(false);
369
+ expect(hotfixRequired).toBe(false);
370
+ });
371
+ });
@@ -1,4 +1,10 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { execSync } from "child_process";
3
+
4
+ // Mock child_process
5
+ vi.mock("child_process", () => ({
6
+ execSync: vi.fn(),
7
+ }));
2
8
 
3
9
  describe("Commit 功能测试", () => {
4
10
  describe("提交类型", () => {
@@ -82,4 +88,183 @@ describe("Commit 功能测试", () => {
82
88
  expect(extraSpace).toBe("");
83
89
  });
84
90
  });
91
+
92
+ describe("自动暂存功能", () => {
93
+ beforeEach(() => {
94
+ vi.clearAllMocks();
95
+ });
96
+
97
+ afterEach(() => {
98
+ vi.restoreAllMocks();
99
+ });
100
+
101
+ it("autoStage 为 true 时应该执行 git add -A", () => {
102
+ const autoStage = true;
103
+
104
+ if (autoStage) {
105
+ execSync("git add -A", { stdio: "pipe" });
106
+ }
107
+
108
+ expect(execSync).toHaveBeenCalledWith("git add -A", { stdio: "pipe" });
109
+ });
110
+
111
+ it("autoStage 为 false 时不应该自动执行 git add -A", () => {
112
+ const autoStage = false;
113
+
114
+ if (autoStage) {
115
+ execSync("git add -A", { stdio: "pipe" });
116
+ }
117
+
118
+ expect(execSync).not.toHaveBeenCalled();
119
+ });
120
+
121
+ it("提交前应该再次执行 git add -A 确保文件被暂存", () => {
122
+ const autoStage = true;
123
+
124
+ // 模拟提交前的暂存操作
125
+ if (autoStage) {
126
+ execSync("git add -A", { stdio: "pipe" });
127
+ }
128
+
129
+ // 模拟提交前再次暂存
130
+ if (autoStage) {
131
+ execSync("git add -A", { stdio: "pipe" });
132
+ }
133
+
134
+ // 应该被调用两次
135
+ expect(execSync).toHaveBeenCalledTimes(2);
136
+ expect(execSync).toHaveBeenCalledWith("git add -A", { stdio: "pipe" });
137
+ });
138
+
139
+ it("默认 autoStage 应该为 true", () => {
140
+ const config = {};
141
+ const autoStage = (config as any).autoStage ?? true;
142
+
143
+ expect(autoStage).toBe(true);
144
+ });
145
+
146
+ it("配置 autoStage 为 false 时应该覆盖默认值", () => {
147
+ const config = { autoStage: false };
148
+ const autoStage = config.autoStage ?? true;
149
+
150
+ expect(autoStage).toBe(false);
151
+ });
152
+ });
153
+
154
+ describe("Git 状态解析", () => {
155
+ it("应该正确解析已暂存的文件", () => {
156
+ // 模拟 git status --porcelain 输出
157
+ // M = 已暂存的修改
158
+ // A = 已暂存的新文件
159
+ const output = "M src/index.ts\nA src/new.ts";
160
+ const lines = output.split("\n");
161
+
162
+ const staged: { status: string; file: string }[] = [];
163
+
164
+ for (const line of lines) {
165
+ if (!line) continue;
166
+ const indexStatus = line[0];
167
+ const file = line.slice(3);
168
+
169
+ if (indexStatus !== " " && indexStatus !== "?") {
170
+ staged.push({ status: indexStatus, file });
171
+ }
172
+ }
173
+
174
+ expect(staged).toHaveLength(2);
175
+ expect(staged[0]).toEqual({ status: "M", file: "src/index.ts" });
176
+ expect(staged[1]).toEqual({ status: "A", file: "src/new.ts" });
177
+ });
178
+
179
+ it("应该正确解析未暂存的文件", () => {
180
+ // 模拟 git status --porcelain 输出
181
+ // " M" = 未暂存的修改
182
+ // "??" = 未跟踪的文件
183
+ const output = " M src/modified.ts\n?? src/untracked.ts";
184
+ const lines = output.split("\n");
185
+
186
+ const unstaged: { status: string; file: string }[] = [];
187
+
188
+ for (const line of lines) {
189
+ if (!line) continue;
190
+ const indexStatus = line[0];
191
+ const workTreeStatus = line[1];
192
+ const file = line.slice(3);
193
+
194
+ if (workTreeStatus !== " " || indexStatus === "?") {
195
+ const status = indexStatus === "?" ? "?" : workTreeStatus;
196
+ unstaged.push({ status, file });
197
+ }
198
+ }
199
+
200
+ expect(unstaged).toHaveLength(2);
201
+ expect(unstaged[0]).toEqual({ status: "M", file: "src/modified.ts" });
202
+ expect(unstaged[1]).toEqual({ status: "?", file: "src/untracked.ts" });
203
+ });
204
+
205
+ it("空输出应该返回空数组", () => {
206
+ const output = "";
207
+ const staged: any[] = [];
208
+ const unstaged: any[] = [];
209
+
210
+ if (!output) {
211
+ expect(staged).toHaveLength(0);
212
+ expect(unstaged).toHaveLength(0);
213
+ }
214
+ });
215
+
216
+ it("应该正确处理同时有暂存和未暂存状态的文件", () => {
217
+ // "MM" = 已暂存且有新的未暂存修改
218
+ const output = "MM src/both.ts";
219
+ const lines = output.split("\n");
220
+
221
+ const staged: { status: string; file: string }[] = [];
222
+ const unstaged: { status: string; file: string }[] = [];
223
+
224
+ for (const line of lines) {
225
+ if (!line) continue;
226
+ const indexStatus = line[0];
227
+ const workTreeStatus = line[1];
228
+ const file = line.slice(3);
229
+
230
+ if (indexStatus !== " " && indexStatus !== "?") {
231
+ staged.push({ status: indexStatus, file });
232
+ }
233
+
234
+ if (workTreeStatus !== " " || indexStatus === "?") {
235
+ const status = indexStatus === "?" ? "?" : workTreeStatus;
236
+ unstaged.push({ status, file });
237
+ }
238
+ }
239
+
240
+ expect(staged).toHaveLength(1);
241
+ expect(unstaged).toHaveLength(1);
242
+ expect(staged[0]).toEqual({ status: "M", file: "src/both.ts" });
243
+ expect(unstaged[0]).toEqual({ status: "M", file: "src/both.ts" });
244
+ });
245
+ });
246
+
247
+ describe("临时文件提交", () => {
248
+ it("应该使用临时文件传递多行 commit message", () => {
249
+ const message = "feat: 新功能\n\n- 详细描述1\n- 详细描述2";
250
+ const lines = message.split("\n");
251
+
252
+ expect(lines).toHaveLength(4);
253
+ expect(lines[0]).toBe("feat: 新功能");
254
+ expect(lines[1]).toBe("");
255
+ expect(lines[2]).toBe("- 详细描述1");
256
+ expect(lines[3]).toBe("- 详细描述2");
257
+ });
258
+
259
+ it("临时文件名应该包含时间戳避免冲突", () => {
260
+ const timestamp1 = Date.now();
261
+ const tmpFile1 = `.gw-commit-msg-${timestamp1}`;
262
+
263
+ // 模拟短暂延迟
264
+ const timestamp2 = timestamp1 + 1;
265
+ const tmpFile2 = `.gw-commit-msg-${timestamp2}`;
266
+
267
+ expect(tmpFile1).not.toBe(tmpFile2);
268
+ });
269
+ });
85
270
  });
@@ -43,8 +43,6 @@ describe("Init 模块测试", () => {
43
43
  beforeEach(() => {
44
44
  vi.clearAllMocks();
45
45
  vi.spyOn(console, "log").mockImplementation(() => {});
46
-
47
- // Default mocks
48
46
  mockHomedir.mockReturnValue("/home/user");
49
47
  mockJoin.mockReturnValue("/home/user/.gwrc.json");
50
48
  });
@@ -56,28 +54,25 @@ describe("Init 模块测试", () => {
56
54
  describe("配置范围选择", () => {
57
55
  it("应该支持全局配置", async () => {
58
56
  mockExistsSync.mockReturnValue(false);
59
-
60
57
  const { select, input } = await import("@inquirer/prompts");
61
58
  vi.mocked(select)
62
- .mockResolvedValueOnce("global") // 配置范围
63
- .mockResolvedValueOnce(false) // requireId
64
- .mockResolvedValueOnce("ask") // autoPush
65
- .mockResolvedValueOnce(true) // autoStage
66
- .mockResolvedValueOnce(true) // useEmoji
67
- .mockResolvedValueOnce(false); // enableAI
68
-
59
+ .mockResolvedValueOnce("global")
60
+ .mockResolvedValueOnce(false)
61
+ .mockResolvedValueOnce(false)
62
+ .mockResolvedValueOnce(false)
63
+ .mockResolvedValueOnce("ask")
64
+ .mockResolvedValueOnce(true)
65
+ .mockResolvedValueOnce(true)
66
+ .mockResolvedValueOnce(false);
69
67
  vi.mocked(input)
70
- .mockResolvedValueOnce("") // baseBranch
71
- .mockResolvedValueOnce("feature") // featurePrefix
72
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
73
- .mockResolvedValueOnce("Story ID") // featureIdLabel
74
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
75
- .mockResolvedValueOnce(""); // defaultTagPrefix
76
-
68
+ .mockResolvedValueOnce("")
69
+ .mockResolvedValueOnce("feature")
70
+ .mockResolvedValueOnce("hotfix")
71
+ .mockResolvedValueOnce("Story ID")
72
+ .mockResolvedValueOnce("Issue ID")
73
+ .mockResolvedValueOnce("");
77
74
  const { init } = await import("../src/commands/init.js");
78
-
79
75
  await init();
80
-
81
76
  expect(mockJoin).toHaveBeenCalledWith("/home/user", ".gwrc.json");
82
77
  expect(mockWriteFileSync).toHaveBeenCalledWith(
83
78
  "/home/user/.gwrc.json",
@@ -87,28 +82,25 @@ describe("Init 模块测试", () => {
87
82
 
88
83
  it("应该支持项目配置", async () => {
89
84
  mockExistsSync.mockReturnValue(false);
90
-
91
85
  const { select, input } = await import("@inquirer/prompts");
92
86
  vi.mocked(select)
93
- .mockResolvedValueOnce("project") // 配置范围
94
- .mockResolvedValueOnce(false) // requireId
95
- .mockResolvedValueOnce("ask") // autoPush
96
- .mockResolvedValueOnce(true) // autoStage
97
- .mockResolvedValueOnce(true) // useEmoji
98
- .mockResolvedValueOnce(false); // enableAI
99
-
87
+ .mockResolvedValueOnce("project")
88
+ .mockResolvedValueOnce(false)
89
+ .mockResolvedValueOnce(false)
90
+ .mockResolvedValueOnce(false)
91
+ .mockResolvedValueOnce("ask")
92
+ .mockResolvedValueOnce(true)
93
+ .mockResolvedValueOnce(true)
94
+ .mockResolvedValueOnce(false);
100
95
  vi.mocked(input)
101
- .mockResolvedValueOnce("") // baseBranch
102
- .mockResolvedValueOnce("feature") // featurePrefix
103
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
104
- .mockResolvedValueOnce("Story ID") // featureIdLabel
105
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
106
- .mockResolvedValueOnce(""); // defaultTagPrefix
107
-
96
+ .mockResolvedValueOnce("")
97
+ .mockResolvedValueOnce("feature")
98
+ .mockResolvedValueOnce("hotfix")
99
+ .mockResolvedValueOnce("Story ID")
100
+ .mockResolvedValueOnce("Issue ID")
101
+ .mockResolvedValueOnce("");
108
102
  const { init } = await import("../src/commands/init.js");
109
-
110
103
  await init();
111
-
112
104
  expect(mockWriteFileSync).toHaveBeenCalledWith(
113
105
  ".gwrc.json",
114
106
  expect.stringContaining('"featurePrefix": "feature"')
@@ -119,44 +111,37 @@ describe("Init 模块测试", () => {
119
111
  describe("配置文件覆盖", () => {
120
112
  it("应该处理配置文件已存在的情况", async () => {
121
113
  mockExistsSync.mockReturnValue(true);
122
-
123
114
  const { select, input } = await import("@inquirer/prompts");
124
115
  vi.mocked(select)
125
- .mockResolvedValueOnce("global") // 配置范围
126
- .mockResolvedValueOnce(true) // 覆盖文件
127
- .mockResolvedValueOnce(false) // requireId
128
- .mockResolvedValueOnce("ask") // autoPush
129
- .mockResolvedValueOnce(true) // autoStage
130
- .mockResolvedValueOnce(true) // useEmoji
131
- .mockResolvedValueOnce(false); // enableAI
132
-
116
+ .mockResolvedValueOnce("global")
117
+ .mockResolvedValueOnce(true)
118
+ .mockResolvedValueOnce(false)
119
+ .mockResolvedValueOnce(false)
120
+ .mockResolvedValueOnce(false)
121
+ .mockResolvedValueOnce("ask")
122
+ .mockResolvedValueOnce(true)
123
+ .mockResolvedValueOnce(true)
124
+ .mockResolvedValueOnce(false);
133
125
  vi.mocked(input)
134
- .mockResolvedValueOnce("") // baseBranch
135
- .mockResolvedValueOnce("feature") // featurePrefix
136
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
137
- .mockResolvedValueOnce("Story ID") // featureIdLabel
138
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
139
- .mockResolvedValueOnce(""); // defaultTagPrefix
140
-
126
+ .mockResolvedValueOnce("")
127
+ .mockResolvedValueOnce("feature")
128
+ .mockResolvedValueOnce("hotfix")
129
+ .mockResolvedValueOnce("Story ID")
130
+ .mockResolvedValueOnce("Issue ID")
131
+ .mockResolvedValueOnce("");
141
132
  const { init } = await import("../src/commands/init.js");
142
-
143
133
  await init();
144
-
145
134
  expect(mockWriteFileSync).toHaveBeenCalled();
146
135
  });
147
136
 
148
137
  it("应该处理用户取消覆盖", async () => {
149
138
  mockExistsSync.mockReturnValue(true);
150
-
151
139
  const { select } = await import("@inquirer/prompts");
152
140
  vi.mocked(select)
153
- .mockResolvedValueOnce("global") // 配置范围
154
- .mockResolvedValueOnce(false); // 不覆盖文件
155
-
141
+ .mockResolvedValueOnce("global")
142
+ .mockResolvedValueOnce(false);
156
143
  const { init } = await import("../src/commands/init.js");
157
-
158
144
  await init();
159
-
160
145
  expect(console.log).toHaveBeenCalledWith("已取消");
161
146
  expect(mockWriteFileSync).not.toHaveBeenCalled();
162
147
  });
@@ -165,31 +150,27 @@ describe("Init 模块测试", () => {
165
150
  describe("基础配置", () => {
166
151
  it("应该正确配置分支前缀", async () => {
167
152
  mockExistsSync.mockReturnValue(false);
168
-
169
153
  const { select, input } = await import("@inquirer/prompts");
170
154
  vi.mocked(select)
171
- .mockResolvedValueOnce("project") // 配置范围
172
- .mockResolvedValueOnce(false) // requireId
173
- .mockResolvedValueOnce("ask") // autoPush
174
- .mockResolvedValueOnce(true) // autoStage
175
- .mockResolvedValueOnce(true) // useEmoji
176
- .mockResolvedValueOnce(false); // enableAI
177
-
155
+ .mockResolvedValueOnce("project")
156
+ .mockResolvedValueOnce(false)
157
+ .mockResolvedValueOnce(false)
158
+ .mockResolvedValueOnce(false)
159
+ .mockResolvedValueOnce("ask")
160
+ .mockResolvedValueOnce(true)
161
+ .mockResolvedValueOnce(true)
162
+ .mockResolvedValueOnce(false);
178
163
  vi.mocked(input)
179
- .mockResolvedValueOnce("develop") // baseBranch
180
- .mockResolvedValueOnce("feat") // featurePrefix
181
- .mockResolvedValueOnce("fix") // hotfixPrefix
182
- .mockResolvedValueOnce("Jira ID") // featureIdLabel
183
- .mockResolvedValueOnce("Bug ID") // hotfixIdLabel
184
- .mockResolvedValueOnce("v"); // defaultTagPrefix
185
-
164
+ .mockResolvedValueOnce("develop")
165
+ .mockResolvedValueOnce("feat")
166
+ .mockResolvedValueOnce("fix")
167
+ .mockResolvedValueOnce("Jira ID")
168
+ .mockResolvedValueOnce("Bug ID")
169
+ .mockResolvedValueOnce("v");
186
170
  const { init } = await import("../src/commands/init.js");
187
-
188
171
  await init();
189
-
190
172
  const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
191
173
  const config = JSON.parse(writtenConfig);
192
-
193
174
  expect(config.baseBranch).toBe("develop");
194
175
  expect(config.featurePrefix).toBe("feat");
195
176
  expect(config.hotfixPrefix).toBe("fix");
@@ -200,61 +181,53 @@ describe("Init 模块测试", () => {
200
181
 
201
182
  it("应该正确配置 ID 要求", async () => {
202
183
  mockExistsSync.mockReturnValue(false);
203
-
204
184
  const { select, input } = await import("@inquirer/prompts");
205
185
  vi.mocked(select)
206
- .mockResolvedValueOnce("project") // 配置范围
207
- .mockResolvedValueOnce(true) // requireId
208
- .mockResolvedValueOnce("ask") // autoPush
209
- .mockResolvedValueOnce(true) // autoStage
210
- .mockResolvedValueOnce(true) // useEmoji
211
- .mockResolvedValueOnce(false); // enableAI
212
-
186
+ .mockResolvedValueOnce("project")
187
+ .mockResolvedValueOnce(true)
188
+ .mockResolvedValueOnce(false)
189
+ .mockResolvedValueOnce(false)
190
+ .mockResolvedValueOnce("ask")
191
+ .mockResolvedValueOnce(true)
192
+ .mockResolvedValueOnce(true)
193
+ .mockResolvedValueOnce(false);
213
194
  vi.mocked(input)
214
- .mockResolvedValueOnce("") // baseBranch
215
- .mockResolvedValueOnce("feature") // featurePrefix
216
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
217
- .mockResolvedValueOnce("Story ID") // featureIdLabel
218
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
219
- .mockResolvedValueOnce(""); // defaultTagPrefix
220
-
195
+ .mockResolvedValueOnce("")
196
+ .mockResolvedValueOnce("feature")
197
+ .mockResolvedValueOnce("hotfix")
198
+ .mockResolvedValueOnce("Story ID")
199
+ .mockResolvedValueOnce("Issue ID")
200
+ .mockResolvedValueOnce("");
221
201
  const { init } = await import("../src/commands/init.js");
222
-
223
202
  await init();
224
-
225
203
  const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
226
204
  const config = JSON.parse(writtenConfig);
227
-
228
205
  expect(config.requireId).toBe(true);
229
206
  });
230
207
 
231
208
  it("应该正确配置自动推送选项", async () => {
232
209
  mockExistsSync.mockReturnValue(false);
233
-
234
210
  const { select, input } = await import("@inquirer/prompts");
235
211
  vi.mocked(select)
236
- .mockResolvedValueOnce("project") // 配置范围
237
- .mockResolvedValueOnce(false) // requireId
238
- .mockResolvedValueOnce("yes") // autoPush
239
- .mockResolvedValueOnce(true) // autoStage
240
- .mockResolvedValueOnce(true) // useEmoji
241
- .mockResolvedValueOnce(false); // enableAI
242
-
212
+ .mockResolvedValueOnce("project")
213
+ .mockResolvedValueOnce(false)
214
+ .mockResolvedValueOnce(false)
215
+ .mockResolvedValueOnce(false)
216
+ .mockResolvedValueOnce("yes")
217
+ .mockResolvedValueOnce(true)
218
+ .mockResolvedValueOnce(true)
219
+ .mockResolvedValueOnce(false);
243
220
  vi.mocked(input)
244
- .mockResolvedValueOnce("") // baseBranch
245
- .mockResolvedValueOnce("feature") // featurePrefix
246
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
247
- .mockResolvedValueOnce("Story ID") // featureIdLabel
248
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
249
- .mockResolvedValueOnce(""); // defaultTagPrefix
250
-
221
+ .mockResolvedValueOnce("")
222
+ .mockResolvedValueOnce("feature")
223
+ .mockResolvedValueOnce("hotfix")
224
+ .mockResolvedValueOnce("Story ID")
225
+ .mockResolvedValueOnce("Issue ID")
226
+ .mockResolvedValueOnce("");
251
227
  const { init } = await import("../src/commands/init.js");
252
-
253
228
  await init();
254
-
255
229
  const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
256
230
  const config = JSON.parse(writtenConfig);
257
-
258
231
  expect(config.autoPush).toBe(true);
259
232
  });
260
233
  });
@@ -262,34 +235,32 @@ describe("Init 模块测试", () => {
262
235
  describe("AI 配置", () => {
263
236
  it("应该正确配置 GitHub Models", async () => {
264
237
  mockExistsSync.mockReturnValue(false);
265
-
266
238
  const { select, input } = await import("@inquirer/prompts");
267
239
  vi.mocked(select)
268
- .mockResolvedValueOnce("project") // 配置范围
269
- .mockResolvedValueOnce(false) // requireId
270
- .mockResolvedValueOnce("ask") // autoPush
271
- .mockResolvedValueOnce(true) // autoStage
272
- .mockResolvedValueOnce(true) // useEmoji
273
- .mockResolvedValueOnce(true) // enableAI
274
- .mockResolvedValueOnce("github") // AI provider
275
- .mockResolvedValueOnce("zh-CN"); // language
276
-
240
+ .mockResolvedValueOnce("project")
241
+ .mockResolvedValueOnce(false)
242
+ .mockResolvedValueOnce(false)
243
+ .mockResolvedValueOnce(false)
244
+ .mockResolvedValueOnce("ask")
245
+ .mockResolvedValueOnce(true)
246
+ .mockResolvedValueOnce(true)
247
+ .mockResolvedValueOnce(true)
248
+ .mockResolvedValueOnce("github")
249
+ .mockResolvedValueOnce("zh-CN")
250
+ .mockResolvedValueOnce(true)
251
+ .mockResolvedValueOnce(true);
277
252
  vi.mocked(input)
278
- .mockResolvedValueOnce("") // baseBranch
279
- .mockResolvedValueOnce("feature") // featurePrefix
280
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
281
- .mockResolvedValueOnce("Story ID") // featureIdLabel
282
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
283
- .mockResolvedValueOnce("") // defaultTagPrefix
284
- .mockResolvedValueOnce("ghp_test_token"); // GitHub token
285
-
253
+ .mockResolvedValueOnce("")
254
+ .mockResolvedValueOnce("feature")
255
+ .mockResolvedValueOnce("hotfix")
256
+ .mockResolvedValueOnce("Story ID")
257
+ .mockResolvedValueOnce("Issue ID")
258
+ .mockResolvedValueOnce("")
259
+ .mockResolvedValueOnce("ghp_test_token");
286
260
  const { init } = await import("../src/commands/init.js");
287
-
288
261
  await init();
289
-
290
262
  const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
291
263
  const config = JSON.parse(writtenConfig);
292
-
293
264
  expect(config.aiCommit.enabled).toBe(true);
294
265
  expect(config.aiCommit.provider).toBe("github");
295
266
  expect(config.aiCommit.apiKey).toBe("ghp_test_token");
@@ -299,34 +270,32 @@ describe("Init 模块测试", () => {
299
270
 
300
271
  it("应该正确配置 OpenAI", async () => {
301
272
  mockExistsSync.mockReturnValue(false);
302
-
303
273
  const { select, input } = await import("@inquirer/prompts");
304
274
  vi.mocked(select)
305
- .mockResolvedValueOnce("project") // 配置范围
306
- .mockResolvedValueOnce(false) // requireId
307
- .mockResolvedValueOnce("ask") // autoPush
308
- .mockResolvedValueOnce(true) // autoStage
309
- .mockResolvedValueOnce(true) // useEmoji
310
- .mockResolvedValueOnce(true) // enableAI
311
- .mockResolvedValueOnce("openai") // AI provider
312
- .mockResolvedValueOnce("en-US"); // language
313
-
275
+ .mockResolvedValueOnce("project")
276
+ .mockResolvedValueOnce(false)
277
+ .mockResolvedValueOnce(false)
278
+ .mockResolvedValueOnce(false)
279
+ .mockResolvedValueOnce("ask")
280
+ .mockResolvedValueOnce(true)
281
+ .mockResolvedValueOnce(true)
282
+ .mockResolvedValueOnce(true)
283
+ .mockResolvedValueOnce("openai")
284
+ .mockResolvedValueOnce("en-US")
285
+ .mockResolvedValueOnce(true)
286
+ .mockResolvedValueOnce(true);
314
287
  vi.mocked(input)
315
- .mockResolvedValueOnce("") // baseBranch
316
- .mockResolvedValueOnce("feature") // featurePrefix
317
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
318
- .mockResolvedValueOnce("Story ID") // featureIdLabel
319
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
320
- .mockResolvedValueOnce("") // defaultTagPrefix
321
- .mockResolvedValueOnce("sk-test-key"); // OpenAI API key
322
-
288
+ .mockResolvedValueOnce("")
289
+ .mockResolvedValueOnce("feature")
290
+ .mockResolvedValueOnce("hotfix")
291
+ .mockResolvedValueOnce("Story ID")
292
+ .mockResolvedValueOnce("Issue ID")
293
+ .mockResolvedValueOnce("")
294
+ .mockResolvedValueOnce("sk-test-key");
323
295
  const { init } = await import("../src/commands/init.js");
324
-
325
296
  await init();
326
-
327
297
  const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
328
298
  const config = JSON.parse(writtenConfig);
329
-
330
299
  expect(config.aiCommit.enabled).toBe(true);
331
300
  expect(config.aiCommit.provider).toBe("openai");
332
301
  expect(config.aiCommit.apiKey).toBe("sk-test-key");
@@ -336,33 +305,31 @@ describe("Init 模块测试", () => {
336
305
 
337
306
  it("应该正确配置 Ollama", async () => {
338
307
  mockExistsSync.mockReturnValue(false);
339
-
340
308
  const { select, input } = await import("@inquirer/prompts");
341
309
  vi.mocked(select)
342
- .mockResolvedValueOnce("project") // 配置范围
343
- .mockResolvedValueOnce(false) // requireId
344
- .mockResolvedValueOnce("ask") // autoPush
345
- .mockResolvedValueOnce(true) // autoStage
346
- .mockResolvedValueOnce(true) // useEmoji
347
- .mockResolvedValueOnce(true) // enableAI
348
- .mockResolvedValueOnce("ollama") // AI provider
349
- .mockResolvedValueOnce("zh-CN"); // language
350
-
310
+ .mockResolvedValueOnce("project")
311
+ .mockResolvedValueOnce(false)
312
+ .mockResolvedValueOnce(false)
313
+ .mockResolvedValueOnce(false)
314
+ .mockResolvedValueOnce("ask")
315
+ .mockResolvedValueOnce(true)
316
+ .mockResolvedValueOnce(true)
317
+ .mockResolvedValueOnce(true)
318
+ .mockResolvedValueOnce("ollama")
319
+ .mockResolvedValueOnce("zh-CN")
320
+ .mockResolvedValueOnce(true)
321
+ .mockResolvedValueOnce(true);
351
322
  vi.mocked(input)
352
- .mockResolvedValueOnce("") // baseBranch
353
- .mockResolvedValueOnce("feature") // featurePrefix
354
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
355
- .mockResolvedValueOnce("Story ID") // featureIdLabel
356
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
357
- .mockResolvedValueOnce(""); // defaultTagPrefix
358
-
323
+ .mockResolvedValueOnce("")
324
+ .mockResolvedValueOnce("feature")
325
+ .mockResolvedValueOnce("hotfix")
326
+ .mockResolvedValueOnce("Story ID")
327
+ .mockResolvedValueOnce("Issue ID")
328
+ .mockResolvedValueOnce("");
359
329
  const { init } = await import("../src/commands/init.js");
360
-
361
330
  await init();
362
-
363
331
  const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
364
332
  const config = JSON.parse(writtenConfig);
365
-
366
333
  expect(config.aiCommit.enabled).toBe(true);
367
334
  expect(config.aiCommit.provider).toBe("ollama");
368
335
  expect(config.aiCommit.apiKey).toBeUndefined();
@@ -372,31 +339,27 @@ describe("Init 模块测试", () => {
372
339
 
373
340
  it("应该正确配置禁用 AI", async () => {
374
341
  mockExistsSync.mockReturnValue(false);
375
-
376
342
  const { select, input } = await import("@inquirer/prompts");
377
343
  vi.mocked(select)
378
- .mockResolvedValueOnce("project") // 配置范围
379
- .mockResolvedValueOnce(false) // requireId
380
- .mockResolvedValueOnce("ask") // autoPush
381
- .mockResolvedValueOnce(true) // autoStage
382
- .mockResolvedValueOnce(true) // useEmoji
383
- .mockResolvedValueOnce(false); // enableAI
384
-
344
+ .mockResolvedValueOnce("project")
345
+ .mockResolvedValueOnce(false)
346
+ .mockResolvedValueOnce(false)
347
+ .mockResolvedValueOnce(false)
348
+ .mockResolvedValueOnce("ask")
349
+ .mockResolvedValueOnce(true)
350
+ .mockResolvedValueOnce(true)
351
+ .mockResolvedValueOnce(false);
385
352
  vi.mocked(input)
386
- .mockResolvedValueOnce("") // baseBranch
387
- .mockResolvedValueOnce("feature") // featurePrefix
388
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
389
- .mockResolvedValueOnce("Story ID") // featureIdLabel
390
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
391
- .mockResolvedValueOnce(""); // defaultTagPrefix
392
-
353
+ .mockResolvedValueOnce("")
354
+ .mockResolvedValueOnce("feature")
355
+ .mockResolvedValueOnce("hotfix")
356
+ .mockResolvedValueOnce("Story ID")
357
+ .mockResolvedValueOnce("Issue ID")
358
+ .mockResolvedValueOnce("");
393
359
  const { init } = await import("../src/commands/init.js");
394
-
395
360
  await init();
396
-
397
361
  const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
398
362
  const config = JSON.parse(writtenConfig);
399
-
400
363
  expect(config.aiCommit.enabled).toBe(false);
401
364
  });
402
365
  });
@@ -404,41 +367,36 @@ describe("Init 模块测试", () => {
404
367
  describe("配置验证", () => {
405
368
  it("应该验证 GitHub Token 不为空", async () => {
406
369
  mockExistsSync.mockReturnValue(false);
407
-
408
370
  const { select, input } = await import("@inquirer/prompts");
409
371
  vi.mocked(select)
410
- .mockResolvedValueOnce("project") // 配置范围
411
- .mockResolvedValueOnce(false) // requireId
412
- .mockResolvedValueOnce("ask") // autoPush
413
- .mockResolvedValueOnce(true) // autoStage
414
- .mockResolvedValueOnce(true) // useEmoji
415
- .mockResolvedValueOnce(true) // enableAI
416
- .mockResolvedValueOnce("github") // AI provider
417
- .mockResolvedValueOnce("zh-CN"); // language
418
-
372
+ .mockResolvedValueOnce("project")
373
+ .mockResolvedValueOnce(false)
374
+ .mockResolvedValueOnce(false)
375
+ .mockResolvedValueOnce(false)
376
+ .mockResolvedValueOnce("ask")
377
+ .mockResolvedValueOnce(true)
378
+ .mockResolvedValueOnce(true)
379
+ .mockResolvedValueOnce(true)
380
+ .mockResolvedValueOnce("github")
381
+ .mockResolvedValueOnce("zh-CN")
382
+ .mockResolvedValueOnce(true)
383
+ .mockResolvedValueOnce(true);
419
384
  vi.mocked(input)
420
- .mockResolvedValueOnce("") // baseBranch
421
- .mockResolvedValueOnce("feature") // featurePrefix
422
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
423
- .mockResolvedValueOnce("Story ID") // featureIdLabel
424
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
425
- .mockResolvedValueOnce("") // defaultTagPrefix
426
- .mockResolvedValueOnce("ghp_valid_token"); // GitHub token
427
-
385
+ .mockResolvedValueOnce("")
386
+ .mockResolvedValueOnce("feature")
387
+ .mockResolvedValueOnce("hotfix")
388
+ .mockResolvedValueOnce("Story ID")
389
+ .mockResolvedValueOnce("Issue ID")
390
+ .mockResolvedValueOnce("")
391
+ .mockResolvedValueOnce("ghp_valid_token");
428
392
  const { init } = await import("../src/commands/init.js");
429
-
430
393
  await init();
431
-
432
- // 验证 input 被调用时包含验证函数
433
394
  const inputCalls = vi.mocked(input).mock.calls;
434
- const tokenInputCall = inputCalls.find(call =>
395
+ const tokenInputCall = inputCalls.find((call) =>
435
396
  call[0].message?.includes("GitHub Token")
436
397
  );
437
-
438
398
  expect(tokenInputCall).toBeDefined();
439
399
  expect(tokenInputCall![0].validate).toBeDefined();
440
-
441
- // 测试验证函数
442
400
  const validate = tokenInputCall![0].validate!;
443
401
  expect(validate("")).toBe("GitHub Token 不能为空");
444
402
  expect(validate("valid-token")).toBe(true);
@@ -446,41 +404,36 @@ describe("Init 模块测试", () => {
446
404
 
447
405
  it("应该验证 OpenAI API Key 不为空", async () => {
448
406
  mockExistsSync.mockReturnValue(false);
449
-
450
407
  const { select, input } = await import("@inquirer/prompts");
451
408
  vi.mocked(select)
452
- .mockResolvedValueOnce("project") // 配置范围
453
- .mockResolvedValueOnce(false) // requireId
454
- .mockResolvedValueOnce("ask") // autoPush
455
- .mockResolvedValueOnce(true) // autoStage
456
- .mockResolvedValueOnce(true) // useEmoji
457
- .mockResolvedValueOnce(true) // enableAI
458
- .mockResolvedValueOnce("openai") // AI provider
459
- .mockResolvedValueOnce("en-US"); // language
460
-
409
+ .mockResolvedValueOnce("project")
410
+ .mockResolvedValueOnce(false)
411
+ .mockResolvedValueOnce(false)
412
+ .mockResolvedValueOnce(false)
413
+ .mockResolvedValueOnce("ask")
414
+ .mockResolvedValueOnce(true)
415
+ .mockResolvedValueOnce(true)
416
+ .mockResolvedValueOnce(true)
417
+ .mockResolvedValueOnce("openai")
418
+ .mockResolvedValueOnce("en-US")
419
+ .mockResolvedValueOnce(true)
420
+ .mockResolvedValueOnce(true);
461
421
  vi.mocked(input)
462
- .mockResolvedValueOnce("") // baseBranch
463
- .mockResolvedValueOnce("feature") // featurePrefix
464
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
465
- .mockResolvedValueOnce("Story ID") // featureIdLabel
466
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
467
- .mockResolvedValueOnce("") // defaultTagPrefix
468
- .mockResolvedValueOnce("sk-valid-key"); // OpenAI API key
469
-
422
+ .mockResolvedValueOnce("")
423
+ .mockResolvedValueOnce("feature")
424
+ .mockResolvedValueOnce("hotfix")
425
+ .mockResolvedValueOnce("Story ID")
426
+ .mockResolvedValueOnce("Issue ID")
427
+ .mockResolvedValueOnce("")
428
+ .mockResolvedValueOnce("sk-valid-key");
470
429
  const { init } = await import("../src/commands/init.js");
471
-
472
430
  await init();
473
-
474
- // 验证 input 被调用时包含验证函数
475
431
  const inputCalls = vi.mocked(input).mock.calls;
476
- const keyInputCall = inputCalls.find(call =>
432
+ const keyInputCall = inputCalls.find((call) =>
477
433
  call[0].message?.includes("OpenAI API Key")
478
434
  );
479
-
480
435
  expect(keyInputCall).toBeDefined();
481
436
  expect(keyInputCall![0].validate).toBeDefined();
482
-
483
- // 测试验证函数
484
437
  const validate = keyInputCall![0].validate!;
485
438
  expect(validate("")).toBe("API Key 不能为空");
486
439
  expect(validate("valid-key")).toBe(true);
@@ -490,31 +443,27 @@ describe("Init 模块测试", () => {
490
443
  describe("配置输出", () => {
491
444
  it("应该包含默认的 commit emojis", async () => {
492
445
  mockExistsSync.mockReturnValue(false);
493
-
494
446
  const { select, input } = await import("@inquirer/prompts");
495
447
  vi.mocked(select)
496
- .mockResolvedValueOnce("project") // 配置范围
497
- .mockResolvedValueOnce(false) // requireId
498
- .mockResolvedValueOnce("ask") // autoPush
499
- .mockResolvedValueOnce(true) // autoStage
500
- .mockResolvedValueOnce(true) // useEmoji
501
- .mockResolvedValueOnce(false); // enableAI
502
-
448
+ .mockResolvedValueOnce("project")
449
+ .mockResolvedValueOnce(false)
450
+ .mockResolvedValueOnce(false)
451
+ .mockResolvedValueOnce(false)
452
+ .mockResolvedValueOnce("ask")
453
+ .mockResolvedValueOnce(true)
454
+ .mockResolvedValueOnce(true)
455
+ .mockResolvedValueOnce(false);
503
456
  vi.mocked(input)
504
- .mockResolvedValueOnce("") // baseBranch
505
- .mockResolvedValueOnce("feature") // featurePrefix
506
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
507
- .mockResolvedValueOnce("Story ID") // featureIdLabel
508
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
509
- .mockResolvedValueOnce(""); // defaultTagPrefix
510
-
457
+ .mockResolvedValueOnce("")
458
+ .mockResolvedValueOnce("feature")
459
+ .mockResolvedValueOnce("hotfix")
460
+ .mockResolvedValueOnce("Story ID")
461
+ .mockResolvedValueOnce("Issue ID")
462
+ .mockResolvedValueOnce("");
511
463
  const { init } = await import("../src/commands/init.js");
512
-
513
464
  await init();
514
-
515
465
  const writtenConfig = mockWriteFileSync.mock.calls[0][1] as string;
516
466
  const config = JSON.parse(writtenConfig);
517
-
518
467
  expect(config.commitEmojis).toBeDefined();
519
468
  expect(config.commitEmojis.feat).toBe("✨");
520
469
  expect(config.commitEmojis.fix).toBe("🐛");
@@ -523,28 +472,25 @@ describe("Init 模块测试", () => {
523
472
 
524
473
  it("应该显示成功消息", async () => {
525
474
  mockExistsSync.mockReturnValue(false);
526
-
527
475
  const { select, input } = await import("@inquirer/prompts");
528
476
  vi.mocked(select)
529
- .mockResolvedValueOnce("global") // 配置范围
530
- .mockResolvedValueOnce(false) // requireId
531
- .mockResolvedValueOnce("ask") // autoPush
532
- .mockResolvedValueOnce(true) // autoStage
533
- .mockResolvedValueOnce(true) // useEmoji
534
- .mockResolvedValueOnce(false); // enableAI
535
-
477
+ .mockResolvedValueOnce("global")
478
+ .mockResolvedValueOnce(false)
479
+ .mockResolvedValueOnce(false)
480
+ .mockResolvedValueOnce(false)
481
+ .mockResolvedValueOnce("ask")
482
+ .mockResolvedValueOnce(true)
483
+ .mockResolvedValueOnce(true)
484
+ .mockResolvedValueOnce(false);
536
485
  vi.mocked(input)
537
- .mockResolvedValueOnce("") // baseBranch
538
- .mockResolvedValueOnce("feature") // featurePrefix
539
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
540
- .mockResolvedValueOnce("Story ID") // featureIdLabel
541
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
542
- .mockResolvedValueOnce(""); // defaultTagPrefix
543
-
486
+ .mockResolvedValueOnce("")
487
+ .mockResolvedValueOnce("feature")
488
+ .mockResolvedValueOnce("hotfix")
489
+ .mockResolvedValueOnce("Story ID")
490
+ .mockResolvedValueOnce("Issue ID")
491
+ .mockResolvedValueOnce("");
544
492
  const { init } = await import("../src/commands/init.js");
545
-
546
493
  await init();
547
-
548
494
  expect(console.log).toHaveBeenCalledWith(
549
495
  expect.stringContaining("✓ 配置已保存到 全局配置文件")
550
496
  );
@@ -552,31 +498,28 @@ describe("Init 模块测试", () => {
552
498
 
553
499
  it("应该显示全局配置的提示信息", async () => {
554
500
  mockExistsSync.mockReturnValue(false);
555
-
556
501
  const { select, input } = await import("@inquirer/prompts");
557
502
  vi.mocked(select)
558
- .mockResolvedValueOnce("global") // 配置范围
559
- .mockResolvedValueOnce(false) // requireId
560
- .mockResolvedValueOnce("ask") // autoPush
561
- .mockResolvedValueOnce(true) // autoStage
562
- .mockResolvedValueOnce(true) // useEmoji
563
- .mockResolvedValueOnce(false); // enableAI
564
-
503
+ .mockResolvedValueOnce("global")
504
+ .mockResolvedValueOnce(false)
505
+ .mockResolvedValueOnce(false)
506
+ .mockResolvedValueOnce(false)
507
+ .mockResolvedValueOnce("ask")
508
+ .mockResolvedValueOnce(true)
509
+ .mockResolvedValueOnce(true)
510
+ .mockResolvedValueOnce(false);
565
511
  vi.mocked(input)
566
- .mockResolvedValueOnce("") // baseBranch
567
- .mockResolvedValueOnce("feature") // featurePrefix
568
- .mockResolvedValueOnce("hotfix") // hotfixPrefix
569
- .mockResolvedValueOnce("Story ID") // featureIdLabel
570
- .mockResolvedValueOnce("Issue ID") // hotfixIdLabel
571
- .mockResolvedValueOnce(""); // defaultTagPrefix
572
-
512
+ .mockResolvedValueOnce("")
513
+ .mockResolvedValueOnce("feature")
514
+ .mockResolvedValueOnce("hotfix")
515
+ .mockResolvedValueOnce("Story ID")
516
+ .mockResolvedValueOnce("Issue ID")
517
+ .mockResolvedValueOnce("");
573
518
  const { init } = await import("../src/commands/init.js");
574
-
575
519
  await init();
576
-
577
520
  expect(console.log).toHaveBeenCalledWith(
578
521
  expect.stringContaining("全局配置对所有项目生效")
579
522
  );
580
523
  });
581
524
  });
582
- });
525
+ });