@zjex/git-workflow 0.2.19 → 0.2.21
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/CODE_DOCUMENTATION.md +169 -0
- package/dist/index.js +396 -214
- package/package.json +4 -2
- package/src/commands/commit.ts +93 -32
- package/src/commands/help.ts +4 -1
- package/src/commands/tag.ts +49 -22
- package/src/commands/update.ts +43 -8
- package/src/index.ts +68 -11
- package/src/update-notifier.ts +122 -34
package/src/index.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zjex/git-workflow - Git 工作流 CLI 工具
|
|
3
|
+
*
|
|
4
|
+
* 主入口文件,负责:
|
|
5
|
+
* 1. 初始化 CLI 应用
|
|
6
|
+
* 2. 注册所有命令
|
|
7
|
+
* 3. 处理全局错误和信号
|
|
8
|
+
* 4. 显示交互式主菜单
|
|
9
|
+
*/
|
|
10
|
+
|
|
1
11
|
// @ts-nocheck shebang handled by tsup banner
|
|
2
12
|
|
|
3
13
|
import { cac } from "cac";
|
|
@@ -14,7 +24,12 @@ import { showHelp } from "./commands/help.js";
|
|
|
14
24
|
import { checkForUpdates } from "./update-notifier.js";
|
|
15
25
|
import { update } from "./commands/update.js";
|
|
16
26
|
|
|
17
|
-
//
|
|
27
|
+
// ========== 全局错误处理 ==========
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 捕获未捕获的异常
|
|
31
|
+
* 主要用于优雅处理用户按 Ctrl+C 退出的情况
|
|
32
|
+
*/
|
|
18
33
|
process.on("uncaughtException", (err) => {
|
|
19
34
|
if (err instanceof ExitPromptError) {
|
|
20
35
|
console.log(""); // 输出空行,让界面更整洁
|
|
@@ -24,7 +39,9 @@ process.on("uncaughtException", (err) => {
|
|
|
24
39
|
process.exit(1);
|
|
25
40
|
});
|
|
26
41
|
|
|
27
|
-
|
|
42
|
+
/**
|
|
43
|
+
* 捕获未处理的 Promise 拒绝
|
|
44
|
+
*/
|
|
28
45
|
process.on("unhandledRejection", (reason) => {
|
|
29
46
|
if (reason instanceof ExitPromptError) {
|
|
30
47
|
console.log("");
|
|
@@ -34,29 +51,50 @@ process.on("unhandledRejection", (reason) => {
|
|
|
34
51
|
process.exit(1);
|
|
35
52
|
});
|
|
36
53
|
|
|
37
|
-
|
|
54
|
+
/**
|
|
55
|
+
* 捕获 SIGINT 信号 (Ctrl+C)
|
|
56
|
+
* 确保用户按 Ctrl+C 时能优雅退出
|
|
57
|
+
*/
|
|
38
58
|
process.on("SIGINT", () => {
|
|
39
59
|
console.log("");
|
|
40
60
|
process.exit(0);
|
|
41
61
|
});
|
|
42
62
|
|
|
43
|
-
|
|
63
|
+
/**
|
|
64
|
+
* 捕获 SIGTERM 信号
|
|
65
|
+
* 处理进程终止信号
|
|
66
|
+
*/
|
|
44
67
|
process.on("SIGTERM", () => {
|
|
45
68
|
console.log("");
|
|
46
69
|
process.exit(0);
|
|
47
70
|
});
|
|
48
71
|
|
|
72
|
+
// ========== 版本信息 ==========
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 版本号由构建工具注入
|
|
76
|
+
* 开发环境下使用 0.0.0-dev
|
|
77
|
+
*/
|
|
49
78
|
declare const __VERSION__: string | undefined;
|
|
50
79
|
|
|
51
|
-
|
|
80
|
+
/**
|
|
81
|
+
* 当前版本号
|
|
82
|
+
* 生产环境:从构建时注入的 __VERSION__ 获取
|
|
83
|
+
* 开发环境:使用 0.0.0-dev
|
|
84
|
+
*/
|
|
52
85
|
const version: string =
|
|
53
86
|
typeof __VERSION__ !== "undefined" && __VERSION__ !== ""
|
|
54
87
|
? __VERSION__
|
|
55
88
|
: "0.0.0-dev";
|
|
56
89
|
|
|
57
|
-
// 交互式主菜单
|
|
90
|
+
// ========== 交互式主菜单 ==========
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 显示交互式主菜单
|
|
94
|
+
* 提供所有可用命令的可视化选择界面
|
|
95
|
+
*/
|
|
58
96
|
async function mainMenu(): Promise<void> {
|
|
59
|
-
// ASCII Art Logo
|
|
97
|
+
// 显示 ASCII Art Logo
|
|
60
98
|
console.log(
|
|
61
99
|
colors.green(`
|
|
62
100
|
███████╗ ██╗███████╗██╗ ██╗
|
|
@@ -97,7 +135,7 @@ async function mainMenu(): Promise<void> {
|
|
|
97
135
|
value: "tag-delete",
|
|
98
136
|
},
|
|
99
137
|
{
|
|
100
|
-
name: `[7] ✏️
|
|
138
|
+
name: `[7] ✏️ 重命名 tag ${colors.dim("gw tu")}`,
|
|
101
139
|
value: "tag-update",
|
|
102
140
|
},
|
|
103
141
|
{
|
|
@@ -174,11 +212,22 @@ async function mainMenu(): Promise<void> {
|
|
|
174
212
|
}
|
|
175
213
|
}
|
|
176
214
|
|
|
215
|
+
// ========== CLI 应用初始化 ==========
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 创建 CLI 应用实例
|
|
219
|
+
* 使用 cac (Command And Conquer) 库
|
|
220
|
+
*/
|
|
177
221
|
const cli = cac("gw");
|
|
178
222
|
|
|
179
|
-
//
|
|
223
|
+
// ========== 命令注册 ==========
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 默认命令 - 显示交互式菜单
|
|
227
|
+
* 运行 `gw` 时触发,会检查更新(交互式模式)
|
|
228
|
+
*/
|
|
180
229
|
cli.command("", "显示交互式菜单").action(async () => {
|
|
181
|
-
await checkForUpdates(version, "@zjex/git-workflow");
|
|
230
|
+
await checkForUpdates(version, "@zjex/git-workflow", true);
|
|
182
231
|
return mainMenu();
|
|
183
232
|
});
|
|
184
233
|
|
|
@@ -242,7 +291,7 @@ cli
|
|
|
242
291
|
});
|
|
243
292
|
|
|
244
293
|
cli
|
|
245
|
-
.command("tag:update", "
|
|
294
|
+
.command("tag:update", "重命名 tag")
|
|
246
295
|
.alias("tu")
|
|
247
296
|
.action(async () => {
|
|
248
297
|
await checkForUpdates(version, "@zjex/git-workflow");
|
|
@@ -290,6 +339,14 @@ cli
|
|
|
290
339
|
return update(version);
|
|
291
340
|
});
|
|
292
341
|
|
|
342
|
+
cli.command("clean", "清理缓存文件").action(async () => {
|
|
343
|
+
const { clearUpdateCache } = await import("./update-notifier.js");
|
|
344
|
+
clearUpdateCache();
|
|
345
|
+
console.log("");
|
|
346
|
+
console.log(colors.green("✔ 缓存已清理"));
|
|
347
|
+
console.log("");
|
|
348
|
+
});
|
|
349
|
+
|
|
293
350
|
cli.help((sections) => {
|
|
294
351
|
sections.push({
|
|
295
352
|
body: showHelp(),
|
package/src/update-notifier.ts
CHANGED
|
@@ -1,57 +1,72 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, unlinkSync } from "fs";
|
|
3
3
|
import { homedir } from "os";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import boxen from "boxen";
|
|
6
6
|
import { select } from "@inquirer/prompts";
|
|
7
7
|
import ora from "ora";
|
|
8
|
+
import semver from "semver";
|
|
8
9
|
import { colors } from "./utils.js";
|
|
9
10
|
|
|
11
|
+
const CHECK_INTERVAL = 1000 * 60 * 60 * 4; // 4 小时检查一次
|
|
10
12
|
const DISMISS_INTERVAL = 1000 * 60 * 60 * 24; // 24 小时后再次提示
|
|
11
13
|
const CACHE_FILE = ".gw-update-check";
|
|
12
14
|
|
|
13
15
|
interface UpdateCache {
|
|
16
|
+
lastCheck?: number; // 上次检查更新的时间
|
|
14
17
|
lastDismiss?: number; // 用户上次关闭提示的时间
|
|
15
|
-
latestVersion?: string;
|
|
18
|
+
latestVersion?: string; // 最新版本号
|
|
19
|
+
checkedVersion?: string; // 检查时的当前版本
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
|
-
*
|
|
23
|
+
* 检查是否有新版本(异步静默检查)
|
|
24
|
+
* @param currentVersion 当前版本
|
|
25
|
+
* @param packageName 包名
|
|
26
|
+
* @param interactive 是否交互式(true: 显示完整提示并可选择更新,false: 只显示简单提示)
|
|
20
27
|
*/
|
|
21
28
|
export async function checkForUpdates(
|
|
22
29
|
currentVersion: string,
|
|
23
|
-
packageName: string = "@zjex/git-workflow"
|
|
30
|
+
packageName: string = "@zjex/git-workflow",
|
|
31
|
+
interactive: boolean = false
|
|
24
32
|
): Promise<void> {
|
|
25
33
|
try {
|
|
26
|
-
// 读取缓存
|
|
27
34
|
const cache = readCache();
|
|
28
35
|
const now = Date.now();
|
|
29
36
|
|
|
30
|
-
//
|
|
31
|
-
if (cache?.
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
// 1. 先检查缓存中是否有新版本需要提示
|
|
38
|
+
if (cache?.latestVersion && cache.checkedVersion === currentVersion) {
|
|
39
|
+
// 如果用户在 24 小时内关闭过提示,跳过
|
|
40
|
+
if (cache.lastDismiss && now - cache.lastDismiss < DISMISS_INTERVAL) {
|
|
41
|
+
// 继续后台检查(不阻塞)
|
|
42
|
+
backgroundCheck(currentVersion, packageName);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 使用 semver 比较版本
|
|
47
|
+
if (semver.gt(cache.latestVersion, currentVersion)) {
|
|
48
|
+
if (interactive) {
|
|
49
|
+
// 交互式模式:显示完整提示,可选择更新
|
|
50
|
+
const action = await showUpdateMessage(
|
|
51
|
+
currentVersion,
|
|
52
|
+
cache.latestVersion,
|
|
53
|
+
packageName
|
|
54
|
+
);
|
|
34
55
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
if (action === "update") {
|
|
47
|
-
// 用户选择立即更新
|
|
48
|
-
await performUpdate(packageName);
|
|
49
|
-
} else if (action === "dismiss") {
|
|
50
|
-
// 用户选择跳过,记录时间
|
|
51
|
-
writeCache({ lastDismiss: now, latestVersion });
|
|
56
|
+
if (action === "update") {
|
|
57
|
+
await performUpdate(packageName);
|
|
58
|
+
} else if (action === "dismiss") {
|
|
59
|
+
writeCache({ ...cache, lastDismiss: now });
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
// 非交互式模式:只显示简单提示
|
|
63
|
+
showSimpleNotification(currentVersion, cache.latestVersion);
|
|
64
|
+
}
|
|
52
65
|
}
|
|
53
|
-
// action === "continue" 时直接继续,不记录
|
|
54
66
|
}
|
|
67
|
+
|
|
68
|
+
// 2. 后台异步检查更新(不阻塞当前命令)
|
|
69
|
+
backgroundCheck(currentVersion, packageName);
|
|
55
70
|
} catch (error) {
|
|
56
71
|
// 如果是用户按 Ctrl+C,重新抛出让全局处理
|
|
57
72
|
if (error?.constructor?.name === "ExitPromptError") {
|
|
@@ -61,6 +76,37 @@ export async function checkForUpdates(
|
|
|
61
76
|
}
|
|
62
77
|
}
|
|
63
78
|
|
|
79
|
+
/**
|
|
80
|
+
* 后台异步检查更新(不阻塞)
|
|
81
|
+
*/
|
|
82
|
+
function backgroundCheck(currentVersion: string, packageName: string): void {
|
|
83
|
+
const cache = readCache();
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
|
|
86
|
+
// 如果距离上次检查不到 4 小时,跳过
|
|
87
|
+
if (cache?.lastCheck && now - cache.lastCheck < CHECK_INTERVAL) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 使用 Promise 异步执行,不阻塞当前命令
|
|
92
|
+
Promise.resolve().then(async () => {
|
|
93
|
+
try {
|
|
94
|
+
const latestVersion = await getLatestVersion(packageName);
|
|
95
|
+
|
|
96
|
+
if (latestVersion) {
|
|
97
|
+
writeCache({
|
|
98
|
+
...cache,
|
|
99
|
+
lastCheck: now,
|
|
100
|
+
latestVersion,
|
|
101
|
+
checkedVersion: currentVersion,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// 静默失败
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
64
110
|
/**
|
|
65
111
|
* 获取 npm 上的最新版本
|
|
66
112
|
*/
|
|
@@ -78,7 +124,29 @@ async function getLatestVersion(packageName: string): Promise<string | null> {
|
|
|
78
124
|
}
|
|
79
125
|
|
|
80
126
|
/**
|
|
81
|
-
*
|
|
127
|
+
* 显示简单的更新通知(非交互式,不阻塞)
|
|
128
|
+
*/
|
|
129
|
+
function showSimpleNotification(current: string, latest: string): void {
|
|
130
|
+
const message = `${colors.yellow("🎉 发现新版本")} ${colors.dim(
|
|
131
|
+
current
|
|
132
|
+
)} → ${colors.green(latest)} ${colors.dim("运行")} ${colors.cyan(
|
|
133
|
+
"gw update"
|
|
134
|
+
)} ${colors.dim("更新")}`;
|
|
135
|
+
|
|
136
|
+
console.log("");
|
|
137
|
+
console.log(
|
|
138
|
+
boxen(message, {
|
|
139
|
+
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
140
|
+
margin: { top: 0, bottom: 1, left: 0, right: 0 },
|
|
141
|
+
borderStyle: "round",
|
|
142
|
+
borderColor: "yellow",
|
|
143
|
+
align: "center",
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 显示更新提示消息并让用户选择(交互式)
|
|
82
150
|
* @returns "update" | "continue" | "dismiss"
|
|
83
151
|
*/
|
|
84
152
|
async function showUpdateMessage(
|
|
@@ -87,7 +155,7 @@ async function showUpdateMessage(
|
|
|
87
155
|
packageName: string
|
|
88
156
|
): Promise<"update" | "continue" | "dismiss"> {
|
|
89
157
|
const message = [
|
|
90
|
-
colors.bold("
|
|
158
|
+
colors.yellow(colors.bold("🎉 发现新版本!")),
|
|
91
159
|
"",
|
|
92
160
|
`${colors.dim(current)} → ${colors.green(colors.bold(latest))}`,
|
|
93
161
|
].join("\n");
|
|
@@ -95,11 +163,12 @@ async function showUpdateMessage(
|
|
|
95
163
|
console.log("");
|
|
96
164
|
console.log(
|
|
97
165
|
boxen(message, {
|
|
98
|
-
padding: 1,
|
|
166
|
+
padding: { top: 1, bottom: 1, left: 3, right: 3 },
|
|
99
167
|
margin: 1,
|
|
100
168
|
borderStyle: "round",
|
|
101
169
|
borderColor: "yellow",
|
|
102
|
-
align: "
|
|
170
|
+
align: "center",
|
|
171
|
+
width: 40,
|
|
103
172
|
})
|
|
104
173
|
);
|
|
105
174
|
|
|
@@ -152,20 +221,25 @@ async function performUpdate(packageName: string): Promise<void> {
|
|
|
152
221
|
});
|
|
153
222
|
|
|
154
223
|
spinner.succeed(colors.green("更新成功!"));
|
|
224
|
+
|
|
225
|
+
// 清理缓存文件
|
|
226
|
+
clearUpdateCache();
|
|
227
|
+
|
|
155
228
|
console.log("");
|
|
156
229
|
console.log(
|
|
157
230
|
boxen(
|
|
158
231
|
[
|
|
159
|
-
colors.bold("✨ 更新完成!"),
|
|
232
|
+
colors.green(colors.bold("✨ 更新完成!")),
|
|
160
233
|
"",
|
|
161
234
|
colors.dim("请重新打开终端使用新版本"),
|
|
162
235
|
].join("\n"),
|
|
163
236
|
{
|
|
164
|
-
padding: 1,
|
|
237
|
+
padding: { top: 1, bottom: 1, left: 3, right: 3 },
|
|
165
238
|
margin: { top: 0, bottom: 1, left: 2, right: 2 },
|
|
166
239
|
borderStyle: "round",
|
|
167
240
|
borderColor: "green",
|
|
168
|
-
align: "
|
|
241
|
+
align: "center",
|
|
242
|
+
width: 40,
|
|
169
243
|
}
|
|
170
244
|
)
|
|
171
245
|
);
|
|
@@ -181,6 +255,20 @@ async function performUpdate(packageName: string): Promise<void> {
|
|
|
181
255
|
}
|
|
182
256
|
}
|
|
183
257
|
|
|
258
|
+
/**
|
|
259
|
+
* 清理更新缓存文件
|
|
260
|
+
*/
|
|
261
|
+
export function clearUpdateCache(): void {
|
|
262
|
+
try {
|
|
263
|
+
const cacheFile = join(homedir(), CACHE_FILE);
|
|
264
|
+
if (existsSync(cacheFile)) {
|
|
265
|
+
unlinkSync(cacheFile);
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
// 静默失败
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
184
272
|
/**
|
|
185
273
|
* 读取缓存
|
|
186
274
|
*/
|