@zjex/git-workflow 0.2.4 → 0.2.6
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/README.md +114 -4
- package/dist/index.js +502 -44
- package/package.json +1 -1
- package/src/ai-service.ts +350 -0
- package/src/commands/commit.ts +121 -36
- package/src/commands/init.ts +156 -9
- package/src/config.ts +9 -0
- package/src/index.ts +25 -3
- package/src/update-notifier.ts +7 -3
- package/test-update-flow.mjs +0 -98
package/src/commands/init.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, writeFileSync } from "fs";
|
|
2
|
-
import { select, input
|
|
2
|
+
import { select, input } from "@inquirer/prompts";
|
|
3
3
|
import { colors, theme, divider } from "../utils.js";
|
|
4
4
|
import type { GwConfig } from "../config.js";
|
|
5
5
|
|
|
@@ -22,9 +22,12 @@ const DEFAULT_COMMIT_EMOJIS = {
|
|
|
22
22
|
|
|
23
23
|
export async function init(): Promise<void> {
|
|
24
24
|
if (existsSync(CONFIG_FILE)) {
|
|
25
|
-
const overwrite = await
|
|
25
|
+
const overwrite = await select({
|
|
26
26
|
message: `${CONFIG_FILE} 已存在,是否覆盖?`,
|
|
27
|
-
|
|
27
|
+
choices: [
|
|
28
|
+
{ name: "否,取消", value: false },
|
|
29
|
+
{ name: "是,覆盖", value: true },
|
|
30
|
+
],
|
|
28
31
|
theme,
|
|
29
32
|
});
|
|
30
33
|
if (!overwrite) {
|
|
@@ -64,9 +67,12 @@ export async function init(): Promise<void> {
|
|
|
64
67
|
divider();
|
|
65
68
|
|
|
66
69
|
// ID 配置
|
|
67
|
-
const requireId = await
|
|
70
|
+
const requireId = await select({
|
|
68
71
|
message: "是否要求必填 ID (Story ID / Issue ID)?",
|
|
69
|
-
|
|
72
|
+
choices: [
|
|
73
|
+
{ name: "否", value: false },
|
|
74
|
+
{ name: "是", value: true },
|
|
75
|
+
],
|
|
70
76
|
theme,
|
|
71
77
|
});
|
|
72
78
|
if (requireId) config.requireId = true;
|
|
@@ -110,16 +116,22 @@ export async function init(): Promise<void> {
|
|
|
110
116
|
divider();
|
|
111
117
|
|
|
112
118
|
// Commit 配置
|
|
113
|
-
const autoStage = await
|
|
119
|
+
const autoStage = await select({
|
|
114
120
|
message: "Commit 时是否自动暂存所有更改?",
|
|
115
|
-
|
|
121
|
+
choices: [
|
|
122
|
+
{ name: "是", value: true },
|
|
123
|
+
{ name: "否", value: false },
|
|
124
|
+
],
|
|
116
125
|
theme,
|
|
117
126
|
});
|
|
118
127
|
if (!autoStage) config.autoStage = false;
|
|
119
128
|
|
|
120
|
-
const useEmoji = await
|
|
129
|
+
const useEmoji = await select({
|
|
121
130
|
message: "Commit 时是否使用 emoji?",
|
|
122
|
-
|
|
131
|
+
choices: [
|
|
132
|
+
{ name: "是", value: true },
|
|
133
|
+
{ name: "否", value: false },
|
|
134
|
+
],
|
|
123
135
|
theme,
|
|
124
136
|
});
|
|
125
137
|
if (!useEmoji) config.useEmoji = false;
|
|
@@ -129,6 +141,122 @@ export async function init(): Promise<void> {
|
|
|
129
141
|
|
|
130
142
|
divider();
|
|
131
143
|
|
|
144
|
+
// AI Commit 配置
|
|
145
|
+
console.log(
|
|
146
|
+
colors.dim("\nAI Commit 配置 (使用 AI 自动生成 commit message)\n")
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const enableAI = await select({
|
|
150
|
+
message: "是否启用 AI Commit 功能?",
|
|
151
|
+
choices: [
|
|
152
|
+
{ name: "是(推荐)", value: true },
|
|
153
|
+
{ name: "否", value: false },
|
|
154
|
+
],
|
|
155
|
+
theme,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
if (enableAI) {
|
|
159
|
+
const aiProvider = await select({
|
|
160
|
+
message: "选择 AI 提供商:",
|
|
161
|
+
choices: [
|
|
162
|
+
{
|
|
163
|
+
name: "GitHub Models(免费,推荐)",
|
|
164
|
+
value: "github",
|
|
165
|
+
description: "使用 GitHub 账号,每天 150 次免费",
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "Groq(免费)",
|
|
169
|
+
value: "groq",
|
|
170
|
+
description: "需要注册,每天 14,400 次免费",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "OpenAI(付费)",
|
|
174
|
+
value: "openai",
|
|
175
|
+
description: "需要付费 API key",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "Claude(付费)",
|
|
179
|
+
value: "claude",
|
|
180
|
+
description: "需要付费 API key",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "Ollama(本地)",
|
|
184
|
+
value: "ollama",
|
|
185
|
+
description: "需要安装 Ollama",
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
theme,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const useBuiltinKey = await select({
|
|
192
|
+
message: "API Key 配置:",
|
|
193
|
+
choices: [
|
|
194
|
+
{
|
|
195
|
+
name: "使用内置 Key(开箱即用)",
|
|
196
|
+
value: true,
|
|
197
|
+
description: "使用工具内置的 API key,共享限额",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: "使用自己的 Key(推荐)",
|
|
201
|
+
value: false,
|
|
202
|
+
description: "配置自己的 API key,独享限额",
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
theme,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
let apiKey = "";
|
|
209
|
+
if (!useBuiltinKey) {
|
|
210
|
+
apiKey = await input({
|
|
211
|
+
message: `输入你的 ${
|
|
212
|
+
aiProvider === "github" ? "GitHub Token" : "API Key"
|
|
213
|
+
}:`,
|
|
214
|
+
validate: (value) => {
|
|
215
|
+
if (!value.trim()) return "API Key 不能为空";
|
|
216
|
+
return true;
|
|
217
|
+
},
|
|
218
|
+
theme,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const language = await select({
|
|
223
|
+
message: "生成的 commit message 语言:",
|
|
224
|
+
choices: [
|
|
225
|
+
{ name: "中文", value: "zh-CN" },
|
|
226
|
+
{ name: "English", value: "en-US" },
|
|
227
|
+
],
|
|
228
|
+
theme,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
config.aiCommit = {
|
|
232
|
+
enabled: true,
|
|
233
|
+
provider: aiProvider as
|
|
234
|
+
| "github"
|
|
235
|
+
| "groq"
|
|
236
|
+
| "openai"
|
|
237
|
+
| "claude"
|
|
238
|
+
| "ollama",
|
|
239
|
+
apiKey: apiKey || undefined,
|
|
240
|
+
language: language as "zh-CN" | "en-US",
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// 根据提供商设置默认模型
|
|
244
|
+
const defaultModels: Record<string, string> = {
|
|
245
|
+
github: "gpt-4o-mini",
|
|
246
|
+
groq: "llama-3.1-8b-instant",
|
|
247
|
+
openai: "gpt-4o-mini",
|
|
248
|
+
claude: "claude-3-haiku-20240307",
|
|
249
|
+
ollama: "qwen2.5-coder:7b",
|
|
250
|
+
};
|
|
251
|
+
config.aiCommit.model = defaultModels[aiProvider];
|
|
252
|
+
} else {
|
|
253
|
+
config.aiCommit = {
|
|
254
|
+
enabled: false,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
divider();
|
|
259
|
+
|
|
132
260
|
// 写入配置
|
|
133
261
|
const content = JSON.stringify(config, null, 2);
|
|
134
262
|
writeFileSync(CONFIG_FILE, content + "\n");
|
|
@@ -139,5 +267,24 @@ export async function init(): Promise<void> {
|
|
|
139
267
|
"\n提示: 可以在配置文件中修改 commitEmojis 来自定义各类型的 emoji"
|
|
140
268
|
)
|
|
141
269
|
);
|
|
270
|
+
|
|
271
|
+
if (config.aiCommit?.enabled) {
|
|
272
|
+
console.log(
|
|
273
|
+
colors.dim(
|
|
274
|
+
"提示: AI Commit 已启用,运行 'gw c' 时可以选择 AI 自动生成 commit message"
|
|
275
|
+
)
|
|
276
|
+
);
|
|
277
|
+
if (!config.aiCommit.apiKey) {
|
|
278
|
+
console.log(
|
|
279
|
+
colors.yellow(
|
|
280
|
+
"\n⚠️ 当前使用内置 API key,建议配置自己的 key 以获得更好的体验"
|
|
281
|
+
)
|
|
282
|
+
);
|
|
283
|
+
console.log(
|
|
284
|
+
colors.dim(" 获取方法: https://github.com/settings/tokens/new")
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
142
289
|
console.log(colors.dim("\n" + content));
|
|
143
290
|
}
|
package/src/config.ts
CHANGED
|
@@ -36,6 +36,15 @@ export interface GwConfig {
|
|
|
36
36
|
chore?: string;
|
|
37
37
|
revert?: string;
|
|
38
38
|
};
|
|
39
|
+
// AI commit 配置
|
|
40
|
+
aiCommit?: {
|
|
41
|
+
enabled?: boolean; // 是否启用 AI commit,默认 true
|
|
42
|
+
provider?: "github" | "groq" | "openai" | "claude" | "ollama"; // AI 提供商,默认 github
|
|
43
|
+
apiKey?: string; // API key,空则使用内置 key
|
|
44
|
+
model?: string; // 模型名称
|
|
45
|
+
language?: "zh-CN" | "en-US"; // 生成语言,默认 zh-CN
|
|
46
|
+
maxTokens?: number; // 最大 token 数,默认 200
|
|
47
|
+
};
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
const defaultConfig: GwConfig = {
|
package/src/index.ts
CHANGED
|
@@ -12,17 +12,39 @@ import { stash } from "./commands/stash.js";
|
|
|
12
12
|
import { commit } from "./commands/commit.js";
|
|
13
13
|
import { showHelp } from "./commands/help.js";
|
|
14
14
|
import { checkForUpdates } from "./update-notifier.js";
|
|
15
|
-
import { checkForUpdates } from "./update-notifier.js";
|
|
16
15
|
|
|
17
16
|
// 捕获 Ctrl+C 退出,静默处理
|
|
18
17
|
process.on("uncaughtException", (err) => {
|
|
19
18
|
if (err instanceof ExitPromptError) {
|
|
19
|
+
console.log(""); // 输出空行,让界面更整洁
|
|
20
20
|
process.exit(0);
|
|
21
21
|
}
|
|
22
22
|
console.error(err);
|
|
23
23
|
process.exit(1);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
// 捕获未处理的 Promise 拒绝
|
|
27
|
+
process.on("unhandledRejection", (reason) => {
|
|
28
|
+
if (reason instanceof ExitPromptError) {
|
|
29
|
+
console.log("");
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
console.error("未处理的 Promise 拒绝:", reason);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 捕获 SIGINT 信号 (Ctrl+C)
|
|
37
|
+
process.on("SIGINT", () => {
|
|
38
|
+
console.log("");
|
|
39
|
+
process.exit(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// 捕获 SIGTERM 信号
|
|
43
|
+
process.on("SIGTERM", () => {
|
|
44
|
+
console.log("");
|
|
45
|
+
process.exit(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
26
48
|
declare const __VERSION__: string | undefined;
|
|
27
49
|
|
|
28
50
|
// 开发环境下从 package.json 读取版本号
|
|
@@ -33,8 +55,8 @@ const version: string =
|
|
|
33
55
|
|
|
34
56
|
// 交互式主菜单
|
|
35
57
|
async function mainMenu(): Promise<void> {
|
|
36
|
-
//
|
|
37
|
-
checkForUpdates(version, "@zjex/git-workflow")
|
|
58
|
+
// 先检查更新,等待完成后再显示主菜单
|
|
59
|
+
await checkForUpdates(version, "@zjex/git-workflow");
|
|
38
60
|
|
|
39
61
|
// ASCII Art Logo
|
|
40
62
|
console.log(
|
package/src/update-notifier.ts
CHANGED
|
@@ -53,7 +53,11 @@ export async function checkForUpdates(
|
|
|
53
53
|
// action === "continue" 时直接继续,不记录
|
|
54
54
|
}
|
|
55
55
|
} catch (error) {
|
|
56
|
-
//
|
|
56
|
+
// 如果是用户按 Ctrl+C,重新抛出让全局处理
|
|
57
|
+
if (error?.constructor?.name === "ExitPromptError") {
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
// 其他错误静默失败,不影响主程序
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
|
|
@@ -123,9 +127,9 @@ async function showUpdateMessage(
|
|
|
123
127
|
|
|
124
128
|
return action as "update" | "continue" | "dismiss";
|
|
125
129
|
} catch (error) {
|
|
126
|
-
// 用户按了 Ctrl+C
|
|
130
|
+
// 用户按了 Ctrl+C,重新抛出错误让全局处理
|
|
127
131
|
console.log("");
|
|
128
|
-
|
|
132
|
+
throw error;
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
|
package/test-update-flow.mjs
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import boxen from "boxen";
|
|
4
|
-
import { select } from "@inquirer/prompts";
|
|
5
|
-
import ora from "ora";
|
|
6
|
-
|
|
7
|
-
const colors = {
|
|
8
|
-
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
9
|
-
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
10
|
-
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
11
|
-
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
12
|
-
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
13
|
-
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
14
|
-
reset: "\x1b[0m",
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
async function testUpdateFlow() {
|
|
18
|
-
const current = "0.1.0";
|
|
19
|
-
const latest = "0.2.0";
|
|
20
|
-
const packageName = "@zjex/git-workflow";
|
|
21
|
-
|
|
22
|
-
// 1. 显示更新提示框
|
|
23
|
-
const message = [
|
|
24
|
-
colors.bold("🎉 发现新版本可用!"),
|
|
25
|
-
"",
|
|
26
|
-
`${colors.dim(current)} → ${colors.green(colors.bold(latest))}`,
|
|
27
|
-
].join("\n");
|
|
28
|
-
|
|
29
|
-
console.log("");
|
|
30
|
-
console.log(
|
|
31
|
-
boxen(message, {
|
|
32
|
-
padding: 1,
|
|
33
|
-
margin: 1,
|
|
34
|
-
borderStyle: "round",
|
|
35
|
-
borderColor: "yellow",
|
|
36
|
-
align: "left",
|
|
37
|
-
})
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
// 2. 交互式选择
|
|
41
|
-
try {
|
|
42
|
-
const action = await select({
|
|
43
|
-
message: "你想做什么?",
|
|
44
|
-
choices: [
|
|
45
|
-
{
|
|
46
|
-
name: "🚀 立即更新",
|
|
47
|
-
value: "update",
|
|
48
|
-
description: `运行 npm install -g ${packageName}`,
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
name: "⏭️ 稍后更新,继续使用",
|
|
52
|
-
value: "continue",
|
|
53
|
-
description: "下次启动时会再次提示",
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: "🙈 跳过此版本 (24h 内不再提示)",
|
|
57
|
-
value: "dismiss",
|
|
58
|
-
description: "24 小时内不会再提示此版本",
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
console.log("");
|
|
64
|
-
|
|
65
|
-
// 3. 根据选择执行操作
|
|
66
|
-
if (action === "update") {
|
|
67
|
-
// 模拟更新过程
|
|
68
|
-
const spinner = ora({
|
|
69
|
-
text: "正在更新...",
|
|
70
|
-
spinner: "dots",
|
|
71
|
-
}).start();
|
|
72
|
-
|
|
73
|
-
// 模拟卸载旧版本
|
|
74
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
75
|
-
spinner.text = "已卸载旧版本,正在安装新版本...";
|
|
76
|
-
|
|
77
|
-
// 模拟安装新版本
|
|
78
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
79
|
-
|
|
80
|
-
spinner.succeed(colors.green("更新成功!"));
|
|
81
|
-
console.log("");
|
|
82
|
-
console.log(colors.cyan(" 提示: 请重新运行命令以使用新版本"));
|
|
83
|
-
console.log("");
|
|
84
|
-
} else if (action === "continue") {
|
|
85
|
-
console.log(colors.cyan("继续使用当前版本..."));
|
|
86
|
-
console.log("");
|
|
87
|
-
} else if (action === "dismiss") {
|
|
88
|
-
console.log(colors.dim("已跳过此版本,24 小时内不再提示"));
|
|
89
|
-
console.log("");
|
|
90
|
-
}
|
|
91
|
-
} catch (error) {
|
|
92
|
-
console.log("");
|
|
93
|
-
console.log(colors.dim("已取消"));
|
|
94
|
-
console.log("");
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
testUpdateFlow();
|