@zjex/git-workflow 0.2.23 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/.github/workflows/deploy-docs.yml +68 -0
  2. package/.github/workflows/test.yml +53 -0
  3. package/.husky/pre-commit +19 -0
  4. package/README.md +74 -1013
  5. package/TESTING.md +436 -0
  6. package/dist/index.js +104 -14
  7. package/docs/.vitepress/cache/deps/_metadata.json +52 -0
  8. package/docs/.vitepress/cache/deps/chunk-2CLQ7TTZ.js +9719 -0
  9. package/docs/.vitepress/cache/deps/chunk-2CLQ7TTZ.js.map +7 -0
  10. package/docs/.vitepress/cache/deps/chunk-LE5NDSFD.js +12824 -0
  11. package/docs/.vitepress/cache/deps/chunk-LE5NDSFD.js.map +7 -0
  12. package/docs/.vitepress/cache/deps/package.json +3 -0
  13. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4505 -0
  14. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  15. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +583 -0
  16. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  17. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1352 -0
  18. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
  19. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1665 -0
  20. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
  21. package/docs/.vitepress/cache/deps/vitepress___minisearch.js +1813 -0
  22. package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
  23. package/docs/.vitepress/cache/deps/vue.js +347 -0
  24. package/docs/.vitepress/cache/deps/vue.js.map +7 -0
  25. package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-2CLQ7TTZ.js +9719 -0
  26. package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-2CLQ7TTZ.js.map +7 -0
  27. package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-LE5NDSFD.js +12824 -0
  28. package/docs/.vitepress/cache/deps_temp_44e2fb0f/chunk-LE5NDSFD.js.map +7 -0
  29. package/docs/.vitepress/cache/deps_temp_44e2fb0f/package.json +3 -0
  30. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vue_devtools-api.js +4505 -0
  31. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vue_devtools-api.js.map +7 -0
  32. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_core.js +583 -0
  33. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_core.js.map +7 -0
  34. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_integrations_useFocusTrap.js +1352 -0
  35. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
  36. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___mark__js_src_vanilla__js.js +1665 -0
  37. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___mark__js_src_vanilla__js.js.map +7 -0
  38. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___minisearch.js +1813 -0
  39. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vitepress___minisearch.js.map +7 -0
  40. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vue.js +347 -0
  41. package/docs/.vitepress/cache/deps_temp_44e2fb0f/vue.js.map +7 -0
  42. package/docs/.vitepress/config.ts +167 -0
  43. package/docs/.vitepress/theme/custom.css +39 -0
  44. package/docs/.vitepress/theme/index.ts +4 -0
  45. package/docs/README.md +82 -0
  46. package/docs/commands/branch.md +468 -0
  47. package/docs/commands/commit.md +554 -0
  48. package/docs/commands/config.md +346 -0
  49. package/docs/commands/index.md +312 -0
  50. package/docs/commands/interactive.md +384 -0
  51. package/docs/commands/release.md +300 -0
  52. package/docs/commands/stash.md +309 -0
  53. package/docs/commands/tag.md +278 -0
  54. package/docs/commands/update.md +347 -0
  55. package/docs/config/ai-config.md +160 -0
  56. package/docs/config/branch-config.md +133 -0
  57. package/docs/config/commit-config.md +185 -0
  58. package/docs/config/config-file.md +776 -0
  59. package/docs/config/examples.md +279 -0
  60. package/docs/config/index.md +478 -0
  61. package/docs/guide/ai-commit.md +576 -0
  62. package/docs/guide/basic-usage.md +522 -0
  63. package/docs/guide/best-practices.md +426 -0
  64. package/docs/guide/branch-management.md +712 -0
  65. package/docs/guide/getting-started.md +294 -0
  66. package/docs/guide/index.md +168 -0
  67. package/docs/guide/installation.md +449 -0
  68. package/docs/guide/release-management.md +744 -0
  69. package/docs/guide/stash-management.md +608 -0
  70. package/docs/guide/tag-management.md +614 -0
  71. package/docs/index.md +205 -0
  72. package/docs/public/favicon.svg +21 -0
  73. package/docs/public/hero-logo.svg +43 -0
  74. package/docs/public/logo.svg +20 -0
  75. package/package.json +19 -3
  76. package/scripts/publish.js +55 -8
  77. package/scripts/publish.sh +20 -2
  78. package/scripts/release.sh +20 -2
  79. package/scripts/update-test-count.js +55 -0
  80. package/src/ai-service.ts +101 -15
  81. package/src/commands/init.ts +18 -0
  82. package/src/commands/tag.ts +1 -1
  83. package/src/config.ts +1 -0
  84. package/tests/COVERAGE_REPORT.md +222 -0
  85. package/tests/QUICK_START.md +242 -0
  86. package/tests/README.md +119 -0
  87. package/tests/TEST_SUMMARY.md +330 -0
  88. package/tests/ai-service.test.ts +705 -0
  89. package/tests/branch.test.ts +255 -0
  90. package/tests/commit.test.ts +85 -0
  91. package/tests/config.test.ts +311 -0
  92. package/tests/help.test.ts +134 -0
  93. package/tests/init.test.ts +582 -0
  94. package/tests/release.test.ts +333 -0
  95. package/tests/setup.ts +21 -0
  96. package/tests/stash.test.ts +376 -0
  97. package/tests/tag.test.ts +396 -0
  98. package/tests/update-notifier.test.ts +384 -0
  99. package/tests/update.test.ts +402 -0
  100. package/tests/utils.test.ts +229 -0
  101. package/vitest.config.ts +22 -0
  102. package/zjex-logo.svg +22 -0
  103. package/zjex-optimized.svg +34 -0
  104. package/zjex.svg +1 -0
@@ -0,0 +1,384 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { execSync } from "child_process";
3
+ import { readFileSync, writeFileSync, existsSync, unlinkSync } from "fs";
4
+ import { homedir } from "os";
5
+ import { checkForUpdates, clearUpdateCache } from "../src/update-notifier";
6
+
7
+ // Mock dependencies
8
+ vi.mock("child_process");
9
+ vi.mock("fs");
10
+ vi.mock("os");
11
+ vi.mock("boxen", () => ({
12
+ default: (content: string) => content,
13
+ }));
14
+ vi.mock("ora", () => ({
15
+ default: () => ({
16
+ start: () => ({
17
+ succeed: vi.fn(),
18
+ fail: vi.fn(),
19
+ }),
20
+ }),
21
+ }));
22
+ vi.mock("@inquirer/prompts", () => ({
23
+ select: vi.fn(),
24
+ }));
25
+
26
+ describe("Update Notifier 模块测试", () => {
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ vi.mocked(homedir).mockReturnValue("/home/user");
30
+ vi.useFakeTimers();
31
+ });
32
+
33
+ afterEach(() => {
34
+ vi.restoreAllMocks();
35
+ vi.useRealTimers();
36
+ });
37
+
38
+ describe("clearUpdateCache 函数", () => {
39
+ it("应该删除缓存文件", () => {
40
+ vi.mocked(existsSync).mockReturnValue(true);
41
+ vi.mocked(unlinkSync).mockImplementation(() => {});
42
+
43
+ clearUpdateCache();
44
+
45
+ expect(unlinkSync).toHaveBeenCalledWith("/home/user/.gw-update-check");
46
+ });
47
+
48
+ it("缓存文件不存在时不应该报错", () => {
49
+ vi.mocked(existsSync).mockReturnValue(false);
50
+
51
+ expect(() => clearUpdateCache()).not.toThrow();
52
+ });
53
+
54
+ it("删除失败时应该静默处理", () => {
55
+ vi.mocked(existsSync).mockReturnValue(true);
56
+ vi.mocked(unlinkSync).mockImplementation(() => {
57
+ throw new Error("Permission denied");
58
+ });
59
+
60
+ expect(() => clearUpdateCache()).not.toThrow();
61
+ });
62
+ });
63
+
64
+ describe("checkForUpdates 函数", () => {
65
+ it("没有缓存时应该后台检查", async () => {
66
+ vi.mocked(existsSync).mockReturnValue(false);
67
+ vi.mocked(execSync).mockReturnValue("1.0.1" as any);
68
+
69
+ await checkForUpdates("1.0.0");
70
+
71
+ // 等待异步操作
72
+ await vi.runAllTimersAsync();
73
+
74
+ expect(writeFileSync).toHaveBeenCalled();
75
+ });
76
+
77
+ it("版本相同时不应该显示提示", async () => {
78
+ const mockCache = {
79
+ lastCheck: Date.now(),
80
+ latestVersion: "1.0.0",
81
+ checkedVersion: "1.0.0",
82
+ };
83
+
84
+ vi.mocked(existsSync).mockReturnValue(true);
85
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockCache));
86
+
87
+ const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
88
+
89
+ await checkForUpdates("1.0.0");
90
+
91
+ expect(consoleSpy).not.toHaveBeenCalled();
92
+
93
+ consoleSpy.mockRestore();
94
+ });
95
+
96
+ it("有新版本时应该显示简单通知(非交互式)", async () => {
97
+ const mockCache = {
98
+ lastCheck: Date.now(),
99
+ latestVersion: "1.0.1",
100
+ checkedVersion: "1.0.0",
101
+ };
102
+
103
+ vi.mocked(existsSync).mockReturnValue(true);
104
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockCache));
105
+
106
+ const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
107
+
108
+ await checkForUpdates("1.0.0", "@zjex/git-workflow", false);
109
+
110
+ expect(consoleSpy).toHaveBeenCalled();
111
+
112
+ consoleSpy.mockRestore();
113
+ });
114
+
115
+ it("24小时内关闭过提示应该跳过", async () => {
116
+ const mockCache = {
117
+ lastCheck: Date.now(),
118
+ lastDismiss: Date.now(),
119
+ latestVersion: "1.0.1",
120
+ checkedVersion: "1.0.0",
121
+ };
122
+
123
+ vi.mocked(existsSync).mockReturnValue(true);
124
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockCache));
125
+
126
+ const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
127
+
128
+ await checkForUpdates("1.0.0");
129
+
130
+ expect(consoleSpy).not.toHaveBeenCalled();
131
+
132
+ consoleSpy.mockRestore();
133
+ });
134
+
135
+ it("超过24小时后应该再次提示", async () => {
136
+ const oneDayAgo = Date.now() - 25 * 60 * 60 * 1000; // 25小时前
137
+ const mockCache = {
138
+ lastCheck: Date.now(),
139
+ lastDismiss: oneDayAgo,
140
+ latestVersion: "1.0.1",
141
+ checkedVersion: "1.0.0",
142
+ };
143
+
144
+ vi.mocked(existsSync).mockReturnValue(true);
145
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockCache));
146
+
147
+ const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
148
+
149
+ await checkForUpdates("1.0.0", "@zjex/git-workflow", false);
150
+
151
+ expect(consoleSpy).toHaveBeenCalled();
152
+
153
+ consoleSpy.mockRestore();
154
+ });
155
+
156
+ it("4小时内不应该重复检查", 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
+ // writeFileSync 不应该被调用(因为在4小时内)
171
+ expect(writeFileSync).not.toHaveBeenCalled();
172
+ });
173
+
174
+ it("超过4小时应该重新检查", async () => {
175
+ const fourHoursAgo = Date.now() - 5 * 60 * 60 * 1000; // 5小时前
176
+ const mockCache = {
177
+ lastCheck: fourHoursAgo,
178
+ latestVersion: "1.0.0",
179
+ checkedVersion: "1.0.0",
180
+ };
181
+
182
+ vi.mocked(existsSync).mockReturnValue(true);
183
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockCache));
184
+ vi.mocked(execSync).mockReturnValue("1.0.1" as any);
185
+
186
+ await checkForUpdates("1.0.0");
187
+ await vi.runAllTimersAsync();
188
+
189
+ expect(writeFileSync).toHaveBeenCalled();
190
+ });
191
+
192
+ it("缓存文件损坏时应该静默处理", async () => {
193
+ vi.mocked(existsSync).mockReturnValue(true);
194
+ vi.mocked(readFileSync).mockReturnValue("invalid json");
195
+
196
+ await expect(checkForUpdates("1.0.0")).resolves.not.toThrow();
197
+ });
198
+
199
+ it("ExitPromptError 应该重新抛出", async () => {
200
+ const mockCache = {
201
+ lastCheck: Date.now(),
202
+ latestVersion: "1.0.1",
203
+ checkedVersion: "1.0.0",
204
+ };
205
+
206
+ vi.mocked(existsSync).mockReturnValue(true);
207
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockCache));
208
+
209
+ const { select } = await import("@inquirer/prompts");
210
+
211
+ // 创建一个自定义错误类
212
+ class ExitPromptError extends Error {
213
+ constructor(message: string) {
214
+ super(message);
215
+ this.name = "ExitPromptError";
216
+ }
217
+ }
218
+
219
+ const exitError = new ExitPromptError("User cancelled");
220
+ vi.mocked(select).mockRejectedValue(exitError);
221
+
222
+ await expect(
223
+ checkForUpdates("1.0.0", "@zjex/git-workflow", true)
224
+ ).rejects.toThrow();
225
+ });
226
+ });
227
+
228
+ describe("Volta 检测", () => {
229
+ it("应该检测 Volta 环境", async () => {
230
+ vi.mocked(execSync).mockReturnValue("/home/user/.volta/bin/gw" as any);
231
+
232
+ const mockCache = {
233
+ lastCheck: Date.now(),
234
+ latestVersion: "1.0.1",
235
+ checkedVersion: "1.0.0",
236
+ };
237
+
238
+ vi.mocked(existsSync).mockReturnValue(true);
239
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockCache));
240
+
241
+ const { select } = await import("@inquirer/prompts");
242
+ vi.mocked(select).mockResolvedValue("continue");
243
+
244
+ await checkForUpdates("1.0.0", "@zjex/git-workflow", true);
245
+
246
+ expect(select).toHaveBeenCalledWith(
247
+ expect.objectContaining({
248
+ choices: expect.arrayContaining([
249
+ expect.objectContaining({
250
+ description: expect.stringContaining("volta install"),
251
+ }),
252
+ ]),
253
+ })
254
+ );
255
+ });
256
+
257
+ it("应该检测非 Volta 环境", async () => {
258
+ vi.mocked(execSync).mockReturnValue("/usr/local/bin/gw" as any);
259
+
260
+ const mockCache = {
261
+ lastCheck: Date.now(),
262
+ latestVersion: "1.0.1",
263
+ checkedVersion: "1.0.0",
264
+ };
265
+
266
+ vi.mocked(existsSync).mockReturnValue(true);
267
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockCache));
268
+
269
+ const { select } = await import("@inquirer/prompts");
270
+ vi.mocked(select).mockResolvedValue("continue");
271
+
272
+ await checkForUpdates("1.0.0", "@zjex/git-workflow", true);
273
+
274
+ expect(select).toHaveBeenCalledWith(
275
+ expect.objectContaining({
276
+ choices: expect.arrayContaining([
277
+ expect.objectContaining({
278
+ description: expect.stringContaining("npm install -g"),
279
+ }),
280
+ ]),
281
+ })
282
+ );
283
+ });
284
+ });
285
+
286
+ describe("版本比较", () => {
287
+ it("应该正确比较 semver 版本", async () => {
288
+ const testCases = [
289
+ { current: "1.0.0", latest: "1.0.1", shouldShow: true },
290
+ { current: "1.0.0", latest: "1.1.0", shouldShow: true },
291
+ { current: "1.0.0", latest: "2.0.0", shouldShow: true },
292
+ { current: "1.0.1", latest: "1.0.0", shouldShow: false },
293
+ { current: "1.0.0", latest: "1.0.0", shouldShow: false },
294
+ ];
295
+
296
+ for (const { current, latest, shouldShow } of testCases) {
297
+ const mockCache = {
298
+ lastCheck: Date.now(),
299
+ latestVersion: latest,
300
+ checkedVersion: current,
301
+ };
302
+
303
+ vi.mocked(existsSync).mockReturnValue(true);
304
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockCache));
305
+
306
+ const consoleSpy = vi
307
+ .spyOn(console, "log")
308
+ .mockImplementation(() => {});
309
+
310
+ await checkForUpdates(current, "@zjex/git-workflow", false);
311
+
312
+ if (shouldShow) {
313
+ expect(consoleSpy).toHaveBeenCalled();
314
+ } else {
315
+ expect(consoleSpy).not.toHaveBeenCalled();
316
+ }
317
+
318
+ consoleSpy.mockRestore();
319
+ vi.clearAllMocks();
320
+ }
321
+ });
322
+ });
323
+
324
+ describe("缓存读写", () => {
325
+ it("应该正确写入缓存", async () => {
326
+ vi.mocked(existsSync).mockReturnValue(false);
327
+ vi.mocked(execSync).mockReturnValue("1.0.1" as any);
328
+
329
+ await checkForUpdates("1.0.0");
330
+ await vi.runAllTimersAsync();
331
+
332
+ expect(writeFileSync).toHaveBeenCalledWith(
333
+ "/home/user/.gw-update-check",
334
+ expect.stringContaining("1.0.1"),
335
+ "utf-8"
336
+ );
337
+ });
338
+
339
+ it("写入缓存失败时应该静默处理", async () => {
340
+ vi.mocked(existsSync).mockReturnValue(false);
341
+ vi.mocked(execSync).mockReturnValue("1.0.1" as any);
342
+ vi.mocked(writeFileSync).mockImplementation(() => {
343
+ throw new Error("Write failed");
344
+ });
345
+
346
+ await expect(checkForUpdates("1.0.0")).resolves.not.toThrow();
347
+ });
348
+
349
+ it("读取缓存失败时应该返回 null", async () => {
350
+ vi.mocked(existsSync).mockReturnValue(true);
351
+ vi.mocked(readFileSync).mockImplementation(() => {
352
+ throw new Error("Read failed");
353
+ });
354
+
355
+ await expect(checkForUpdates("1.0.0")).resolves.not.toThrow();
356
+ });
357
+ });
358
+
359
+ describe("网络请求", () => {
360
+ it("获取最新版本失败时应该静默处理", async () => {
361
+ vi.mocked(existsSync).mockReturnValue(false);
362
+ vi.mocked(execSync).mockImplementation(() => {
363
+ throw new Error("Network error");
364
+ });
365
+
366
+ await expect(checkForUpdates("1.0.0")).resolves.not.toThrow();
367
+ });
368
+
369
+ it("应该使用正确的 npm 命令", async () => {
370
+ vi.mocked(existsSync).mockReturnValue(false);
371
+ vi.mocked(execSync).mockReturnValue("1.0.1" as any);
372
+
373
+ await checkForUpdates("1.0.0", "@zjex/git-workflow");
374
+ await vi.runAllTimersAsync();
375
+
376
+ expect(execSync).toHaveBeenCalledWith(
377
+ "npm view @zjex/git-workflow version",
378
+ expect.objectContaining({
379
+ timeout: 3000,
380
+ })
381
+ );
382
+ });
383
+ });
384
+ });