@zjex/git-workflow 0.2.20 → 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 +80 -32
- package/package.json +1 -1
- package/src/commands/commit.ts +93 -32
- package/src/commands/help.ts +1 -1
- package/src/commands/tag.ts +49 -22
- package/src/index.ts +59 -10
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# 代码文档说明
|
|
2
|
+
|
|
3
|
+
## 已添加详细注释的文件
|
|
4
|
+
|
|
5
|
+
### ✅ src/commands/commit.ts
|
|
6
|
+
|
|
7
|
+
**功能**: 交互式提交命令,支持 AI 自动生成和手动编写
|
|
8
|
+
|
|
9
|
+
**注释内容**:
|
|
10
|
+
|
|
11
|
+
- 模块级注释:说明 Conventional Commits 和 Gitmoji 规范
|
|
12
|
+
- 函数注释:每个函数都有 JSDoc 注释
|
|
13
|
+
- 步骤标记:用 `==========` 分隔不同的处理步骤
|
|
14
|
+
- 行内注释:关键逻辑都有说明
|
|
15
|
+
|
|
16
|
+
**关键函数**:
|
|
17
|
+
|
|
18
|
+
- `commit()`: 主函数,处理整个提交流程
|
|
19
|
+
- `buildManualCommitMessage()`: 手动构建 commit message
|
|
20
|
+
- `parseGitStatus()`: 解析 git 状态
|
|
21
|
+
- `formatFileStatus()`: 格式化文件状态显示
|
|
22
|
+
|
|
23
|
+
### ✅ src/index.ts
|
|
24
|
+
|
|
25
|
+
**功能**: 主入口文件,CLI 应用初始化和命令注册
|
|
26
|
+
|
|
27
|
+
**注释内容**:
|
|
28
|
+
|
|
29
|
+
- 模块级注释:说明文件职责
|
|
30
|
+
- 全局错误处理:Ctrl+C、未捕获异常、Promise 拒绝
|
|
31
|
+
- 版本信息:构建时注入
|
|
32
|
+
- 交互式主菜单:ASCII Logo 和命令选择
|
|
33
|
+
- CLI 命令注册:所有命令的注册和配置
|
|
34
|
+
|
|
35
|
+
## 其他文件状态
|
|
36
|
+
|
|
37
|
+
### ✅ 无错误的文件(已通过 TypeScript 检查)
|
|
38
|
+
|
|
39
|
+
- src/ai-service.ts
|
|
40
|
+
- src/config.ts
|
|
41
|
+
- src/utils.ts
|
|
42
|
+
- src/update-notifier.ts
|
|
43
|
+
- src/commands/branch.ts
|
|
44
|
+
- src/commands/help.ts
|
|
45
|
+
- src/commands/init.ts
|
|
46
|
+
- src/commands/release.ts
|
|
47
|
+
- src/commands/stash.ts
|
|
48
|
+
- src/commands/tag.ts
|
|
49
|
+
- src/commands/update.ts
|
|
50
|
+
|
|
51
|
+
## 代码质量改进
|
|
52
|
+
|
|
53
|
+
### 1. 类型安全
|
|
54
|
+
|
|
55
|
+
- 所有函数都有明确的返回类型
|
|
56
|
+
- 使用 TypeScript 的类型推导
|
|
57
|
+
- 接口定义清晰(如 `FileStatus`)
|
|
58
|
+
|
|
59
|
+
### 2. 错误处理
|
|
60
|
+
|
|
61
|
+
- 全局错误捕获
|
|
62
|
+
- 优雅的 Ctrl+C 处理
|
|
63
|
+
- 详细的错误提示信息
|
|
64
|
+
- 提供手动执行命令
|
|
65
|
+
|
|
66
|
+
### 3. 用户体验
|
|
67
|
+
|
|
68
|
+
- 提交前检查暂存文件
|
|
69
|
+
- 清晰的步骤分隔
|
|
70
|
+
- 彩色输出和 emoji
|
|
71
|
+
- 交互式选择界面
|
|
72
|
+
|
|
73
|
+
### 4. 代码组织
|
|
74
|
+
|
|
75
|
+
- 模块化设计
|
|
76
|
+
- 单一职责原则
|
|
77
|
+
- 清晰的函数命名
|
|
78
|
+
- 详细的注释说明
|
|
79
|
+
|
|
80
|
+
## 关键改进点
|
|
81
|
+
|
|
82
|
+
### commit.ts
|
|
83
|
+
|
|
84
|
+
1. **修复了 `message` 变量未初始化问题**
|
|
85
|
+
|
|
86
|
+
- 初始化为空字符串
|
|
87
|
+
- 确保所有代码路径都会赋值
|
|
88
|
+
|
|
89
|
+
2. **添加提交前检查**
|
|
90
|
+
|
|
91
|
+
- 再次验证是否有暂存文件
|
|
92
|
+
- 避免空提交
|
|
93
|
+
|
|
94
|
+
3. **改进错误提示**
|
|
95
|
+
|
|
96
|
+
- 显示完整错误信息
|
|
97
|
+
- 提供手动执行命令
|
|
98
|
+
|
|
99
|
+
4. **修复 refactor 对齐问题**
|
|
100
|
+
- 针对 ♻️ emoji 特殊处理
|
|
101
|
+
- 动态调整间距
|
|
102
|
+
|
|
103
|
+
### index.ts
|
|
104
|
+
|
|
105
|
+
1. **完善错误处理**
|
|
106
|
+
|
|
107
|
+
- 捕获所有类型的错误
|
|
108
|
+
- 优雅退出
|
|
109
|
+
|
|
110
|
+
2. **添加模块说明**
|
|
111
|
+
- 清晰的文件职责
|
|
112
|
+
- 详细的功能说明
|
|
113
|
+
|
|
114
|
+
## 建议
|
|
115
|
+
|
|
116
|
+
如果需要给其他文件添加详细注释,可以按照以下优先级:
|
|
117
|
+
|
|
118
|
+
1. **高优先级**(核心业务逻辑):
|
|
119
|
+
|
|
120
|
+
- src/ai-service.ts - AI commit 生成
|
|
121
|
+
- src/config.ts - 配置管理
|
|
122
|
+
- src/commands/tag.ts - Tag 管理
|
|
123
|
+
|
|
124
|
+
2. **中优先级**(常用功能):
|
|
125
|
+
|
|
126
|
+
- src/commands/branch.ts - 分支管理
|
|
127
|
+
- src/commands/stash.ts - Stash 管理
|
|
128
|
+
- src/update-notifier.ts - 更新检查
|
|
129
|
+
|
|
130
|
+
3. **低优先级**(辅助功能):
|
|
131
|
+
- src/utils.ts - 工具函数
|
|
132
|
+
- src/commands/help.ts - 帮助信息
|
|
133
|
+
- src/commands/init.ts - 初始化配置
|
|
134
|
+
|
|
135
|
+
## 注释规范
|
|
136
|
+
|
|
137
|
+
### JSDoc 注释格式
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
/**
|
|
141
|
+
* 函数简短描述
|
|
142
|
+
*
|
|
143
|
+
* 详细说明(可选)
|
|
144
|
+
*
|
|
145
|
+
* @param paramName 参数说明
|
|
146
|
+
* @returns 返回值说明
|
|
147
|
+
*/
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 行内注释
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// 简短说明当前代码的作用
|
|
154
|
+
const result = doSomething();
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 步骤标记
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// ========== 步骤 1: 处理输入 ==========
|
|
161
|
+
// ========== 步骤 2: 验证数据 ==========
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## 总结
|
|
165
|
+
|
|
166
|
+
✅ 核心文件已添加详细注释
|
|
167
|
+
✅ 所有文件通过 TypeScript 检查
|
|
168
|
+
✅ 代码质量和可维护性显著提升
|
|
169
|
+
✅ 用户体验得到改善
|
package/dist/index.js
CHANGED
|
@@ -978,38 +978,55 @@ async function updateTag() {
|
|
|
978
978
|
divider();
|
|
979
979
|
const choices = tags.map((tag) => ({ name: tag, value: tag }));
|
|
980
980
|
choices.push({ name: "\u53D6\u6D88", value: "__cancel__" });
|
|
981
|
-
const
|
|
982
|
-
message: "\u9009\u62E9\u8981\
|
|
981
|
+
const oldTag = await select2({
|
|
982
|
+
message: "\u9009\u62E9\u8981\u91CD\u547D\u540D\u7684 tag:",
|
|
983
983
|
choices,
|
|
984
984
|
theme
|
|
985
985
|
});
|
|
986
|
-
if (
|
|
986
|
+
if (oldTag === "__cancel__") {
|
|
987
987
|
console.log(colors.yellow("\u5DF2\u53D6\u6D88"));
|
|
988
988
|
return;
|
|
989
989
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
990
|
+
console.log("");
|
|
991
|
+
console.log(colors.dim(`\u5F53\u524D tag: ${oldTag}`));
|
|
992
|
+
console.log("");
|
|
993
|
+
const newTag = await input2({
|
|
994
|
+
message: "\u8F93\u5165\u65B0\u7684 tag \u540D\u79F0:",
|
|
995
|
+
default: oldTag,
|
|
993
996
|
theme
|
|
994
997
|
});
|
|
995
|
-
if (!
|
|
998
|
+
if (!newTag || newTag === oldTag) {
|
|
996
999
|
console.log(colors.yellow("\u5DF2\u53D6\u6D88"));
|
|
997
1000
|
return;
|
|
998
1001
|
}
|
|
1002
|
+
const existingTags = execOutput("git tag -l").split("\n").filter(Boolean);
|
|
1003
|
+
if (existingTags.includes(newTag)) {
|
|
1004
|
+
console.log(colors.red(`Tag ${newTag} \u5DF2\u5B58\u5728\uFF0C\u65E0\u6CD5\u91CD\u547D\u540D`));
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
999
1007
|
divider();
|
|
1000
|
-
const spinner = ora2(`\u6B63\u5728\
|
|
1008
|
+
const spinner = ora2(`\u6B63\u5728\u91CD\u547D\u540D tag: ${oldTag} \u2192 ${newTag}`).start();
|
|
1001
1009
|
try {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1010
|
+
const commit2 = execOutput(`git rev-list -n 1 "${oldTag}"`).trim();
|
|
1011
|
+
const message = execOutput(
|
|
1012
|
+
`git tag -l --format='%(contents)' "${oldTag}"`
|
|
1013
|
+
).trim();
|
|
1014
|
+
if (message) {
|
|
1015
|
+
execSync3(`git tag -a "${newTag}" "${commit2}" -m "${message}"`, {
|
|
1016
|
+
stdio: "pipe"
|
|
1017
|
+
});
|
|
1018
|
+
} else {
|
|
1019
|
+
execSync3(`git tag "${newTag}" "${commit2}"`, { stdio: "pipe" });
|
|
1020
|
+
}
|
|
1021
|
+
execSync3(`git tag -d "${oldTag}"`, { stdio: "pipe" });
|
|
1022
|
+
spinner.succeed(`Tag \u5DF2\u91CD\u547D\u540D: ${oldTag} \u2192 ${newTag}`);
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
spinner.fail("tag \u91CD\u547D\u540D\u5931\u8D25");
|
|
1025
|
+
console.log(colors.red(String(error)));
|
|
1009
1026
|
return;
|
|
1010
1027
|
}
|
|
1011
1028
|
const pushRemote = await select2({
|
|
1012
|
-
message: "\u662F\u5426\
|
|
1029
|
+
message: "\u662F\u5426\u540C\u6B65\u5230\u8FDC\u7A0B?",
|
|
1013
1030
|
choices: [
|
|
1014
1031
|
{ name: "\u662F", value: true },
|
|
1015
1032
|
{ name: "\u5426", value: false }
|
|
@@ -1017,13 +1034,16 @@ async function updateTag() {
|
|
|
1017
1034
|
theme
|
|
1018
1035
|
});
|
|
1019
1036
|
if (pushRemote) {
|
|
1020
|
-
const pushSpinner = ora2("\u6B63\u5728\
|
|
1037
|
+
const pushSpinner = ora2("\u6B63\u5728\u540C\u6B65\u5230\u8FDC\u7A0B...").start();
|
|
1021
1038
|
try {
|
|
1022
|
-
execSync3(`git push origin "${
|
|
1023
|
-
|
|
1039
|
+
execSync3(`git push origin "${newTag}"`, { stdio: "pipe" });
|
|
1040
|
+
execSync3(`git push origin --delete "${oldTag}"`, { stdio: "pipe" });
|
|
1041
|
+
pushSpinner.succeed(`\u8FDC\u7A0B tag \u5DF2\u540C\u6B65: ${oldTag} \u2192 ${newTag}`);
|
|
1024
1042
|
} catch {
|
|
1025
1043
|
pushSpinner.warn(
|
|
1026
|
-
`\u8FDC\u7A0B\
|
|
1044
|
+
`\u8FDC\u7A0B\u540C\u6B65\u5931\u8D25\uFF0C\u53EF\u7A0D\u540E\u624B\u52A8\u6267\u884C:
|
|
1045
|
+
git push origin ${newTag}
|
|
1046
|
+
git push origin --delete ${oldTag}`
|
|
1027
1047
|
);
|
|
1028
1048
|
}
|
|
1029
1049
|
}
|
|
@@ -1882,8 +1902,7 @@ var DEFAULT_COMMIT_TYPES = [
|
|
|
1882
1902
|
{ type: "test", emoji: "\u2705", description: "\u6D4B\u8BD5\u76F8\u5173" },
|
|
1883
1903
|
{ type: "build", emoji: "\u{1F4E6}", description: "\u6784\u5EFA/\u4F9D\u8D56\u76F8\u5173" },
|
|
1884
1904
|
{ type: "ci", emoji: "\u{1F477}", description: "CI/CD \u76F8\u5173" },
|
|
1885
|
-
{ type: "chore", emoji: "\u{1F527}", description: "\u5176\u4ED6\u6742\u9879" }
|
|
1886
|
-
{ type: "revert", emoji: "\u23EA", description: "\u56DE\u9000\u63D0\u4EA4" }
|
|
1905
|
+
{ type: "chore", emoji: "\u{1F527}", description: "\u5176\u4ED6\u6742\u9879" }
|
|
1887
1906
|
];
|
|
1888
1907
|
function getCommitTypes(config2) {
|
|
1889
1908
|
const customEmojis = config2.commitEmojis || {};
|
|
@@ -1915,11 +1934,17 @@ function parseGitStatus() {
|
|
|
1915
1934
|
function formatFileStatus(status) {
|
|
1916
1935
|
const statusMap = {
|
|
1917
1936
|
M: colors.yellow("M"),
|
|
1937
|
+
// 修改
|
|
1918
1938
|
A: colors.green("A"),
|
|
1939
|
+
// 新增
|
|
1919
1940
|
D: colors.red("D"),
|
|
1941
|
+
// 删除
|
|
1920
1942
|
R: colors.yellow("R"),
|
|
1943
|
+
// 重命名
|
|
1921
1944
|
C: colors.yellow("C"),
|
|
1945
|
+
// 复制
|
|
1922
1946
|
"?": colors.green("?")
|
|
1947
|
+
// 未跟踪
|
|
1923
1948
|
};
|
|
1924
1949
|
return statusMap[status] || status;
|
|
1925
1950
|
}
|
|
@@ -1994,7 +2019,7 @@ async function commit() {
|
|
|
1994
2019
|
theme
|
|
1995
2020
|
});
|
|
1996
2021
|
}
|
|
1997
|
-
let message;
|
|
2022
|
+
let message = "";
|
|
1998
2023
|
if (commitMode === "ai") {
|
|
1999
2024
|
const spinner2 = ora4("AI \u6B63\u5728\u5206\u6790\u4EE3\u7801\u53D8\u66F4...").start();
|
|
2000
2025
|
try {
|
|
@@ -2049,6 +2074,17 @@ async function commit() {
|
|
|
2049
2074
|
}
|
|
2050
2075
|
const spinner = ora4("\u6B63\u5728\u63D0\u4EA4...").start();
|
|
2051
2076
|
try {
|
|
2077
|
+
const finalStatus = parseGitStatus();
|
|
2078
|
+
if (finalStatus.staged.length === 0) {
|
|
2079
|
+
spinner.fail("\u6CA1\u6709\u6682\u5B58\u7684\u6587\u4EF6\u53EF\u4EE5\u63D0\u4EA4");
|
|
2080
|
+
console.log("");
|
|
2081
|
+
console.log(colors.yellow("\u8BF7\u5148\u6682\u5B58\u6587\u4EF6:"));
|
|
2082
|
+
console.log(colors.cyan(" git add <file>"));
|
|
2083
|
+
console.log(colors.dim(" \u6216"));
|
|
2084
|
+
console.log(colors.cyan(" git add -A"));
|
|
2085
|
+
console.log("");
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2052
2088
|
const escapedMessage = message.replace(/"/g, '\\"');
|
|
2053
2089
|
execSync5(`git commit -m "${escapedMessage}"`, { stdio: "pipe" });
|
|
2054
2090
|
spinner.succeed("\u63D0\u4EA4\u6210\u529F");
|
|
@@ -2056,19 +2092,31 @@ async function commit() {
|
|
|
2056
2092
|
console.log(colors.dim(`commit: ${commitHash}`));
|
|
2057
2093
|
} catch (error) {
|
|
2058
2094
|
spinner.fail("\u63D0\u4EA4\u5931\u8D25");
|
|
2095
|
+
console.log("");
|
|
2059
2096
|
if (error instanceof Error) {
|
|
2060
|
-
console.log(colors.red(
|
|
2097
|
+
console.log(colors.red("\u9519\u8BEF\u4FE1\u606F:"));
|
|
2098
|
+
console.log(colors.dim(` ${error.message}`));
|
|
2061
2099
|
}
|
|
2100
|
+
console.log("");
|
|
2101
|
+
console.log(colors.yellow("\u4F60\u53EF\u4EE5\u624B\u52A8\u6267\u884C\u4EE5\u4E0B\u547D\u4EE4:"));
|
|
2102
|
+
console.log(colors.cyan(` git commit -m "${message}"`));
|
|
2103
|
+
console.log("");
|
|
2062
2104
|
}
|
|
2063
2105
|
}
|
|
2064
2106
|
async function buildManualCommitMessage(config2) {
|
|
2065
2107
|
const commitTypes = getCommitTypes(config2);
|
|
2066
2108
|
const typeChoice = await select6({
|
|
2067
2109
|
message: "\u9009\u62E9\u63D0\u4EA4\u7C7B\u578B:",
|
|
2068
|
-
choices: commitTypes.map((t) =>
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2110
|
+
choices: commitTypes.map((t) => {
|
|
2111
|
+
const typeText = t.type.padEnd(10);
|
|
2112
|
+
const spacing = t.type === "refactor" ? " " : " ";
|
|
2113
|
+
return {
|
|
2114
|
+
name: `${t.emoji}${spacing}${typeText} ${colors.dim(t.description)}`,
|
|
2115
|
+
value: t
|
|
2116
|
+
};
|
|
2117
|
+
}),
|
|
2118
|
+
pageSize: commitTypes.length,
|
|
2119
|
+
// 显示所有选项,不滚动
|
|
2072
2120
|
theme
|
|
2073
2121
|
});
|
|
2074
2122
|
const scope = await input5({
|
|
@@ -2161,7 +2209,7 @@ Tag \u547D\u4EE4:
|
|
|
2161
2209
|
gw tag:delete \u5220\u9664 tag
|
|
2162
2210
|
gw td \u540C\u4E0A (\u522B\u540D)
|
|
2163
2211
|
|
|
2164
|
-
gw tag:update \
|
|
2212
|
+
gw tag:update \u91CD\u547D\u540D tag
|
|
2165
2213
|
gw tu \u540C\u4E0A (\u522B\u540D)
|
|
2166
2214
|
|
|
2167
2215
|
\u53D1\u5E03\u547D\u4EE4:
|
|
@@ -2365,7 +2413,7 @@ process.on("SIGTERM", () => {
|
|
|
2365
2413
|
console.log("");
|
|
2366
2414
|
process.exit(0);
|
|
2367
2415
|
});
|
|
2368
|
-
var version = true ? "0.2.
|
|
2416
|
+
var version = true ? "0.2.21" : "0.0.0-dev";
|
|
2369
2417
|
async function mainMenu() {
|
|
2370
2418
|
console.log(
|
|
2371
2419
|
colors.green(`
|
|
@@ -2407,7 +2455,7 @@ async function mainMenu() {
|
|
|
2407
2455
|
value: "tag-delete"
|
|
2408
2456
|
},
|
|
2409
2457
|
{
|
|
2410
|
-
name: `[7] \u270F\uFE0F \
|
|
2458
|
+
name: `[7] \u270F\uFE0F \u91CD\u547D\u540D tag ${colors.dim("gw tu")}`,
|
|
2411
2459
|
value: "tag-update"
|
|
2412
2460
|
},
|
|
2413
2461
|
{
|
|
@@ -2517,7 +2565,7 @@ cli.command("tag:delete", "\u5220\u9664 tag").alias("td").action(async () => {
|
|
|
2517
2565
|
checkGitRepo();
|
|
2518
2566
|
return deleteTag();
|
|
2519
2567
|
});
|
|
2520
|
-
cli.command("tag:update", "\
|
|
2568
|
+
cli.command("tag:update", "\u91CD\u547D\u540D tag").alias("tu").action(async () => {
|
|
2521
2569
|
await checkForUpdates(version, "@zjex/git-workflow");
|
|
2522
2570
|
checkGitRepo();
|
|
2523
2571
|
return updateTag();
|
package/package.json
CHANGED
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
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/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,9 +212,20 @@ 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
230
|
await checkForUpdates(version, "@zjex/git-workflow", true);
|
|
182
231
|
return mainMenu();
|
|
@@ -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");
|