@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zjex/git-workflow",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.21",
|
|
4
4
|
"description": "🚀 极简的 Git 工作流 CLI 工具,让分支管理和版本发布变得轻松愉快",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -42,10 +42,12 @@
|
|
|
42
42
|
"@inquirer/prompts": "^7.0.0",
|
|
43
43
|
"boxen": "^8.0.1",
|
|
44
44
|
"cac": "^6.7.14",
|
|
45
|
-
"ora": "^9.0.0"
|
|
45
|
+
"ora": "^9.0.0",
|
|
46
|
+
"semver": "^7.7.3"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@types/node": "^25.0.3",
|
|
50
|
+
"@types/semver": "^7.7.1",
|
|
49
51
|
"changelogen": "^0.6.2",
|
|
50
52
|
"husky": "^9.1.7",
|
|
51
53
|
"tsup": "^8.5.1",
|
package/src/commands/commit.ts
CHANGED
|
@@ -5,7 +5,11 @@ import { colors, theme, execOutput, divider } from "../utils.js";
|
|
|
5
5
|
import { getConfig } from "../config.js";
|
|
6
6
|
import { generateAICommitMessage, isAICommitAvailable } from "../ai-service.js";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Conventional Commits 类型定义 + Gitmoji
|
|
10
|
+
* 遵循 https://www.conventionalcommits.org/ 规范
|
|
11
|
+
* 使用 https://gitmoji.dev/ emoji
|
|
12
|
+
*/
|
|
9
13
|
const DEFAULT_COMMIT_TYPES = [
|
|
10
14
|
{ type: "feat", emoji: "✨", description: "新功能" },
|
|
11
15
|
{ type: "fix", emoji: "🐛", description: "修复 Bug" },
|
|
@@ -17,11 +21,15 @@ const DEFAULT_COMMIT_TYPES = [
|
|
|
17
21
|
{ type: "build", emoji: "📦", description: "构建/依赖相关" },
|
|
18
22
|
{ type: "ci", emoji: "👷", description: "CI/CD 相关" },
|
|
19
23
|
{ type: "chore", emoji: "🔧", description: "其他杂项" },
|
|
20
|
-
{ type: "revert", emoji: "⏪", description: "回退提交" },
|
|
21
24
|
] as const;
|
|
22
25
|
|
|
23
26
|
type CommitType = (typeof DEFAULT_COMMIT_TYPES)[number]["type"];
|
|
24
27
|
|
|
28
|
+
/**
|
|
29
|
+
* 获取提交类型列表(支持自定义 emoji)
|
|
30
|
+
* @param config 用户配置
|
|
31
|
+
* @returns 提交类型列表
|
|
32
|
+
*/
|
|
25
33
|
function getCommitTypes(config: ReturnType<typeof getConfig>) {
|
|
26
34
|
const customEmojis = config.commitEmojis || {};
|
|
27
35
|
return DEFAULT_COMMIT_TYPES.map((item) => ({
|
|
@@ -30,11 +38,18 @@ function getCommitTypes(config: ReturnType<typeof getConfig>) {
|
|
|
30
38
|
}));
|
|
31
39
|
}
|
|
32
40
|
|
|
41
|
+
/**
|
|
42
|
+
* 文件状态接口
|
|
43
|
+
*/
|
|
33
44
|
interface FileStatus {
|
|
34
|
-
status: string;
|
|
35
|
-
file: string;
|
|
45
|
+
status: string; // M=修改, A=新增, D=删除, ?=未跟踪
|
|
46
|
+
file: string; // 文件路径
|
|
36
47
|
}
|
|
37
48
|
|
|
49
|
+
/**
|
|
50
|
+
* 解析 git status 输出
|
|
51
|
+
* @returns 已暂存和未暂存的文件列表
|
|
52
|
+
*/
|
|
38
53
|
function parseGitStatus(): { staged: FileStatus[]; unstaged: FileStatus[] } {
|
|
39
54
|
const output = execOutput("git status --porcelain");
|
|
40
55
|
if (!output) return { staged: [], unstaged: [] };
|
|
@@ -44,9 +59,9 @@ function parseGitStatus(): { staged: FileStatus[]; unstaged: FileStatus[] } {
|
|
|
44
59
|
|
|
45
60
|
for (const line of output.split("\n")) {
|
|
46
61
|
if (!line) continue;
|
|
47
|
-
const indexStatus = line[0];
|
|
48
|
-
const workTreeStatus = line[1];
|
|
49
|
-
const file = line.slice(3);
|
|
62
|
+
const indexStatus = line[0]; // 暂存区状态
|
|
63
|
+
const workTreeStatus = line[1]; // 工作区状态
|
|
64
|
+
const file = line.slice(3); // 文件路径
|
|
50
65
|
|
|
51
66
|
// 已暂存的更改 (index 有状态)
|
|
52
67
|
if (indexStatus !== " " && indexStatus !== "?") {
|
|
@@ -63,23 +78,33 @@ function parseGitStatus(): { staged: FileStatus[]; unstaged: FileStatus[] } {
|
|
|
63
78
|
return { staged, unstaged };
|
|
64
79
|
}
|
|
65
80
|
|
|
81
|
+
/**
|
|
82
|
+
* 格式化文件状态显示(带颜色)
|
|
83
|
+
* @param status 文件状态
|
|
84
|
+
* @returns 带颜色的状态字符串
|
|
85
|
+
*/
|
|
66
86
|
function formatFileStatus(status: string): string {
|
|
67
87
|
const statusMap: Record<string, string> = {
|
|
68
|
-
M: colors.yellow("M"),
|
|
69
|
-
A: colors.green("A"),
|
|
70
|
-
D: colors.red("D"),
|
|
71
|
-
R: colors.yellow("R"),
|
|
72
|
-
C: colors.yellow("C"),
|
|
73
|
-
"?": colors.green("?"),
|
|
88
|
+
M: colors.yellow("M"), // 修改
|
|
89
|
+
A: colors.green("A"), // 新增
|
|
90
|
+
D: colors.red("D"), // 删除
|
|
91
|
+
R: colors.yellow("R"), // 重命名
|
|
92
|
+
C: colors.yellow("C"), // 复制
|
|
93
|
+
"?": colors.green("?"), // 未跟踪
|
|
74
94
|
};
|
|
75
95
|
return statusMap[status] || status;
|
|
76
96
|
}
|
|
77
97
|
|
|
98
|
+
/**
|
|
99
|
+
* 交互式提交命令
|
|
100
|
+
* 支持 AI 自动生成和手动编写两种模式
|
|
101
|
+
* 遵循 Conventional Commits 规范
|
|
102
|
+
*/
|
|
78
103
|
export async function commit(): Promise<void> {
|
|
79
104
|
const config = getConfig();
|
|
80
105
|
let { staged, unstaged } = parseGitStatus();
|
|
81
106
|
|
|
82
|
-
//
|
|
107
|
+
// ========== 步骤 1: 处理未暂存的文件 ==========
|
|
83
108
|
if (unstaged.length > 0) {
|
|
84
109
|
const autoStage = config.autoStage ?? true;
|
|
85
110
|
|
|
@@ -131,7 +156,7 @@ export async function commit(): Promise<void> {
|
|
|
131
156
|
}
|
|
132
157
|
}
|
|
133
158
|
|
|
134
|
-
//
|
|
159
|
+
// ========== 步骤 2: 检查是否有文件可提交 ==========
|
|
135
160
|
if (staged.length === 0) {
|
|
136
161
|
console.log(colors.yellow("工作区干净,没有需要提交的更改"));
|
|
137
162
|
return;
|
|
@@ -144,7 +169,7 @@ export async function commit(): Promise<void> {
|
|
|
144
169
|
}
|
|
145
170
|
divider();
|
|
146
171
|
|
|
147
|
-
//
|
|
172
|
+
// ========== 步骤 3: 选择提交方式(AI 或手动)==========
|
|
148
173
|
const aiAvailable = isAICommitAvailable(config);
|
|
149
174
|
let commitMode: "ai" | "manual" = "manual";
|
|
150
175
|
|
|
@@ -167,10 +192,12 @@ export async function commit(): Promise<void> {
|
|
|
167
192
|
});
|
|
168
193
|
}
|
|
169
194
|
|
|
170
|
-
|
|
195
|
+
// 初始化 commit message 变量
|
|
196
|
+
let message: string = "";
|
|
171
197
|
|
|
198
|
+
// ========== 步骤 4: 生成 commit message ==========
|
|
199
|
+
// AI 生成模式
|
|
172
200
|
if (commitMode === "ai") {
|
|
173
|
-
// AI 生成模式
|
|
174
201
|
const spinner = ora("AI 正在分析代码变更...").start();
|
|
175
202
|
|
|
176
203
|
try {
|
|
@@ -208,11 +235,12 @@ export async function commit(): Promise<void> {
|
|
|
208
235
|
}
|
|
209
236
|
}
|
|
210
237
|
|
|
238
|
+
// 手动输入模式
|
|
211
239
|
if (commitMode === "manual") {
|
|
212
|
-
// 手动输入模式(原有逻辑)
|
|
213
240
|
message = await buildManualCommitMessage(config);
|
|
214
241
|
}
|
|
215
242
|
|
|
243
|
+
// ========== 步骤 5: 预览并确认提交 ==========
|
|
216
244
|
divider();
|
|
217
245
|
console.log("提交信息预览:");
|
|
218
246
|
console.log(colors.green(message));
|
|
@@ -232,9 +260,23 @@ export async function commit(): Promise<void> {
|
|
|
232
260
|
return;
|
|
233
261
|
}
|
|
234
262
|
|
|
263
|
+
// ========== 步骤 6: 执行提交 ==========
|
|
235
264
|
const spinner = ora("正在提交...").start();
|
|
236
265
|
|
|
237
266
|
try {
|
|
267
|
+
// 提交前再次检查是否有暂存的文件
|
|
268
|
+
const finalStatus = parseGitStatus();
|
|
269
|
+
if (finalStatus.staged.length === 0) {
|
|
270
|
+
spinner.fail("没有暂存的文件可以提交");
|
|
271
|
+
console.log("");
|
|
272
|
+
console.log(colors.yellow("请先暂存文件:"));
|
|
273
|
+
console.log(colors.cyan(" git add <file>"));
|
|
274
|
+
console.log(colors.dim(" 或"));
|
|
275
|
+
console.log(colors.cyan(" git add -A"));
|
|
276
|
+
console.log("");
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
238
280
|
// 使用 -m 参数,需要转义引号
|
|
239
281
|
const escapedMessage = message.replace(/"/g, '\\"');
|
|
240
282
|
execSync(`git commit -m "${escapedMessage}"`, { stdio: "pipe" });
|
|
@@ -245,14 +287,26 @@ export async function commit(): Promise<void> {
|
|
|
245
287
|
console.log(colors.dim(`commit: ${commitHash}`));
|
|
246
288
|
} catch (error) {
|
|
247
289
|
spinner.fail("提交失败");
|
|
290
|
+
console.log("");
|
|
291
|
+
|
|
292
|
+
// 显示详细错误信息
|
|
248
293
|
if (error instanceof Error) {
|
|
249
|
-
console.log(colors.red(
|
|
294
|
+
console.log(colors.red("错误信息:"));
|
|
295
|
+
console.log(colors.dim(` ${error.message}`));
|
|
250
296
|
}
|
|
297
|
+
|
|
298
|
+
console.log("");
|
|
299
|
+
console.log(colors.yellow("你可以手动执行以下命令:"));
|
|
300
|
+
console.log(colors.cyan(` git commit -m "${message}"`));
|
|
301
|
+
console.log("");
|
|
251
302
|
}
|
|
252
303
|
}
|
|
253
304
|
|
|
254
305
|
/**
|
|
255
306
|
* 手动构建 commit message
|
|
307
|
+
* 通过交互式问答收集信息,构建符合 Conventional Commits 规范的提交信息
|
|
308
|
+
* @param config 用户配置
|
|
309
|
+
* @returns 完整的 commit message
|
|
256
310
|
*/
|
|
257
311
|
async function buildManualCommitMessage(
|
|
258
312
|
config: ReturnType<typeof getConfig>
|
|
@@ -260,23 +314,30 @@ async function buildManualCommitMessage(
|
|
|
260
314
|
// 获取提交类型(支持自定义 emoji)
|
|
261
315
|
const commitTypes = getCommitTypes(config);
|
|
262
316
|
|
|
263
|
-
// 选择提交类型
|
|
317
|
+
// ========== 1. 选择提交类型 ==========
|
|
264
318
|
const typeChoice = await select({
|
|
265
319
|
message: "选择提交类型:",
|
|
266
|
-
choices: commitTypes.map((t) =>
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
320
|
+
choices: commitTypes.map((t) => {
|
|
321
|
+
// 使用固定宽度格式化,不依赖 emoji 宽度
|
|
322
|
+
const typeText = t.type.padEnd(10);
|
|
323
|
+
// 针对 refactor 特殊处理,因为 ♻️ emoji 在不同终端宽度不一致
|
|
324
|
+
const spacing = t.type === "refactor" ? " " : " ";
|
|
325
|
+
return {
|
|
326
|
+
name: `${t.emoji}${spacing}${typeText} ${colors.dim(t.description)}`,
|
|
327
|
+
value: t,
|
|
328
|
+
};
|
|
329
|
+
}),
|
|
330
|
+
pageSize: commitTypes.length, // 显示所有选项,不滚动
|
|
270
331
|
theme,
|
|
271
332
|
});
|
|
272
333
|
|
|
273
|
-
// 输入 scope (可选)
|
|
334
|
+
// ========== 2. 输入 scope (可选) ==========
|
|
274
335
|
const scope = await input({
|
|
275
336
|
message: "输入影响范围 scope (可跳过):",
|
|
276
337
|
theme,
|
|
277
338
|
});
|
|
278
339
|
|
|
279
|
-
// 输入简短描述
|
|
340
|
+
// ========== 3. 输入简短描述 (必填) ==========
|
|
280
341
|
const subject = await input({
|
|
281
342
|
message: "输入简短描述:",
|
|
282
343
|
validate: (value) => {
|
|
@@ -287,13 +348,13 @@ async function buildManualCommitMessage(
|
|
|
287
348
|
theme,
|
|
288
349
|
});
|
|
289
350
|
|
|
290
|
-
// 输入详细描述 (可选)
|
|
351
|
+
// ========== 4. 输入详细描述 (可选) ==========
|
|
291
352
|
const body = await input({
|
|
292
353
|
message: "输入详细描述 (可跳过):",
|
|
293
354
|
theme,
|
|
294
355
|
});
|
|
295
356
|
|
|
296
|
-
// 是否有破坏性变更
|
|
357
|
+
// ========== 5. 是否有破坏性变更 ==========
|
|
297
358
|
const hasBreaking = await select({
|
|
298
359
|
message: "是否包含破坏性变更 (BREAKING CHANGE)?",
|
|
299
360
|
choices: [
|
|
@@ -312,13 +373,13 @@ async function buildManualCommitMessage(
|
|
|
312
373
|
});
|
|
313
374
|
}
|
|
314
375
|
|
|
315
|
-
// 关联 Issue (可选)
|
|
376
|
+
// ========== 6. 关联 Issue (可选) ==========
|
|
316
377
|
const issues = await input({
|
|
317
378
|
message: "关联 Issue (如 #123, 可跳过):",
|
|
318
379
|
theme,
|
|
319
380
|
});
|
|
320
381
|
|
|
321
|
-
// 构建 commit message
|
|
382
|
+
// ========== 7. 构建 commit message ==========
|
|
322
383
|
const { type, emoji } = typeChoice;
|
|
323
384
|
const scopePart = scope ? `(${scope})` : "";
|
|
324
385
|
const breakingMark = hasBreaking ? "!" : "";
|
|
@@ -330,7 +391,7 @@ async function buildManualCommitMessage(
|
|
|
330
391
|
// Header: [emoji] type(scope)!: subject
|
|
331
392
|
let message = `${emojiPrefix}${type}${scopePart}${breakingMark}: ${subject}`;
|
|
332
393
|
|
|
333
|
-
// Body
|
|
394
|
+
// Body (可选)
|
|
334
395
|
if (body || hasBreaking || issues) {
|
|
335
396
|
message += "\n";
|
|
336
397
|
|
package/src/commands/help.ts
CHANGED
|
@@ -25,7 +25,7 @@ Tag 命令:
|
|
|
25
25
|
gw tag:delete 删除 tag
|
|
26
26
|
gw td 同上 (别名)
|
|
27
27
|
|
|
28
|
-
gw tag:update
|
|
28
|
+
gw tag:update 重命名 tag
|
|
29
29
|
gw tu 同上 (别名)
|
|
30
30
|
|
|
31
31
|
发布命令:
|
|
@@ -42,6 +42,9 @@ Tag 命令:
|
|
|
42
42
|
gw update 检查并更新到最新版本
|
|
43
43
|
gw upt 同上 (别名)
|
|
44
44
|
|
|
45
|
+
清理命令:
|
|
46
|
+
gw clean 清理缓存文件
|
|
47
|
+
|
|
45
48
|
Stash 命令:
|
|
46
49
|
gw stash 交互式管理 stash
|
|
47
50
|
gw s 同上 (别名)
|
package/src/commands/tag.ts
CHANGED
|
@@ -417,7 +417,7 @@ export async function deleteTag(): Promise<void> {
|
|
|
417
417
|
}
|
|
418
418
|
|
|
419
419
|
/**
|
|
420
|
-
* 修改 tag
|
|
420
|
+
* 修改 tag 名称(重命名 tag)
|
|
421
421
|
*/
|
|
422
422
|
export async function updateTag(): Promise<void> {
|
|
423
423
|
const fetchSpinner = ora("正在获取 tags...").start();
|
|
@@ -438,47 +438,71 @@ export async function updateTag(): Promise<void> {
|
|
|
438
438
|
const choices = tags.map((tag) => ({ name: tag, value: tag }));
|
|
439
439
|
choices.push({ name: "取消", value: "__cancel__" });
|
|
440
440
|
|
|
441
|
-
const
|
|
442
|
-
message: "
|
|
441
|
+
const oldTag = await select({
|
|
442
|
+
message: "选择要重命名的 tag:",
|
|
443
443
|
choices,
|
|
444
444
|
theme,
|
|
445
445
|
});
|
|
446
446
|
|
|
447
|
-
if (
|
|
447
|
+
if (oldTag === "__cancel__") {
|
|
448
448
|
console.log(colors.yellow("已取消"));
|
|
449
449
|
return;
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
452
|
+
console.log("");
|
|
453
|
+
console.log(colors.dim(`当前 tag: ${oldTag}`));
|
|
454
|
+
console.log("");
|
|
455
|
+
|
|
456
|
+
const newTag = await input({
|
|
457
|
+
message: "输入新的 tag 名称:",
|
|
458
|
+
default: oldTag,
|
|
455
459
|
theme,
|
|
456
460
|
});
|
|
457
461
|
|
|
458
|
-
if (!
|
|
462
|
+
if (!newTag || newTag === oldTag) {
|
|
459
463
|
console.log(colors.yellow("已取消"));
|
|
460
464
|
return;
|
|
461
465
|
}
|
|
462
466
|
|
|
467
|
+
// 检查新 tag 是否已存在
|
|
468
|
+
const existingTags = execOutput("git tag -l").split("\n").filter(Boolean);
|
|
469
|
+
if (existingTags.includes(newTag)) {
|
|
470
|
+
console.log(colors.red(`Tag ${newTag} 已存在,无法重命名`));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
463
474
|
divider();
|
|
464
475
|
|
|
465
|
-
const spinner = ora(
|
|
476
|
+
const spinner = ora(`正在重命名 tag: ${oldTag} → ${newTag}`).start();
|
|
466
477
|
|
|
467
478
|
try {
|
|
479
|
+
// 获取旧 tag 的 commit 和消息
|
|
480
|
+
const commit = execOutput(`git rev-list -n 1 "${oldTag}"`).trim();
|
|
481
|
+
const message = execOutput(
|
|
482
|
+
`git tag -l --format='%(contents)' "${oldTag}"`
|
|
483
|
+
).trim();
|
|
484
|
+
|
|
485
|
+
// 创建新 tag(指向同一个 commit)
|
|
486
|
+
if (message) {
|
|
487
|
+
execSync(`git tag -a "${newTag}" "${commit}" -m "${message}"`, {
|
|
488
|
+
stdio: "pipe",
|
|
489
|
+
});
|
|
490
|
+
} else {
|
|
491
|
+
execSync(`git tag "${newTag}" "${commit}"`, { stdio: "pipe" });
|
|
492
|
+
}
|
|
493
|
+
|
|
468
494
|
// 删除旧 tag
|
|
469
|
-
execSync(`git tag -d "${
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
} catch {
|
|
476
|
-
spinner.fail("tag 更新失败");
|
|
495
|
+
execSync(`git tag -d "${oldTag}"`, { stdio: "pipe" });
|
|
496
|
+
|
|
497
|
+
spinner.succeed(`Tag 已重命名: ${oldTag} → ${newTag}`);
|
|
498
|
+
} catch (error) {
|
|
499
|
+
spinner.fail("tag 重命名失败");
|
|
500
|
+
console.log(colors.red(String(error)));
|
|
477
501
|
return;
|
|
478
502
|
}
|
|
479
503
|
|
|
480
504
|
const pushRemote = await select({
|
|
481
|
-
message: "
|
|
505
|
+
message: "是否同步到远程?",
|
|
482
506
|
choices: [
|
|
483
507
|
{ name: "是", value: true },
|
|
484
508
|
{ name: "否", value: false },
|
|
@@ -487,13 +511,16 @@ export async function updateTag(): Promise<void> {
|
|
|
487
511
|
});
|
|
488
512
|
|
|
489
513
|
if (pushRemote) {
|
|
490
|
-
const pushSpinner = ora("
|
|
514
|
+
const pushSpinner = ora("正在同步到远程...").start();
|
|
491
515
|
try {
|
|
492
|
-
|
|
493
|
-
|
|
516
|
+
// 推送新 tag
|
|
517
|
+
execSync(`git push origin "${newTag}"`, { stdio: "pipe" });
|
|
518
|
+
// 删除远程旧 tag
|
|
519
|
+
execSync(`git push origin --delete "${oldTag}"`, { stdio: "pipe" });
|
|
520
|
+
pushSpinner.succeed(`远程 tag 已同步: ${oldTag} → ${newTag}`);
|
|
494
521
|
} catch {
|
|
495
522
|
pushSpinner.warn(
|
|
496
|
-
|
|
523
|
+
`远程同步失败,可稍后手动执行:\n git push origin ${newTag}\n git push origin --delete ${oldTag}`
|
|
497
524
|
);
|
|
498
525
|
}
|
|
499
526
|
}
|
package/src/commands/update.ts
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
2
|
import ora from "ora";
|
|
3
3
|
import boxen from "boxen";
|
|
4
|
+
import semver from "semver";
|
|
5
|
+
import { existsSync, unlinkSync } from "fs";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { join } from "path";
|
|
4
8
|
import { colors } from "../utils.js";
|
|
5
9
|
|
|
10
|
+
const CACHE_FILE = ".gw-update-check";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 清理更新缓存文件
|
|
14
|
+
*/
|
|
15
|
+
function clearUpdateCache(): void {
|
|
16
|
+
try {
|
|
17
|
+
const cacheFile = join(homedir(), CACHE_FILE);
|
|
18
|
+
if (existsSync(cacheFile)) {
|
|
19
|
+
unlinkSync(cacheFile);
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
// 静默失败
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
6
26
|
/**
|
|
7
27
|
* 获取 npm 上的最新版本
|
|
8
28
|
*/
|
|
@@ -42,11 +62,12 @@ export async function update(currentVersion: string): Promise<void> {
|
|
|
42
62
|
|
|
43
63
|
spinner.stop();
|
|
44
64
|
|
|
45
|
-
|
|
65
|
+
// 使用 semver 比较版本
|
|
66
|
+
if (semver.gte(currentVersion, latestVersion)) {
|
|
46
67
|
console.log(
|
|
47
68
|
boxen(
|
|
48
69
|
[
|
|
49
|
-
colors.bold("✅ 已是最新版本"),
|
|
70
|
+
colors.green(colors.bold("✅ 已是最新版本")),
|
|
50
71
|
"",
|
|
51
72
|
`当前版本: ${colors.green(currentVersion)}`,
|
|
52
73
|
].join("\n"),
|
|
@@ -63,21 +84,30 @@ export async function update(currentVersion: string): Promise<void> {
|
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
// 有新版本
|
|
87
|
+
const versionText = `${currentVersion} → ${latestVersion}`;
|
|
88
|
+
const maxWidth = Math.max(
|
|
89
|
+
"🎉 发现新版本!".length,
|
|
90
|
+
versionText.length,
|
|
91
|
+
"✨ 更新完成!".length,
|
|
92
|
+
"请重新打开终端使用新版本".length
|
|
93
|
+
);
|
|
94
|
+
|
|
66
95
|
console.log(
|
|
67
96
|
boxen(
|
|
68
97
|
[
|
|
69
|
-
colors.bold("🎉 发现新版本!"),
|
|
98
|
+
colors.yellow(colors.bold("🎉 发现新版本!")),
|
|
70
99
|
"",
|
|
71
100
|
`${colors.dim(currentVersion)} → ${colors.green(
|
|
72
101
|
colors.bold(latestVersion)
|
|
73
102
|
)}`,
|
|
74
103
|
].join("\n"),
|
|
75
104
|
{
|
|
76
|
-
padding: 1,
|
|
105
|
+
padding: { top: 1, bottom: 1, left: 3, right: 3 },
|
|
77
106
|
margin: { top: 0, bottom: 1, left: 2, right: 2 },
|
|
78
107
|
borderStyle: "round",
|
|
79
108
|
borderColor: "yellow",
|
|
80
|
-
align: "
|
|
109
|
+
align: "center",
|
|
110
|
+
width: 40,
|
|
81
111
|
}
|
|
82
112
|
)
|
|
83
113
|
);
|
|
@@ -91,20 +121,25 @@ export async function update(currentVersion: string): Promise<void> {
|
|
|
91
121
|
});
|
|
92
122
|
|
|
93
123
|
updateSpinner.succeed(colors.green("更新成功!"));
|
|
124
|
+
|
|
125
|
+
// 清理缓存文件
|
|
126
|
+
clearUpdateCache();
|
|
127
|
+
|
|
94
128
|
console.log("");
|
|
95
129
|
console.log(
|
|
96
130
|
boxen(
|
|
97
131
|
[
|
|
98
|
-
colors.bold("✨ 更新完成!"),
|
|
132
|
+
colors.green(colors.bold("✨ 更新完成!")),
|
|
99
133
|
"",
|
|
100
134
|
colors.dim("请重新打开终端使用新版本"),
|
|
101
135
|
].join("\n"),
|
|
102
136
|
{
|
|
103
|
-
padding: 1,
|
|
137
|
+
padding: { top: 1, bottom: 1, left: 3, right: 3 },
|
|
104
138
|
margin: { top: 0, bottom: 1, left: 2, right: 2 },
|
|
105
139
|
borderStyle: "round",
|
|
106
140
|
borderColor: "green",
|
|
107
|
-
align: "
|
|
141
|
+
align: "center",
|
|
142
|
+
width: 40,
|
|
108
143
|
}
|
|
109
144
|
)
|
|
110
145
|
);
|