@zjex/git-workflow 0.3.10 → 0.4.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/src/index.ts CHANGED
@@ -15,7 +15,13 @@ import { select } from "@inquirer/prompts";
15
15
  import { ExitPromptError } from "@inquirer/core";
16
16
  import { checkGitRepo, theme, colors } from "./utils.js";
17
17
  import { createBranch, deleteBranch } from "./commands/branch.js";
18
- import { listTags, createTag, deleteTag, updateTag } from "./commands/tag.js";
18
+ import {
19
+ listTags,
20
+ createTag,
21
+ deleteTag,
22
+ updateTag,
23
+ cleanInvalidTags,
24
+ } from "./commands/tag.js";
19
25
  import { release } from "./commands/release.js";
20
26
  import { init } from "./commands/init.js";
21
27
  import { stash } from "./commands/stash.js";
@@ -119,7 +125,7 @@ async function mainMenu(): Promise<void> {
119
125
  value: "hotfix",
120
126
  },
121
127
  {
122
- name: `[3] 🗑️ 删除分支 ${colors.dim("gw d")}`,
128
+ name: `[3] 🗑️ 删除分支 ${colors.dim("gw brd")}`,
123
129
  value: "delete",
124
130
  },
125
131
  {
@@ -143,15 +149,15 @@ async function mainMenu(): Promise<void> {
143
149
  value: "tags",
144
150
  },
145
151
  {
146
- name: `[9] 📦 发布版本 ${colors.dim("gw r")}`,
152
+ name: `[9] 发布版本 ${colors.dim("gw r")}`,
147
153
  value: "release",
148
154
  },
149
155
  {
150
- name: `[a] 💾 管理 stash ${colors.dim("gw s")}`,
156
+ name: `[a] 管理 stash ${colors.dim("gw s")}`,
151
157
  value: "stash",
152
158
  },
153
159
  {
154
- name: `[b] 📊 查看日志 ${colors.dim("gw log")}`,
160
+ name: `[b] 查看日志 ${colors.dim("gw log")}`,
155
161
  value: "log",
156
162
  },
157
163
  {
@@ -263,9 +269,8 @@ cli
263
269
  });
264
270
 
265
271
  cli
266
- .command("delete [branch]", "删除本地/远程分支")
267
- .alias("del")
268
- .alias("d")
272
+ .command("br:del [branch]", "删除本地/远程分支")
273
+ .alias("brd")
269
274
  .action(async (branch?: string) => {
270
275
  await checkForUpdates(version, "@zjex/git-workflow");
271
276
  checkGitRepo();
@@ -291,7 +296,7 @@ cli
291
296
  });
292
297
 
293
298
  cli
294
- .command("tag:delete", "删除 tag")
299
+ .command("tag:del", "删除 tag")
295
300
  .alias("td")
296
301
  .action(async () => {
297
302
  await checkForUpdates(version, "@zjex/git-workflow");
@@ -308,6 +313,15 @@ cli
308
313
  return updateTag();
309
314
  });
310
315
 
316
+ cli
317
+ .command("tag:clean", "清理无效 tag")
318
+ .alias("tc")
319
+ .action(async () => {
320
+ await checkForUpdates(version, "@zjex/git-workflow");
321
+ checkGitRepo();
322
+ return cleanInvalidTags();
323
+ });
324
+
311
325
  cli
312
326
  .command("release", "交互式选择版本号并更新 package.json")
313
327
  .alias("r")
@@ -364,77 +378,80 @@ cli
364
378
  return log(logOptions);
365
379
  });
366
380
 
367
- cli.command("clean", "清理缓存和临时文件").action(async () => {
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
- // 静默失败
381
+ cli
382
+ .command("clean", "清理缓存和临时文件")
383
+ .alias("cc")
384
+ .action(async () => {
385
+ const { clearUpdateCache } = await import("./update-notifier.js");
386
+ const { existsSync, unlinkSync, readdirSync } = await import("fs");
387
+ const { homedir, tmpdir } = await import("os");
388
+ const { join } = await import("path");
389
+ const { select } = await import("@inquirer/prompts");
390
+
391
+ let cleanedCount = 0;
392
+ let deletedGlobalConfig = false;
393
+
394
+ // 检查全局配置文件是否存在
395
+ const globalConfig = join(homedir(), ".gwrc.json");
396
+ const hasGlobalConfig = existsSync(globalConfig);
397
+
398
+ // 如果有全局配置文件,询问是否删除
399
+ if (hasGlobalConfig) {
400
+ const shouldDeleteConfig = await select({
401
+ message: "检测到全局配置文件,是否删除?",
402
+ choices: [
403
+ { name: "否,保留配置文件", value: false },
404
+ { name: "是,删除配置文件", value: true },
405
+ ],
406
+ theme,
407
+ });
408
+
409
+ if (shouldDeleteConfig) {
410
+ try {
411
+ unlinkSync(globalConfig);
412
+ cleanedCount++;
413
+ deletedGlobalConfig = true;
414
+ } catch {
415
+ // 静默失败
416
+ }
399
417
  }
400
418
  }
401
- }
402
419
 
403
- // 1. 清理更新缓存
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
- // 静默失败
420
+ // 1. 清理更新缓存
421
+ clearUpdateCache();
422
+ cleanedCount++;
423
+
424
+ // 2. 清理临时 commit 消息文件
425
+ try {
426
+ const tmpDir = tmpdir();
427
+ const files = readdirSync(tmpDir);
428
+ const gwTmpFiles = files.filter((f) => f.startsWith(".gw-commit-msg-"));
429
+
430
+ for (const file of gwTmpFiles) {
431
+ try {
432
+ unlinkSync(join(tmpDir, file));
433
+ cleanedCount++;
434
+ } catch {
435
+ // 静默失败
436
+ }
419
437
  }
438
+ } catch {
439
+ // 静默失败
420
440
  }
421
- } catch {
422
- // 静默失败
423
- }
424
-
425
- console.log("");
426
- console.log(colors.green(`✔ 已清理 ${cleanedCount} 个文件`));
427
441
 
428
- if (deletedGlobalConfig) {
429
442
  console.log("");
430
- console.log(colors.yellow("⚠️ 全局配置文件已删除"));
431
- console.log(
432
- colors.dim(` 如需重新配置,请运行: ${colors.cyan("gw init")}`)
433
- );
434
- }
443
+ console.log(colors.green(`✔ 已清理 ${cleanedCount} 个文件`));
444
+
445
+ if (deletedGlobalConfig) {
446
+ console.log("");
447
+ console.log(colors.yellow("⚠️ 全局配置文件已删除"));
448
+ console.log(
449
+ colors.dim(` 如需重新配置,请运行: ${colors.cyan("gw init")}`)
450
+ );
451
+ }
435
452
 
436
- console.log("");
437
- });
453
+ console.log("");
454
+ });
438
455
 
439
456
  // 不使用 cac 的 version,手动处理 --version 和 --help
440
457
  cli.option("-v, --version", "显示版本号");
@@ -181,7 +181,7 @@ async function showUpdateMessage(
181
181
  console.log(
182
182
  boxen(message, {
183
183
  padding: { top: 1, bottom: 1, left: 3, right: 3 },
184
- margin: 1,
184
+ margin: { top: 0, bottom: 0, left: 1, right: 1 },
185
185
  borderStyle: "round",
186
186
  borderColor: "yellow",
187
187
  align: "center",
@@ -0,0 +1,409 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ describe("命令别名测试", () => {
4
+ describe("分支命令", () => {
5
+ it("应该支持 feature 命令及其别名", () => {
6
+ const commands = ["feature", "feat", "f"];
7
+ const expectedCommand = "feature";
8
+
9
+ commands.forEach((cmd) => {
10
+ // 验证命令映射
11
+ const isValid = cmd === "feature" || cmd === "feat" || cmd === "f";
12
+ expect(isValid).toBe(true);
13
+ });
14
+ });
15
+
16
+ it("应该支持 hotfix 命令及其别名", () => {
17
+ const commands = ["hotfix", "fix", "h"];
18
+ const expectedCommand = "hotfix";
19
+
20
+ commands.forEach((cmd) => {
21
+ const isValid = cmd === "hotfix" || cmd === "fix" || cmd === "h";
22
+ expect(isValid).toBe(true);
23
+ });
24
+ });
25
+
26
+ it("应该支持 br:del 命令及其别名", () => {
27
+ const commands = ["br:del", "brd"];
28
+ const expectedCommand = "br:del";
29
+
30
+ commands.forEach((cmd) => {
31
+ const isValid = cmd === "br:del" || cmd === "brd";
32
+ expect(isValid).toBe(true);
33
+ });
34
+ });
35
+
36
+ it("br:del 命令应该替代旧的 delete 命令", () => {
37
+ const newCommands = ["br:del", "brd"];
38
+ const oldCommands = ["delete", "del", "d"];
39
+
40
+ // 新命令应该存在
41
+ expect(newCommands.length).toBeGreaterThan(0);
42
+
43
+ // 验证新命令格式
44
+ expect(newCommands[0]).toBe("br:del");
45
+ expect(newCommands[1]).toBe("brd");
46
+ });
47
+ });
48
+
49
+ describe("Tag 命令", () => {
50
+ it("应该支持 tag 命令及其别名", () => {
51
+ const commands = ["tag", "t"];
52
+
53
+ commands.forEach((cmd) => {
54
+ const isValid = cmd === "tag" || cmd === "t";
55
+ expect(isValid).toBe(true);
56
+ });
57
+ });
58
+
59
+ it("应该支持 tags 命令及其别名", () => {
60
+ const commands = ["tags", "ts"];
61
+
62
+ commands.forEach((cmd) => {
63
+ const isValid = cmd === "tags" || cmd === "ts";
64
+ expect(isValid).toBe(true);
65
+ });
66
+ });
67
+
68
+ it("应该支持 tag:del 命令及其别名", () => {
69
+ const commands = ["tag:del", "td"];
70
+
71
+ commands.forEach((cmd) => {
72
+ const isValid = cmd === "tag:del" || cmd === "td";
73
+ expect(isValid).toBe(true);
74
+ });
75
+ });
76
+
77
+ it("应该支持 tag:update 命令及其别名", () => {
78
+ const commands = ["tag:update", "tu"];
79
+
80
+ commands.forEach((cmd) => {
81
+ const isValid = cmd === "tag:update" || cmd === "tu";
82
+ expect(isValid).toBe(true);
83
+ });
84
+ });
85
+
86
+ it("应该支持 tag:clean 命令及其别名", () => {
87
+ const commands = ["tag:clean", "tc"];
88
+
89
+ commands.forEach((cmd) => {
90
+ const isValid = cmd === "tag:clean" || cmd === "tc";
91
+ expect(isValid).toBe(true);
92
+ });
93
+ });
94
+ });
95
+
96
+ describe("提交命令", () => {
97
+ it("应该支持 commit 命令及其别名", () => {
98
+ const commands = ["commit", "c", "cm"];
99
+
100
+ commands.forEach((cmd) => {
101
+ const isValid = cmd === "commit" || cmd === "c" || cmd === "cm";
102
+ expect(isValid).toBe(true);
103
+ });
104
+ });
105
+ });
106
+
107
+ describe("Stash 命令", () => {
108
+ it("应该支持 stash 命令及其别名", () => {
109
+ const commands = ["stash", "s", "st"];
110
+
111
+ commands.forEach((cmd) => {
112
+ const isValid = cmd === "stash" || cmd === "s" || cmd === "st";
113
+ expect(isValid).toBe(true);
114
+ });
115
+ });
116
+ });
117
+
118
+ describe("日志命令", () => {
119
+ it("应该支持 log 命令及其别名", () => {
120
+ const commands = ["log", "ls", "l"];
121
+
122
+ commands.forEach((cmd) => {
123
+ const isValid = cmd === "log" || cmd === "ls" || cmd === "l";
124
+ expect(isValid).toBe(true);
125
+ });
126
+ });
127
+ });
128
+
129
+ describe("版本命令", () => {
130
+ it("应该支持 release 命令及其别名", () => {
131
+ const commands = ["release", "r"];
132
+
133
+ commands.forEach((cmd) => {
134
+ const isValid = cmd === "release" || cmd === "r";
135
+ expect(isValid).toBe(true);
136
+ });
137
+ });
138
+ });
139
+
140
+ describe("更新命令", () => {
141
+ it("应该支持 update 命令及其别名", () => {
142
+ const commands = ["update", "upt"];
143
+
144
+ commands.forEach((cmd) => {
145
+ const isValid = cmd === "update" || cmd === "upt";
146
+ expect(isValid).toBe(true);
147
+ });
148
+ });
149
+ });
150
+
151
+ describe("清理命令", () => {
152
+ it("应该支持 clean 命令及其别名", () => {
153
+ const commands = ["clean", "cc"];
154
+
155
+ commands.forEach((cmd) => {
156
+ const isValid = cmd === "clean" || cmd === "cc";
157
+ expect(isValid).toBe(true);
158
+ });
159
+ });
160
+ });
161
+
162
+ describe("初始化命令", () => {
163
+ it("应该支持 init 命令", () => {
164
+ const command = "init";
165
+ expect(command).toBe("init");
166
+ });
167
+
168
+ it("init 命令不应该有别名", () => {
169
+ const aliases: string[] = [];
170
+ expect(aliases.length).toBe(0);
171
+ });
172
+ });
173
+
174
+ describe("命令命名规范", () => {
175
+ it("分支删除命令应该使用 br:del 格式", () => {
176
+ const command = "br:del";
177
+ expect(command).toMatch(/^br:del$/);
178
+ expect(command).toContain(":");
179
+ });
180
+
181
+ it("tag 删除命令应该使用 tag:del 格式", () => {
182
+ const command = "tag:del";
183
+ expect(command).toMatch(/^tag:del$/);
184
+ expect(command).toContain(":");
185
+ });
186
+
187
+ it("tag 更新命令应该使用 tag:update 格式", () => {
188
+ const command = "tag:update";
189
+ expect(command).toMatch(/^tag:update$/);
190
+ expect(command).toContain(":");
191
+ });
192
+
193
+ it("tag 清理命令应该使用 tag:clean 格式", () => {
194
+ const command = "tag:clean";
195
+ expect(command).toMatch(/^tag:clean$/);
196
+ expect(command).toContain(":");
197
+ });
198
+
199
+ it("子命令应该使用冒号分隔", () => {
200
+ const subCommands = ["br:del", "tag:del", "tag:update", "tag:clean"];
201
+
202
+ subCommands.forEach((cmd) => {
203
+ expect(cmd).toContain(":");
204
+ const parts = cmd.split(":");
205
+ expect(parts.length).toBe(2);
206
+ expect(parts[0].length).toBeGreaterThan(0);
207
+ expect(parts[1].length).toBeGreaterThan(0);
208
+ });
209
+ });
210
+ });
211
+
212
+ describe("别名简洁性", () => {
213
+ it("所有别名应该不超过 4 个字符", () => {
214
+ const aliases = [
215
+ "f",
216
+ "feat",
217
+ "h",
218
+ "fix",
219
+ "brd",
220
+ "t",
221
+ "ts",
222
+ "td",
223
+ "tu",
224
+ "tc",
225
+ "c",
226
+ "cm",
227
+ "s",
228
+ "st",
229
+ "l",
230
+ "ls",
231
+ "r",
232
+ "upt",
233
+ "cc",
234
+ ];
235
+
236
+ aliases.forEach((alias) => {
237
+ expect(alias.length).toBeLessThanOrEqual(4);
238
+ });
239
+ });
240
+
241
+ it("单字母别名应该是最常用的命令", () => {
242
+ const singleLetterAliases = {
243
+ f: "feature",
244
+ h: "hotfix",
245
+ t: "tag",
246
+ c: "commit",
247
+ s: "stash",
248
+ l: "log",
249
+ r: "release",
250
+ };
251
+
252
+ Object.entries(singleLetterAliases).forEach(([alias, command]) => {
253
+ expect(alias.length).toBe(1);
254
+ expect(command.length).toBeGreaterThan(1);
255
+ });
256
+ });
257
+
258
+ it("两字母别名应该是次常用的命令", () => {
259
+ const twoLetterAliases = {
260
+ ts: "tags",
261
+ td: "tag:del",
262
+ tu: "tag:update",
263
+ tc: "tag:clean",
264
+ cm: "commit",
265
+ st: "stash",
266
+ ls: "log",
267
+ cc: "clean",
268
+ };
269
+
270
+ Object.entries(twoLetterAliases).forEach(([alias, command]) => {
271
+ expect(alias.length).toBe(2);
272
+ });
273
+ });
274
+
275
+ it("三字母别名应该是较少使用的命令", () => {
276
+ const threeLetterAliases = {
277
+ brd: "br:del",
278
+ upt: "update",
279
+ };
280
+
281
+ Object.entries(threeLetterAliases).forEach(([alias, command]) => {
282
+ expect(alias.length).toBe(3);
283
+ });
284
+ });
285
+
286
+ it("四字母别名应该是完整单词缩写", () => {
287
+ const fourLetterAliases = {
288
+ feat: "feature",
289
+ fix: "hotfix",
290
+ };
291
+
292
+ Object.entries(fourLetterAliases).forEach(([alias, command]) => {
293
+ expect(alias.length).toBeGreaterThanOrEqual(3);
294
+ expect(alias.length).toBeLessThanOrEqual(4);
295
+ });
296
+ });
297
+ });
298
+
299
+ describe("命令一致性", () => {
300
+ it("删除操作应该使用统一的 del 后缀", () => {
301
+ const deleteCommands = ["br:del", "tag:del"];
302
+
303
+ deleteCommands.forEach((cmd) => {
304
+ expect(cmd).toMatch(/del$/);
305
+ });
306
+ });
307
+
308
+ it("删除操作的别名应该以 d 结尾", () => {
309
+ const deleteAliases = ["brd", "td"];
310
+
311
+ deleteAliases.forEach((alias) => {
312
+ expect(alias).toMatch(/d$/);
313
+ });
314
+ });
315
+
316
+ it("清理操作应该使用 clean 命名", () => {
317
+ const cleanCommands = ["clean", "tag:clean"];
318
+
319
+ cleanCommands.forEach((cmd) => {
320
+ expect(cmd).toContain("clean");
321
+ });
322
+ });
323
+
324
+ it("清理操作的别名应该以 c 结尾", () => {
325
+ const cleanAliases = ["cc", "tc"];
326
+
327
+ cleanAliases.forEach((alias) => {
328
+ expect(alias).toMatch(/c$/);
329
+ });
330
+ });
331
+ });
332
+
333
+ describe("命令分组", () => {
334
+ it("应该有分支管理命令组", () => {
335
+ const branchCommands = ["feature", "hotfix", "br:del"];
336
+ expect(branchCommands.length).toBe(3);
337
+ });
338
+
339
+ it("应该有 tag 管理命令组", () => {
340
+ const tagCommands = ["tag", "tags", "tag:del", "tag:update", "tag:clean"];
341
+ expect(tagCommands.length).toBe(5);
342
+ });
343
+
344
+ it("应该有提交管理命令组", () => {
345
+ const commitCommands = ["commit", "log"];
346
+ expect(commitCommands.length).toBe(2);
347
+ });
348
+
349
+ it("应该有工具管理命令组", () => {
350
+ const toolCommands = ["init", "update", "clean"];
351
+ expect(toolCommands.length).toBe(3);
352
+ });
353
+
354
+ it("应该有其他辅助命令组", () => {
355
+ const otherCommands = ["stash", "release"];
356
+ expect(otherCommands.length).toBe(2);
357
+ });
358
+ });
359
+
360
+ describe("命令总数统计", () => {
361
+ it("应该有正确数量的主命令", () => {
362
+ const mainCommands = [
363
+ "feature",
364
+ "hotfix",
365
+ "br:del",
366
+ "tag",
367
+ "tags",
368
+ "tag:del",
369
+ "tag:update",
370
+ "tag:clean",
371
+ "commit",
372
+ "log",
373
+ "stash",
374
+ "release",
375
+ "init",
376
+ "update",
377
+ "clean",
378
+ ];
379
+
380
+ expect(mainCommands.length).toBe(15);
381
+ });
382
+
383
+ it("应该有正确数量的别名", () => {
384
+ const aliases = [
385
+ "f",
386
+ "feat",
387
+ "h",
388
+ "fix",
389
+ "brd",
390
+ "t",
391
+ "ts",
392
+ "td",
393
+ "tu",
394
+ "tc",
395
+ "c",
396
+ "cm",
397
+ "l",
398
+ "ls",
399
+ "s",
400
+ "st",
401
+ "r",
402
+ "upt",
403
+ "cc",
404
+ ];
405
+
406
+ expect(aliases.length).toBe(19);
407
+ });
408
+ });
409
+ });