@zjex/git-workflow 0.5.2 → 0.6.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/tests/tag.test.ts CHANGED
@@ -1,46 +1,89 @@
1
1
  import { describe, it, expect } from "vitest";
2
+ import {
3
+ extractTagPrefix,
4
+ getLatestTagCommand,
5
+ isValidVersionTag,
6
+ normalizeTagLookupStrategy,
7
+ shouldFetchAllTagsForCreateTag,
8
+ } from "../src/tag-utils";
2
9
 
3
10
  describe("Tag 功能测试", () => {
11
+ describe("Tag 策略", () => {
12
+ it("默认应该使用 latest 策略", () => {
13
+ expect(normalizeTagLookupStrategy(undefined)).toBe("latest");
14
+ expect(normalizeTagLookupStrategy("unknown")).toBe("latest");
15
+ });
16
+
17
+ it("应该识别 latest 策略", () => {
18
+ expect(normalizeTagLookupStrategy("latest")).toBe("latest");
19
+ });
20
+
21
+ it("all 策略应该始终全量拉取 tags", () => {
22
+ expect(shouldFetchAllTagsForCreateTag("all", "v")).toBe(true);
23
+ expect(shouldFetchAllTagsForCreateTag("all")).toBe(true);
24
+ });
25
+
26
+ it("latest 策略在已知前缀时不应该全量拉取 tags", () => {
27
+ expect(shouldFetchAllTagsForCreateTag("latest", "v")).toBe(false);
28
+ });
29
+
30
+ it("latest 策略在未知前缀时应该回退为全量拉取", () => {
31
+ expect(shouldFetchAllTagsForCreateTag("latest")).toBe(true);
32
+ });
33
+
34
+ it("应该生成全量排序的最新 tag 查询命令", () => {
35
+ expect(getLatestTagCommand("v", "all")).toBe(
36
+ 'git tag -l "v*" --sort=-v:refname',
37
+ );
38
+ });
39
+
40
+ it("应该生成按时间获取最新 tag 的查询命令", () => {
41
+ expect(getLatestTagCommand("v", "latest")).toBe(
42
+ 'git for-each-ref --sort=-creatordate --format="%(refname:short)" "refs/tags/v*"',
43
+ );
44
+ });
45
+ });
46
+
4
47
  describe("前缀提取", () => {
5
48
  it("应该正确提取 v 前缀", () => {
6
49
  const tag = "v0.1.0";
7
- const prefix = tag.replace(/[0-9].*/, "");
50
+ const prefix = extractTagPrefix(tag);
8
51
  expect(prefix).toBe("v");
9
52
  });
10
53
 
11
54
  it("应该正确提取 release- 前缀", () => {
12
55
  const tag = "release-1.0.0";
13
- const prefix = tag.replace(/[0-9].*/, "");
56
+ const prefix = extractTagPrefix(tag);
14
57
  expect(prefix).toBe("release-");
15
58
  });
16
59
 
17
60
  it("应该正确提取 @ 开头的 scope 前缀", () => {
18
61
  const tag = "@scope/package@1.0.0";
19
- const prefix = tag.replace(/[0-9].*/, "");
62
+ const prefix = extractTagPrefix(tag);
20
63
  expect(prefix).toBe("@scope/package@");
21
64
  });
22
65
 
23
66
  it("应该正确处理无前缀 tag", () => {
24
67
  const tag = "1.0.0";
25
- const prefix = tag.replace(/[0-9].*/, "") || "(无前缀)";
68
+ const prefix = extractTagPrefix(tag) || "(无前缀)";
26
69
  expect(prefix).toBe("(无前缀)");
27
70
  });
28
71
 
29
72
  it("应该正确提取 g 前缀", () => {
30
73
  const tag = "g0.1.0";
31
- const prefix = tag.replace(/[0-9].*/, "");
74
+ const prefix = extractTagPrefix(tag);
32
75
  expect(prefix).toBe("g");
33
76
  });
34
77
 
35
78
  it("应该正确提取带下划线的前缀", () => {
36
79
  const tag = "version_1.0.0";
37
- const prefix = tag.replace(/[0-9].*/, "");
80
+ const prefix = extractTagPrefix(tag);
38
81
  expect(prefix).toBe("version_");
39
82
  });
40
83
 
41
84
  it("应该正确提取带点的前缀", () => {
42
85
  const tag = "v.1.0.0";
43
- const prefix = tag.replace(/[0-9].*/, "");
86
+ const prefix = extractTagPrefix(tag);
44
87
  expect(prefix).toBe("v.");
45
88
  });
46
89
  });
@@ -51,7 +94,7 @@ describe("Tag 功能测试", () => {
51
94
  const grouped = new Map<string, string[]>();
52
95
 
53
96
  tags.forEach((tag) => {
54
- const prefix = tag.replace(/[0-9].*/, "") || "(无前缀)";
97
+ const prefix = extractTagPrefix(tag) || "(无前缀)";
55
98
  if (!grouped.has(prefix)) {
56
99
  grouped.set(prefix, []);
57
100
  }
@@ -75,7 +118,7 @@ describe("Tag 功能测试", () => {
75
118
  const grouped = new Map<string, string[]>();
76
119
 
77
120
  tags.forEach((tag) => {
78
- const prefix = tag.replace(/[0-9].*/, "") || "(无前缀)";
121
+ const prefix = extractTagPrefix(tag) || "(无前缀)";
79
122
  if (!grouped.has(prefix)) {
80
123
  grouped.set(prefix, []);
81
124
  }
@@ -94,7 +137,7 @@ describe("Tag 功能测试", () => {
94
137
  const grouped = new Map<string, string[]>();
95
138
 
96
139
  tags.forEach((tag) => {
97
- const prefix = tag.replace(/[0-9].*/, "") || "(无前缀)";
140
+ const prefix = extractTagPrefix(tag) || "(无前缀)";
98
141
  if (!grouped.has(prefix)) {
99
142
  grouped.set(prefix, []);
100
143
  }
@@ -397,56 +440,56 @@ describe("Tag 功能测试", () => {
397
440
  describe("无效标签检测", () => {
398
441
  it("应该识别不包含数字的标签为无效", () => {
399
442
  const tag = "vnull";
400
- const isInvalid = !/\d/.test(tag);
443
+ const isInvalid = !isValidVersionTag(tag);
401
444
 
402
445
  expect(isInvalid).toBe(true);
403
446
  });
404
447
 
405
448
  it("应该识别 vundefined 为无效标签", () => {
406
449
  const tag = "vundefined";
407
- const isInvalid = !/\d/.test(tag);
450
+ const isInvalid = !isValidVersionTag(tag);
408
451
 
409
452
  expect(isInvalid).toBe(true);
410
453
  });
411
454
 
412
455
  it("应该识别空版本号为无效标签", () => {
413
456
  const tag = "v";
414
- const isInvalid = !/\d/.test(tag);
457
+ const isInvalid = !isValidVersionTag(tag);
415
458
 
416
459
  expect(isInvalid).toBe(true);
417
460
  });
418
461
 
419
462
  it("应该识别纯字母标签为无效", () => {
420
463
  const tag = "release";
421
- const isInvalid = !/\d/.test(tag);
464
+ const isInvalid = !isValidVersionTag(tag);
422
465
 
423
466
  expect(isInvalid).toBe(true);
424
467
  });
425
468
 
426
469
  it("应该识别包含数字的标签为有效", () => {
427
470
  const tag = "v1.0.0";
428
- const isValid = /\d/.test(tag);
471
+ const isValid = isValidVersionTag(tag);
429
472
 
430
473
  expect(isValid).toBe(true);
431
474
  });
432
475
 
433
476
  it("应该识别预发布版本为有效", () => {
434
477
  const tag = "v1.0.0-beta.1";
435
- const isValid = /\d/.test(tag);
478
+ const isValid = isValidVersionTag(tag);
436
479
 
437
480
  expect(isValid).toBe(true);
438
481
  });
439
482
 
440
483
  it("应该识别无前缀版本号为有效", () => {
441
484
  const tag = "1.0.0";
442
- const isValid = /\d/.test(tag);
485
+ const isValid = isValidVersionTag(tag);
443
486
 
444
487
  expect(isValid).toBe(true);
445
488
  });
446
489
 
447
490
  it("应该识别带前缀的单数字版本为有效", () => {
448
491
  const tag = "v1";
449
- const isValid = /\d/.test(tag);
492
+ const isValid = isValidVersionTag(tag);
450
493
 
451
494
  expect(isValid).toBe(true);
452
495
  });
@@ -463,7 +506,7 @@ describe("Tag 功能测试", () => {
463
506
  "v",
464
507
  "v2.0.0",
465
508
  ];
466
- const invalidTags = allTags.filter((tag) => !/\d/.test(tag));
509
+ const invalidTags = allTags.filter((tag) => !isValidVersionTag(tag));
467
510
 
468
511
  expect(invalidTags).toEqual(["vnull", "vundefined", "v"]);
469
512
  expect(invalidTags.length).toBe(3);
@@ -471,7 +514,7 @@ describe("Tag 功能测试", () => {
471
514
 
472
515
  it("应该在没有无效标签时返回空数组", () => {
473
516
  const allTags = ["v1.0.0", "v1.1.0", "v2.0.0", "release-1.0.0"];
474
- const invalidTags = allTags.filter((tag) => !/\d/.test(tag));
517
+ const invalidTags = allTags.filter((tag) => !isValidVersionTag(tag));
475
518
 
476
519
  expect(invalidTags).toEqual([]);
477
520
  expect(invalidTags.length).toBe(0);
@@ -479,7 +522,7 @@ describe("Tag 功能测试", () => {
479
522
 
480
523
  it("应该在全是无效标签时返回所有标签", () => {
481
524
  const allTags = ["vnull", "vundefined", "v", "release"];
482
- const invalidTags = allTags.filter((tag) => !/\d/.test(tag));
525
+ const invalidTags = allTags.filter((tag) => !isValidVersionTag(tag));
483
526
 
484
527
  expect(invalidTags).toEqual(allTags);
485
528
  expect(invalidTags.length).toBe(4);
@@ -493,7 +536,7 @@ describe("Tag 功能测试", () => {
493
536
  "vundefined",
494
537
  "v2.0.0-beta.1",
495
538
  ];
496
- const validTags = allTags.filter((tag) => /\d/.test(tag));
539
+ const validTags = allTags.filter(isValidVersionTag);
497
540
 
498
541
  expect(validTags).toEqual(["v1.0.0", "v1.1.0", "v2.0.0-beta.1"]);
499
542
  expect(validTags.length).toBe(3);
@@ -501,7 +544,7 @@ describe("Tag 功能测试", () => {
501
544
 
502
545
  it("应该处理空标签列表", () => {
503
546
  const allTags: string[] = [];
504
- const invalidTags = allTags.filter((tag) => !/\d/.test(tag));
547
+ const invalidTags = allTags.filter((tag) => !isValidVersionTag(tag));
505
548
 
506
549
  expect(invalidTags).toEqual([]);
507
550
  expect(invalidTags.length).toBe(0);
@@ -516,7 +559,7 @@ describe("Tag 功能测试", () => {
516
559
  "g1.0.0",
517
560
  "tag",
518
561
  ];
519
- const invalidTags = allTags.filter((tag) => !/\d/.test(tag));
562
+ const invalidTags = allTags.filter((tag) => !isValidVersionTag(tag));
520
563
 
521
564
  expect(invalidTags).toEqual(["vnull", "release-", "hotfix", "tag"]);
522
565
  expect(invalidTags.length).toBe(4);
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { execSync } from "child_process";
2
+ import { execSync, spawn } from "child_process";
3
3
  import { readFileSync, writeFileSync, existsSync, unlinkSync } from "fs";
4
4
  import { homedir } from "os";
5
5
  import { checkForUpdates, clearUpdateCache } from "../src/update-notifier";
@@ -28,6 +28,11 @@ describe("Update Notifier 模块测试", () => {
28
28
  vi.clearAllMocks();
29
29
  vi.mocked(homedir).mockReturnValue("/home/user");
30
30
  vi.useFakeTimers();
31
+
32
+ // Mock spawn 返回一个带 unref 的对象
33
+ vi.mocked(spawn).mockReturnValue({
34
+ unref: vi.fn(),
35
+ } as any);
31
36
  });
32
37
 
33
38
  afterEach(() => {
@@ -62,16 +67,20 @@ describe("Update Notifier 模块测试", () => {
62
67
  });
63
68
 
64
69
  describe("checkForUpdates 函数", () => {
65
- it("没有缓存时应该后台检查", async () => {
70
+ it("没有缓存时应该启动后台检查", async () => {
66
71
  vi.mocked(existsSync).mockReturnValue(false);
67
- vi.mocked(execSync).mockReturnValue("1.0.1" as any);
68
72
 
69
73
  await checkForUpdates("1.0.0");
70
74
 
71
- // 等待异步操作
72
- await vi.runAllTimersAsync();
73
-
74
- expect(writeFileSync).toHaveBeenCalled();
75
+ // 没有缓存时应该启动子进程检查
76
+ expect(spawn).toHaveBeenCalledWith(
77
+ "node",
78
+ expect.arrayContaining(["-e", expect.any(String)]),
79
+ expect.objectContaining({
80
+ detached: true,
81
+ stdio: "ignore",
82
+ })
83
+ );
75
84
  });
76
85
 
77
86
  it("版本相同时不应该显示提示", async () => {
@@ -153,61 +162,20 @@ describe("Update Notifier 模块测试", () => {
153
162
  consoleSpy.mockRestore();
154
163
  });
155
164
 
156
- it("每次运行都应该后台检查最新版本", async () => {
157
- const mockCache = {
158
- lastCheck: Date.now(),
159
- latestVersion: "1.0.0",
160
- checkedVersion: "1.0.0",
161
- };
162
-
163
- vi.mocked(existsSync).mockReturnValue(true);
164
- vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockCache));
165
- vi.mocked(execSync).mockReturnValue("1.0.1" as any);
166
-
167
- await checkForUpdates("1.0.0");
168
- await vi.runAllTimersAsync();
169
-
170
- // 每次运行都应该后台检查
171
- expect(writeFileSync).toHaveBeenCalled();
172
- });
173
-
174
- it("后台检查应该更新缓存中的最新版本", async () => {
175
- const mockCache = {
176
- lastCheck: Date.now() - 2 * 60 * 60 * 1000,
177
- latestVersion: "1.0.0",
178
- checkedVersion: "1.0.0",
179
- };
180
-
181
- vi.mocked(existsSync).mockReturnValue(true);
182
- vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockCache));
183
- vi.mocked(execSync).mockReturnValue("1.0.1" as any);
184
-
185
- await checkForUpdates("1.0.0");
186
- await vi.runAllTimersAsync();
187
-
188
- expect(writeFileSync).toHaveBeenCalled();
189
- });
190
-
191
- it("有新版本时每次都应该后台检查", async () => {
165
+ it("每次运行都应该启动后台检查", async () => {
192
166
  const mockCache = {
193
167
  lastCheck: Date.now(), // 刚刚检查过
194
- latestVersion: "1.0.1", // 有新版本
168
+ latestVersion: "1.0.0",
195
169
  checkedVersion: "1.0.0",
196
170
  };
197
171
 
198
172
  vi.mocked(existsSync).mockReturnValue(true);
199
173
  vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockCache));
200
- vi.mocked(execSync).mockReturnValue("1.0.2" as any);
201
-
202
- const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
203
174
 
204
175
  await checkForUpdates("1.0.0");
205
- await vi.runAllTimersAsync();
206
-
207
- // 即使刚检查过,有新版本时也应该继续检查
208
- expect(writeFileSync).toHaveBeenCalled();
209
176
 
210
- consoleSpy.mockRestore();
177
+ // 每次都应该启动子进程检查
178
+ expect(spawn).toHaveBeenCalled();
211
179
  });
212
180
 
213
181
  it("缓存文件损坏时应该静默处理", async () => {
@@ -310,7 +278,7 @@ describe("Update Notifier 模块测试", () => {
310
278
  { current: "1.0.0", latest: "1.0.1", shouldShow: true },
311
279
  { current: "1.0.0", latest: "1.1.0", shouldShow: true },
312
280
  { current: "1.0.0", latest: "2.0.0", shouldShow: true },
313
- { current: "1.0.1", latest: "1.0.0", shouldShow: true }, // 回滚场景也应该提示
281
+ { current: "1.0.1", latest: "1.0.0", shouldShow: false }, // 本地版本更高,不提示
314
282
  { current: "1.0.0", latest: "1.0.0", shouldShow: false },
315
283
  ];
316
284
 
@@ -343,23 +311,8 @@ describe("Update Notifier 模块测试", () => {
343
311
  });
344
312
 
345
313
  describe("缓存读写", () => {
346
- it("应该正确写入缓存", async () => {
347
- vi.mocked(existsSync).mockReturnValue(false);
348
- vi.mocked(execSync).mockReturnValue("1.0.1" as any);
349
-
350
- await checkForUpdates("1.0.0");
351
- await vi.runAllTimersAsync();
352
-
353
- expect(writeFileSync).toHaveBeenCalledWith(
354
- "/home/user/.gw-update-check",
355
- expect.stringContaining("1.0.1"),
356
- "utf-8"
357
- );
358
- });
359
-
360
314
  it("写入缓存失败时应该静默处理", async () => {
361
315
  vi.mocked(existsSync).mockReturnValue(false);
362
- vi.mocked(execSync).mockReturnValue("1.0.1" as any);
363
316
  vi.mocked(writeFileSync).mockImplementation(() => {
364
317
  throw new Error("Write failed");
365
318
  });
@@ -380,24 +333,24 @@ describe("Update Notifier 模块测试", () => {
380
333
  describe("网络请求", () => {
381
334
  it("获取最新版本失败时应该静默处理", async () => {
382
335
  vi.mocked(existsSync).mockReturnValue(false);
383
- vi.mocked(execSync).mockImplementation(() => {
384
- throw new Error("Network error");
336
+ vi.mocked(spawn).mockImplementation(() => {
337
+ throw new Error("Spawn error");
385
338
  });
386
339
 
387
340
  await expect(checkForUpdates("1.0.0")).resolves.not.toThrow();
388
341
  });
389
342
 
390
- it("应该使用正确的 npm 命令", async () => {
343
+ it("后台检查应该使用正确的参数", async () => {
391
344
  vi.mocked(existsSync).mockReturnValue(false);
392
- vi.mocked(execSync).mockReturnValue("1.0.1" as any);
393
345
 
394
346
  await checkForUpdates("1.0.0", "@zjex/git-workflow");
395
- await vi.runAllTimersAsync();
396
347
 
397
- expect(execSync).toHaveBeenCalledWith(
398
- "npm view @zjex/git-workflow version",
348
+ expect(spawn).toHaveBeenCalledWith(
349
+ "node",
350
+ expect.arrayContaining(["-e", expect.stringContaining("npm view @zjex/git-workflow version")]),
399
351
  expect.objectContaining({
400
- timeout: 3000,
352
+ detached: true,
353
+ stdio: "ignore",
401
354
  })
402
355
  );
403
356
  });