@zjex/git-workflow 0.2.17 → 0.2.18
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 +587 -158
- package/dist/index.js +158 -108
- package/package.json +1 -1
- package/src/ai-service.ts +5 -44
- package/src/commands/commit.ts +26 -23
- package/src/commands/help.ts +4 -1
- package/src/commands/init.ts +99 -47
- package/src/config.ts +46 -11
package/src/commands/init.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync, writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
2
4
|
import { select, input } from "@inquirer/prompts";
|
|
3
5
|
import { colors, theme, divider } from "../utils.js";
|
|
4
6
|
import type { GwConfig } from "../config.js";
|
|
5
7
|
|
|
6
|
-
const CONFIG_FILE = ".gwrc.json";
|
|
7
|
-
|
|
8
8
|
// 默认的 commit emoji 配置
|
|
9
9
|
const DEFAULT_COMMIT_EMOJIS = {
|
|
10
10
|
feat: "✨",
|
|
@@ -21,9 +21,34 @@ const DEFAULT_COMMIT_EMOJIS = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export async function init(): Promise<void> {
|
|
24
|
-
|
|
24
|
+
console.log("");
|
|
25
|
+
console.log(colors.bold("⚙️ 初始化 git-workflow 配置"));
|
|
26
|
+
console.log("");
|
|
27
|
+
|
|
28
|
+
// 选择配置范围
|
|
29
|
+
const configScope = await select({
|
|
30
|
+
message: "选择配置范围:",
|
|
31
|
+
choices: [
|
|
32
|
+
{
|
|
33
|
+
name: "全局配置(所有项目生效)",
|
|
34
|
+
value: "global",
|
|
35
|
+
description: "保存到 ~/.gwrc.json,所有项目都会使用此配置",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "项目配置(仅当前项目)",
|
|
39
|
+
value: "project",
|
|
40
|
+
description: "保存到当前目录 .gwrc.json,仅当前项目使用",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
theme,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const isGlobal = configScope === "global";
|
|
47
|
+
const configFile = isGlobal ? join(homedir(), ".gwrc.json") : ".gwrc.json";
|
|
48
|
+
|
|
49
|
+
if (existsSync(configFile)) {
|
|
25
50
|
const overwrite = await select({
|
|
26
|
-
message: `${
|
|
51
|
+
message: `${isGlobal ? "全局" : "项目"}配置文件已存在,是否覆盖?`,
|
|
27
52
|
choices: [
|
|
28
53
|
{ name: "否,取消", value: false },
|
|
29
54
|
{ name: "是,覆盖", value: true },
|
|
@@ -55,14 +80,14 @@ export async function init(): Promise<void> {
|
|
|
55
80
|
default: "feature",
|
|
56
81
|
theme,
|
|
57
82
|
});
|
|
58
|
-
|
|
83
|
+
config.featurePrefix = featurePrefix;
|
|
59
84
|
|
|
60
85
|
const hotfixPrefix = await input({
|
|
61
86
|
message: "Hotfix 分支前缀:",
|
|
62
87
|
default: "hotfix",
|
|
63
88
|
theme,
|
|
64
89
|
});
|
|
65
|
-
|
|
90
|
+
config.hotfixPrefix = hotfixPrefix;
|
|
66
91
|
|
|
67
92
|
divider();
|
|
68
93
|
|
|
@@ -75,21 +100,21 @@ export async function init(): Promise<void> {
|
|
|
75
100
|
],
|
|
76
101
|
theme,
|
|
77
102
|
});
|
|
78
|
-
|
|
103
|
+
config.requireId = requireId;
|
|
79
104
|
|
|
80
105
|
const featureIdLabel = await input({
|
|
81
106
|
message: "Feature 分支 ID 标签:",
|
|
82
107
|
default: "Story ID",
|
|
83
108
|
theme,
|
|
84
109
|
});
|
|
85
|
-
|
|
110
|
+
config.featureIdLabel = featureIdLabel;
|
|
86
111
|
|
|
87
112
|
const hotfixIdLabel = await input({
|
|
88
113
|
message: "Hotfix 分支 ID 标签:",
|
|
89
114
|
default: "Issue ID",
|
|
90
115
|
theme,
|
|
91
116
|
});
|
|
92
|
-
|
|
117
|
+
config.hotfixIdLabel = hotfixIdLabel;
|
|
93
118
|
|
|
94
119
|
divider();
|
|
95
120
|
|
|
@@ -110,8 +135,12 @@ export async function init(): Promise<void> {
|
|
|
110
135
|
],
|
|
111
136
|
theme,
|
|
112
137
|
});
|
|
113
|
-
if (autoPushChoice === "yes")
|
|
114
|
-
|
|
138
|
+
if (autoPushChoice === "yes") {
|
|
139
|
+
config.autoPush = true;
|
|
140
|
+
} else if (autoPushChoice === "no") {
|
|
141
|
+
config.autoPush = false;
|
|
142
|
+
}
|
|
143
|
+
// autoPushChoice === "ask" 时不设置,使用默认行为(每次询问)
|
|
115
144
|
|
|
116
145
|
divider();
|
|
117
146
|
|
|
@@ -124,7 +153,7 @@ export async function init(): Promise<void> {
|
|
|
124
153
|
],
|
|
125
154
|
theme,
|
|
126
155
|
});
|
|
127
|
-
|
|
156
|
+
config.autoStage = autoStage;
|
|
128
157
|
|
|
129
158
|
const useEmoji = await select({
|
|
130
159
|
message: "Commit 时是否使用 emoji?",
|
|
@@ -134,7 +163,7 @@ export async function init(): Promise<void> {
|
|
|
134
163
|
],
|
|
135
164
|
theme,
|
|
136
165
|
});
|
|
137
|
-
|
|
166
|
+
config.useEmoji = useEmoji;
|
|
138
167
|
|
|
139
168
|
// 始终写入默认的 commitEmojis 配置,方便用户修改
|
|
140
169
|
config.commitEmojis = DEFAULT_COMMIT_EMOJIS;
|
|
@@ -164,11 +193,6 @@ export async function init(): Promise<void> {
|
|
|
164
193
|
value: "github",
|
|
165
194
|
description: "使用 GitHub 账号,每天 150 次免费",
|
|
166
195
|
},
|
|
167
|
-
{
|
|
168
|
-
name: "Groq(免费)",
|
|
169
|
-
value: "groq",
|
|
170
|
-
description: "需要注册,每天 14,400 次免费",
|
|
171
|
-
},
|
|
172
196
|
{
|
|
173
197
|
name: "OpenAI(付费)",
|
|
174
198
|
value: "openai",
|
|
@@ -188,28 +212,42 @@ export async function init(): Promise<void> {
|
|
|
188
212
|
theme,
|
|
189
213
|
});
|
|
190
214
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
215
|
+
let apiKey = "";
|
|
216
|
+
|
|
217
|
+
// GitHub Models 需要配置 GitHub Token
|
|
218
|
+
if (aiProvider === "github") {
|
|
219
|
+
console.log("");
|
|
220
|
+
console.log(colors.cyan("💡 如何获取 GitHub Token:"));
|
|
221
|
+
console.log(
|
|
222
|
+
colors.dim(" 1. 访问: https://github.com/settings/tokens/new")
|
|
223
|
+
);
|
|
224
|
+
console.log(colors.dim(" 2. 勾选 'repo' 权限"));
|
|
225
|
+
console.log(colors.dim(" 3. 生成并复制 token"));
|
|
226
|
+
console.log("");
|
|
227
|
+
|
|
228
|
+
apiKey = await input({
|
|
229
|
+
message: "输入你的 GitHub Token:",
|
|
230
|
+
validate: (value) => {
|
|
231
|
+
if (!value.trim()) return "GitHub Token 不能为空";
|
|
232
|
+
return true;
|
|
203
233
|
},
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
})
|
|
234
|
+
theme,
|
|
235
|
+
});
|
|
236
|
+
} else if (aiProvider !== "ollama") {
|
|
237
|
+
// OpenAI 和 Claude 必须配置 API key
|
|
238
|
+
console.log("");
|
|
239
|
+
if (aiProvider === "openai") {
|
|
240
|
+
console.log(colors.cyan("💡 如何获取 OpenAI API Key:"));
|
|
241
|
+
console.log(colors.dim(" 访问: https://platform.openai.com/api-keys"));
|
|
242
|
+
} else {
|
|
243
|
+
console.log(colors.cyan("💡 如何获取 Claude API Key:"));
|
|
244
|
+
console.log(colors.dim(" 访问: https://console.anthropic.com/"));
|
|
245
|
+
}
|
|
246
|
+
console.log("");
|
|
207
247
|
|
|
208
|
-
let apiKey = "";
|
|
209
|
-
if (!useBuiltinKey) {
|
|
210
248
|
apiKey = await input({
|
|
211
249
|
message: `输入你的 ${
|
|
212
|
-
aiProvider === "
|
|
250
|
+
aiProvider === "openai" ? "OpenAI API Key" : "Claude API Key"
|
|
213
251
|
}:`,
|
|
214
252
|
validate: (value) => {
|
|
215
253
|
if (!value.trim()) return "API Key 不能为空";
|
|
@@ -230,12 +268,7 @@ export async function init(): Promise<void> {
|
|
|
230
268
|
|
|
231
269
|
config.aiCommit = {
|
|
232
270
|
enabled: true,
|
|
233
|
-
provider: aiProvider as
|
|
234
|
-
| "github"
|
|
235
|
-
| "groq"
|
|
236
|
-
| "openai"
|
|
237
|
-
| "claude"
|
|
238
|
-
| "ollama",
|
|
271
|
+
provider: aiProvider as "github" | "openai" | "claude" | "ollama",
|
|
239
272
|
apiKey: apiKey || undefined,
|
|
240
273
|
language: language as "zh-CN" | "en-US",
|
|
241
274
|
};
|
|
@@ -243,7 +276,6 @@ export async function init(): Promise<void> {
|
|
|
243
276
|
// 根据提供商设置默认模型
|
|
244
277
|
const defaultModels: Record<string, string> = {
|
|
245
278
|
github: "gpt-4o-mini",
|
|
246
|
-
groq: "llama-3.1-8b-instant",
|
|
247
279
|
openai: "gpt-4o-mini",
|
|
248
280
|
claude: "claude-3-haiku-20240307",
|
|
249
281
|
ollama: "qwen2.5-coder:7b",
|
|
@@ -259,15 +291,35 @@ export async function init(): Promise<void> {
|
|
|
259
291
|
|
|
260
292
|
// 写入配置
|
|
261
293
|
const content = JSON.stringify(config, null, 2);
|
|
262
|
-
writeFileSync(
|
|
294
|
+
writeFileSync(configFile, content + "\n");
|
|
263
295
|
|
|
264
|
-
console.log(colors.green(`✓ 配置已保存到 ${CONFIG_FILE}`));
|
|
265
296
|
console.log(
|
|
266
|
-
colors.
|
|
267
|
-
|
|
297
|
+
colors.green(
|
|
298
|
+
`✓ 配置已保存到 ${
|
|
299
|
+
isGlobal ? "全局配置文件" : "项目配置文件"
|
|
300
|
+
}: ${configFile}`
|
|
268
301
|
)
|
|
269
302
|
);
|
|
270
303
|
|
|
304
|
+
if (isGlobal) {
|
|
305
|
+
console.log("");
|
|
306
|
+
console.log(colors.cyan("💡 提示:"));
|
|
307
|
+
console.log(
|
|
308
|
+
colors.dim(" • 全局配置对所有项目生效,无需在每个项目中重复配置")
|
|
309
|
+
);
|
|
310
|
+
console.log(
|
|
311
|
+
colors.dim(" • 如需为特定项目自定义配置,可在项目中运行 gw init")
|
|
312
|
+
);
|
|
313
|
+
console.log(colors.dim(" • 项目配置会覆盖全局配置"));
|
|
314
|
+
} else {
|
|
315
|
+
console.log("");
|
|
316
|
+
console.log(
|
|
317
|
+
colors.dim(
|
|
318
|
+
"提示: 可以在配置文件中修改 commitEmojis 来自定义各类型的 emoji"
|
|
319
|
+
)
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
271
323
|
if (config.aiCommit?.enabled) {
|
|
272
324
|
console.log(
|
|
273
325
|
colors.dim(
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
3
4
|
import { execOutput } from "./utils.js";
|
|
4
5
|
|
|
5
6
|
export interface GwConfig {
|
|
@@ -39,7 +40,7 @@ export interface GwConfig {
|
|
|
39
40
|
// AI commit 配置
|
|
40
41
|
aiCommit?: {
|
|
41
42
|
enabled?: boolean; // 是否启用 AI commit,默认 true
|
|
42
|
-
provider?: "github" | "
|
|
43
|
+
provider?: "github" | "openai" | "claude" | "ollama"; // AI 提供商,默认 github
|
|
43
44
|
apiKey?: string; // API key,空则使用内置 key
|
|
44
45
|
model?: string; // 模型名称
|
|
45
46
|
language?: "zh-CN" | "en-US"; // 生成语言,默认 zh-CN
|
|
@@ -89,21 +90,55 @@ function findConfigFile(): string | null {
|
|
|
89
90
|
return null;
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
function findGlobalConfigFile(): string | null {
|
|
94
|
+
const globalConfigPath = join(homedir(), ".gwrc.json");
|
|
95
|
+
return existsSync(globalConfigPath) ? globalConfigPath : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
92
98
|
export function loadConfig(): GwConfig {
|
|
93
|
-
|
|
99
|
+
let config = { ...defaultConfig };
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
// 1. 先加载全局配置
|
|
102
|
+
const globalConfigPath = findGlobalConfigFile();
|
|
103
|
+
if (globalConfigPath) {
|
|
104
|
+
try {
|
|
105
|
+
const content = readFileSync(globalConfigPath, "utf-8");
|
|
106
|
+
const globalConfig = JSON.parse(content) as Partial<GwConfig>;
|
|
107
|
+
// 深度合并 aiCommit 配置
|
|
108
|
+
config = {
|
|
109
|
+
...config,
|
|
110
|
+
...globalConfig,
|
|
111
|
+
aiCommit: {
|
|
112
|
+
...config.aiCommit,
|
|
113
|
+
...globalConfig.aiCommit,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
} catch (e) {
|
|
117
|
+
console.warn(`全局配置文件解析失败: ${globalConfigPath}`);
|
|
118
|
+
}
|
|
97
119
|
}
|
|
98
120
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
121
|
+
// 2. 再加载项目配置(会覆盖全局配置)
|
|
122
|
+
const projectConfigPath = findConfigFile();
|
|
123
|
+
if (projectConfigPath) {
|
|
124
|
+
try {
|
|
125
|
+
const content = readFileSync(projectConfigPath, "utf-8");
|
|
126
|
+
const projectConfig = JSON.parse(content) as Partial<GwConfig>;
|
|
127
|
+
// 深度合并 aiCommit 配置
|
|
128
|
+
config = {
|
|
129
|
+
...config,
|
|
130
|
+
...projectConfig,
|
|
131
|
+
aiCommit: {
|
|
132
|
+
...config.aiCommit,
|
|
133
|
+
...projectConfig.aiCommit,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
} catch (e) {
|
|
137
|
+
console.warn(`项目配置文件解析失败: ${projectConfigPath}`);
|
|
138
|
+
}
|
|
106
139
|
}
|
|
140
|
+
|
|
141
|
+
return config;
|
|
107
142
|
}
|
|
108
143
|
|
|
109
144
|
// 全局配置实例
|