@zjex/git-workflow 0.2.5 → 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 +497 -41
- 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 +23 -1
- package/test-ctrl-c.mjs +0 -32
- package/test-full-flow.mjs +0 -108
- package/test-update-flow.mjs +0 -98
package/dist/index.js
CHANGED
|
@@ -274,7 +274,7 @@ async function deleteBranch(branchArg) {
|
|
|
274
274
|
}
|
|
275
275
|
if (branch.startsWith("__remote__")) {
|
|
276
276
|
const remoteBranch = branch.replace("__remote__", "");
|
|
277
|
-
const
|
|
277
|
+
const confirm = await select({
|
|
278
278
|
message: `\u786E\u8BA4\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${remoteBranch}?`,
|
|
279
279
|
choices: [
|
|
280
280
|
{ name: "\u662F", value: true },
|
|
@@ -282,7 +282,7 @@ async function deleteBranch(branchArg) {
|
|
|
282
282
|
],
|
|
283
283
|
theme
|
|
284
284
|
});
|
|
285
|
-
if (!
|
|
285
|
+
if (!confirm) {
|
|
286
286
|
console.log(colors.yellow("\u5DF2\u53D6\u6D88"));
|
|
287
287
|
return;
|
|
288
288
|
}
|
|
@@ -736,7 +736,7 @@ async function release() {
|
|
|
736
736
|
|
|
737
737
|
// src/commands/init.ts
|
|
738
738
|
import { existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
739
|
-
import { select as select4, input as input3
|
|
739
|
+
import { select as select4, input as input3 } from "@inquirer/prompts";
|
|
740
740
|
var CONFIG_FILE = ".gwrc.json";
|
|
741
741
|
var DEFAULT_COMMIT_EMOJIS = {
|
|
742
742
|
feat: "\u2728",
|
|
@@ -753,9 +753,12 @@ var DEFAULT_COMMIT_EMOJIS = {
|
|
|
753
753
|
};
|
|
754
754
|
async function init() {
|
|
755
755
|
if (existsSync2(CONFIG_FILE)) {
|
|
756
|
-
const overwrite = await
|
|
756
|
+
const overwrite = await select4({
|
|
757
757
|
message: `${CONFIG_FILE} \u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6?`,
|
|
758
|
-
|
|
758
|
+
choices: [
|
|
759
|
+
{ name: "\u5426\uFF0C\u53D6\u6D88", value: false },
|
|
760
|
+
{ name: "\u662F\uFF0C\u8986\u76D6", value: true }
|
|
761
|
+
],
|
|
759
762
|
theme
|
|
760
763
|
});
|
|
761
764
|
if (!overwrite) {
|
|
@@ -784,9 +787,12 @@ async function init() {
|
|
|
784
787
|
});
|
|
785
788
|
if (hotfixPrefix !== "hotfix") config2.hotfixPrefix = hotfixPrefix;
|
|
786
789
|
divider();
|
|
787
|
-
const requireId = await
|
|
790
|
+
const requireId = await select4({
|
|
788
791
|
message: "\u662F\u5426\u8981\u6C42\u5FC5\u586B ID (Story ID / Issue ID)?",
|
|
789
|
-
|
|
792
|
+
choices: [
|
|
793
|
+
{ name: "\u5426", value: false },
|
|
794
|
+
{ name: "\u662F", value: true }
|
|
795
|
+
],
|
|
790
796
|
theme
|
|
791
797
|
});
|
|
792
798
|
if (requireId) config2.requireId = true;
|
|
@@ -820,20 +826,124 @@ async function init() {
|
|
|
820
826
|
if (autoPushChoice === "yes") config2.autoPush = true;
|
|
821
827
|
if (autoPushChoice === "no") config2.autoPush = false;
|
|
822
828
|
divider();
|
|
823
|
-
const autoStage = await
|
|
829
|
+
const autoStage = await select4({
|
|
824
830
|
message: "Commit \u65F6\u662F\u5426\u81EA\u52A8\u6682\u5B58\u6240\u6709\u66F4\u6539?",
|
|
825
|
-
|
|
831
|
+
choices: [
|
|
832
|
+
{ name: "\u662F", value: true },
|
|
833
|
+
{ name: "\u5426", value: false }
|
|
834
|
+
],
|
|
826
835
|
theme
|
|
827
836
|
});
|
|
828
837
|
if (!autoStage) config2.autoStage = false;
|
|
829
|
-
const useEmoji = await
|
|
838
|
+
const useEmoji = await select4({
|
|
830
839
|
message: "Commit \u65F6\u662F\u5426\u4F7F\u7528 emoji?",
|
|
831
|
-
|
|
840
|
+
choices: [
|
|
841
|
+
{ name: "\u662F", value: true },
|
|
842
|
+
{ name: "\u5426", value: false }
|
|
843
|
+
],
|
|
832
844
|
theme
|
|
833
845
|
});
|
|
834
846
|
if (!useEmoji) config2.useEmoji = false;
|
|
835
847
|
config2.commitEmojis = DEFAULT_COMMIT_EMOJIS;
|
|
836
848
|
divider();
|
|
849
|
+
console.log(
|
|
850
|
+
colors.dim("\nAI Commit \u914D\u7F6E (\u4F7F\u7528 AI \u81EA\u52A8\u751F\u6210 commit message)\n")
|
|
851
|
+
);
|
|
852
|
+
const enableAI = await select4({
|
|
853
|
+
message: "\u662F\u5426\u542F\u7528 AI Commit \u529F\u80FD?",
|
|
854
|
+
choices: [
|
|
855
|
+
{ name: "\u662F\uFF08\u63A8\u8350\uFF09", value: true },
|
|
856
|
+
{ name: "\u5426", value: false }
|
|
857
|
+
],
|
|
858
|
+
theme
|
|
859
|
+
});
|
|
860
|
+
if (enableAI) {
|
|
861
|
+
const aiProvider = await select4({
|
|
862
|
+
message: "\u9009\u62E9 AI \u63D0\u4F9B\u5546:",
|
|
863
|
+
choices: [
|
|
864
|
+
{
|
|
865
|
+
name: "GitHub Models\uFF08\u514D\u8D39\uFF0C\u63A8\u8350\uFF09",
|
|
866
|
+
value: "github",
|
|
867
|
+
description: "\u4F7F\u7528 GitHub \u8D26\u53F7\uFF0C\u6BCF\u5929 150 \u6B21\u514D\u8D39"
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
name: "Groq\uFF08\u514D\u8D39\uFF09",
|
|
871
|
+
value: "groq",
|
|
872
|
+
description: "\u9700\u8981\u6CE8\u518C\uFF0C\u6BCF\u5929 14,400 \u6B21\u514D\u8D39"
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
name: "OpenAI\uFF08\u4ED8\u8D39\uFF09",
|
|
876
|
+
value: "openai",
|
|
877
|
+
description: "\u9700\u8981\u4ED8\u8D39 API key"
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
name: "Claude\uFF08\u4ED8\u8D39\uFF09",
|
|
881
|
+
value: "claude",
|
|
882
|
+
description: "\u9700\u8981\u4ED8\u8D39 API key"
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
name: "Ollama\uFF08\u672C\u5730\uFF09",
|
|
886
|
+
value: "ollama",
|
|
887
|
+
description: "\u9700\u8981\u5B89\u88C5 Ollama"
|
|
888
|
+
}
|
|
889
|
+
],
|
|
890
|
+
theme
|
|
891
|
+
});
|
|
892
|
+
const useBuiltinKey = await select4({
|
|
893
|
+
message: "API Key \u914D\u7F6E:",
|
|
894
|
+
choices: [
|
|
895
|
+
{
|
|
896
|
+
name: "\u4F7F\u7528\u5185\u7F6E Key\uFF08\u5F00\u7BB1\u5373\u7528\uFF09",
|
|
897
|
+
value: true,
|
|
898
|
+
description: "\u4F7F\u7528\u5DE5\u5177\u5185\u7F6E\u7684 API key\uFF0C\u5171\u4EAB\u9650\u989D"
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
name: "\u4F7F\u7528\u81EA\u5DF1\u7684 Key\uFF08\u63A8\u8350\uFF09",
|
|
902
|
+
value: false,
|
|
903
|
+
description: "\u914D\u7F6E\u81EA\u5DF1\u7684 API key\uFF0C\u72EC\u4EAB\u9650\u989D"
|
|
904
|
+
}
|
|
905
|
+
],
|
|
906
|
+
theme
|
|
907
|
+
});
|
|
908
|
+
let apiKey = "";
|
|
909
|
+
if (!useBuiltinKey) {
|
|
910
|
+
apiKey = await input3({
|
|
911
|
+
message: `\u8F93\u5165\u4F60\u7684 ${aiProvider === "github" ? "GitHub Token" : "API Key"}:`,
|
|
912
|
+
validate: (value) => {
|
|
913
|
+
if (!value.trim()) return "API Key \u4E0D\u80FD\u4E3A\u7A7A";
|
|
914
|
+
return true;
|
|
915
|
+
},
|
|
916
|
+
theme
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
const language = await select4({
|
|
920
|
+
message: "\u751F\u6210\u7684 commit message \u8BED\u8A00:",
|
|
921
|
+
choices: [
|
|
922
|
+
{ name: "\u4E2D\u6587", value: "zh-CN" },
|
|
923
|
+
{ name: "English", value: "en-US" }
|
|
924
|
+
],
|
|
925
|
+
theme
|
|
926
|
+
});
|
|
927
|
+
config2.aiCommit = {
|
|
928
|
+
enabled: true,
|
|
929
|
+
provider: aiProvider,
|
|
930
|
+
apiKey: apiKey || void 0,
|
|
931
|
+
language
|
|
932
|
+
};
|
|
933
|
+
const defaultModels = {
|
|
934
|
+
github: "gpt-4o-mini",
|
|
935
|
+
groq: "llama-3.1-8b-instant",
|
|
936
|
+
openai: "gpt-4o-mini",
|
|
937
|
+
claude: "claude-3-haiku-20240307",
|
|
938
|
+
ollama: "qwen2.5-coder:7b"
|
|
939
|
+
};
|
|
940
|
+
config2.aiCommit.model = defaultModels[aiProvider];
|
|
941
|
+
} else {
|
|
942
|
+
config2.aiCommit = {
|
|
943
|
+
enabled: false
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
divider();
|
|
837
947
|
const content = JSON.stringify(config2, null, 2);
|
|
838
948
|
writeFileSync2(CONFIG_FILE, content + "\n");
|
|
839
949
|
console.log(colors.green(`\u2713 \u914D\u7F6E\u5DF2\u4FDD\u5B58\u5230 ${CONFIG_FILE}`));
|
|
@@ -842,6 +952,23 @@ async function init() {
|
|
|
842
952
|
"\n\u63D0\u793A: \u53EF\u4EE5\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u4FEE\u6539 commitEmojis \u6765\u81EA\u5B9A\u4E49\u5404\u7C7B\u578B\u7684 emoji"
|
|
843
953
|
)
|
|
844
954
|
);
|
|
955
|
+
if (config2.aiCommit?.enabled) {
|
|
956
|
+
console.log(
|
|
957
|
+
colors.dim(
|
|
958
|
+
"\u63D0\u793A: AI Commit \u5DF2\u542F\u7528\uFF0C\u8FD0\u884C 'gw c' \u65F6\u53EF\u4EE5\u9009\u62E9 AI \u81EA\u52A8\u751F\u6210 commit message"
|
|
959
|
+
)
|
|
960
|
+
);
|
|
961
|
+
if (!config2.aiCommit.apiKey) {
|
|
962
|
+
console.log(
|
|
963
|
+
colors.yellow(
|
|
964
|
+
"\n\u26A0\uFE0F \u5F53\u524D\u4F7F\u7528\u5185\u7F6E API key\uFF0C\u5EFA\u8BAE\u914D\u7F6E\u81EA\u5DF1\u7684 key \u4EE5\u83B7\u5F97\u66F4\u597D\u7684\u4F53\u9A8C"
|
|
965
|
+
)
|
|
966
|
+
);
|
|
967
|
+
console.log(
|
|
968
|
+
colors.dim(" \u83B7\u53D6\u65B9\u6CD5: https://github.com/settings/tokens/new")
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
845
972
|
console.log(colors.dim("\n" + content));
|
|
846
973
|
}
|
|
847
974
|
|
|
@@ -1081,8 +1208,254 @@ async function dropStash(index) {
|
|
|
1081
1208
|
|
|
1082
1209
|
// src/commands/commit.ts
|
|
1083
1210
|
import { execSync as execSync5 } from "child_process";
|
|
1084
|
-
import { select as select6, input as input5,
|
|
1211
|
+
import { select as select6, input as input5, checkbox } from "@inquirer/prompts";
|
|
1085
1212
|
import ora4 from "ora";
|
|
1213
|
+
|
|
1214
|
+
// src/ai-service.ts
|
|
1215
|
+
var AI_PROVIDERS = {
|
|
1216
|
+
github: {
|
|
1217
|
+
name: "GitHub Models",
|
|
1218
|
+
endpoint: "https://models.github.ai/inference/chat/completions",
|
|
1219
|
+
defaultModel: "gpt-4o-mini",
|
|
1220
|
+
free: true,
|
|
1221
|
+
needsKey: true
|
|
1222
|
+
},
|
|
1223
|
+
groq: {
|
|
1224
|
+
name: "Groq",
|
|
1225
|
+
endpoint: "https://api.groq.com/openai/v1/chat/completions",
|
|
1226
|
+
defaultModel: "llama-3.1-8b-instant",
|
|
1227
|
+
free: true,
|
|
1228
|
+
needsKey: true
|
|
1229
|
+
},
|
|
1230
|
+
openai: {
|
|
1231
|
+
name: "OpenAI",
|
|
1232
|
+
endpoint: "https://api.openai.com/v1/chat/completions",
|
|
1233
|
+
defaultModel: "gpt-4o-mini",
|
|
1234
|
+
free: false,
|
|
1235
|
+
needsKey: true
|
|
1236
|
+
},
|
|
1237
|
+
claude: {
|
|
1238
|
+
name: "Claude",
|
|
1239
|
+
endpoint: "https://api.anthropic.com/v1/messages",
|
|
1240
|
+
defaultModel: "claude-3-haiku-20240307",
|
|
1241
|
+
free: false,
|
|
1242
|
+
needsKey: true
|
|
1243
|
+
},
|
|
1244
|
+
ollama: {
|
|
1245
|
+
name: "Ollama",
|
|
1246
|
+
endpoint: "http://localhost:11434/api/generate",
|
|
1247
|
+
defaultModel: "qwen2.5-coder:7b",
|
|
1248
|
+
free: true,
|
|
1249
|
+
needsKey: false
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
function getGitDiff() {
|
|
1253
|
+
try {
|
|
1254
|
+
const diff = execOutput("git diff --cached");
|
|
1255
|
+
if (!diff) {
|
|
1256
|
+
return execOutput("git diff");
|
|
1257
|
+
}
|
|
1258
|
+
return diff;
|
|
1259
|
+
} catch {
|
|
1260
|
+
return "";
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
function buildPrompt(diff, language) {
|
|
1264
|
+
const isZh = language === "zh-CN";
|
|
1265
|
+
const systemPrompt = isZh ? `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684 Git commit message \u751F\u6210\u52A9\u624B\u3002\u8BF7\u6839\u636E\u63D0\u4F9B\u7684 git diff \u751F\u6210\u7B26\u5408 Conventional Commits \u89C4\u8303\u7684 commit message\u3002
|
|
1266
|
+
|
|
1267
|
+
\u89C4\u5219\uFF1A
|
|
1268
|
+
1. \u683C\u5F0F\uFF1A<type>(<scope>): <subject>
|
|
1269
|
+
2. type \u5FC5\u987B\u662F\u4EE5\u4E0B\u4E4B\u4E00\uFF1Afeat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
1270
|
+
3. scope \u662F\u53EF\u9009\u7684\uFF0C\u8868\u793A\u5F71\u54CD\u8303\u56F4
|
|
1271
|
+
4. subject \u7528\u4E2D\u6587\u63CF\u8FF0\uFF0C\u7B80\u6D01\u660E\u4E86\uFF0C\u4E0D\u8D85\u8FC7 50 \u5B57
|
|
1272
|
+
5. \u53EA\u8FD4\u56DE commit message\uFF0C\u4E0D\u8981\u6709\u5176\u4ED6\u89E3\u91CA
|
|
1273
|
+
|
|
1274
|
+
\u793A\u4F8B\uFF1A
|
|
1275
|
+
- feat(auth): \u6DFB\u52A0\u7528\u6237\u767B\u5F55\u529F\u80FD
|
|
1276
|
+
- fix(api): \u4FEE\u590D\u6570\u636E\u83B7\u53D6\u5931\u8D25\u7684\u95EE\u9898
|
|
1277
|
+
- docs(readme): \u66F4\u65B0\u5B89\u88C5\u8BF4\u660E` : `You are a professional Git commit message generator. Generate a commit message following Conventional Commits specification based on the provided git diff.
|
|
1278
|
+
|
|
1279
|
+
Rules:
|
|
1280
|
+
1. Format: <type>(<scope>): <subject>
|
|
1281
|
+
2. type must be one of: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
1282
|
+
3. scope is optional, indicates the affected area
|
|
1283
|
+
4. subject should be concise, no more than 50 characters
|
|
1284
|
+
5. Return only the commit message, no explanations
|
|
1285
|
+
|
|
1286
|
+
Examples:
|
|
1287
|
+
- feat(auth): add user login functionality
|
|
1288
|
+
- fix(api): resolve data fetching failure
|
|
1289
|
+
- docs(readme): update installation guide`;
|
|
1290
|
+
const userPrompt = isZh ? `\u8BF7\u6839\u636E\u4EE5\u4E0B git diff \u751F\u6210 commit message\uFF1A
|
|
1291
|
+
|
|
1292
|
+
${diff}` : `Generate a commit message based on the following git diff:
|
|
1293
|
+
|
|
1294
|
+
${diff}`;
|
|
1295
|
+
return `${systemPrompt}
|
|
1296
|
+
|
|
1297
|
+
${userPrompt}`;
|
|
1298
|
+
}
|
|
1299
|
+
async function callGitHubAPI(prompt, apiKey, model, maxTokens) {
|
|
1300
|
+
const response = await fetch(AI_PROVIDERS.github.endpoint, {
|
|
1301
|
+
method: "POST",
|
|
1302
|
+
headers: {
|
|
1303
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1304
|
+
"Content-Type": "application/json"
|
|
1305
|
+
},
|
|
1306
|
+
body: JSON.stringify({
|
|
1307
|
+
model,
|
|
1308
|
+
messages: [{ role: "user", content: prompt }],
|
|
1309
|
+
max_tokens: maxTokens,
|
|
1310
|
+
temperature: 0.3
|
|
1311
|
+
})
|
|
1312
|
+
});
|
|
1313
|
+
if (!response.ok) {
|
|
1314
|
+
const error = await response.text();
|
|
1315
|
+
throw new Error(`GitHub Models API \u9519\u8BEF: ${response.status} ${error}`);
|
|
1316
|
+
}
|
|
1317
|
+
const data = await response.json();
|
|
1318
|
+
return data.choices[0]?.message?.content?.trim() || "";
|
|
1319
|
+
}
|
|
1320
|
+
async function callGroqAPI(prompt, apiKey, model, maxTokens) {
|
|
1321
|
+
const response = await fetch(AI_PROVIDERS.groq.endpoint, {
|
|
1322
|
+
method: "POST",
|
|
1323
|
+
headers: {
|
|
1324
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1325
|
+
"Content-Type": "application/json"
|
|
1326
|
+
},
|
|
1327
|
+
body: JSON.stringify({
|
|
1328
|
+
model,
|
|
1329
|
+
messages: [{ role: "user", content: prompt }],
|
|
1330
|
+
max_tokens: maxTokens,
|
|
1331
|
+
temperature: 0.3
|
|
1332
|
+
})
|
|
1333
|
+
});
|
|
1334
|
+
if (!response.ok) {
|
|
1335
|
+
const error = await response.text();
|
|
1336
|
+
throw new Error(`Groq API \u9519\u8BEF: ${response.status} ${error}`);
|
|
1337
|
+
}
|
|
1338
|
+
const data = await response.json();
|
|
1339
|
+
return data.choices[0]?.message?.content?.trim() || "";
|
|
1340
|
+
}
|
|
1341
|
+
async function callOpenAIAPI(prompt, apiKey, model, maxTokens) {
|
|
1342
|
+
const response = await fetch(AI_PROVIDERS.openai.endpoint, {
|
|
1343
|
+
method: "POST",
|
|
1344
|
+
headers: {
|
|
1345
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1346
|
+
"Content-Type": "application/json"
|
|
1347
|
+
},
|
|
1348
|
+
body: JSON.stringify({
|
|
1349
|
+
model,
|
|
1350
|
+
messages: [{ role: "user", content: prompt }],
|
|
1351
|
+
max_tokens: maxTokens,
|
|
1352
|
+
temperature: 0.3
|
|
1353
|
+
})
|
|
1354
|
+
});
|
|
1355
|
+
if (!response.ok) {
|
|
1356
|
+
const error = await response.text();
|
|
1357
|
+
throw new Error(`OpenAI API \u9519\u8BEF: ${response.status} ${error}`);
|
|
1358
|
+
}
|
|
1359
|
+
const data = await response.json();
|
|
1360
|
+
return data.choices[0]?.message?.content?.trim() || "";
|
|
1361
|
+
}
|
|
1362
|
+
async function callClaudeAPI(prompt, apiKey, model, maxTokens) {
|
|
1363
|
+
const response = await fetch(AI_PROVIDERS.claude.endpoint, {
|
|
1364
|
+
method: "POST",
|
|
1365
|
+
headers: {
|
|
1366
|
+
"x-api-key": apiKey,
|
|
1367
|
+
"anthropic-version": "2023-06-01",
|
|
1368
|
+
"Content-Type": "application/json"
|
|
1369
|
+
},
|
|
1370
|
+
body: JSON.stringify({
|
|
1371
|
+
model,
|
|
1372
|
+
messages: [{ role: "user", content: prompt }],
|
|
1373
|
+
max_tokens: maxTokens,
|
|
1374
|
+
temperature: 0.3
|
|
1375
|
+
})
|
|
1376
|
+
});
|
|
1377
|
+
if (!response.ok) {
|
|
1378
|
+
const error = await response.text();
|
|
1379
|
+
throw new Error(`Claude API \u9519\u8BEF: ${response.status} ${error}`);
|
|
1380
|
+
}
|
|
1381
|
+
const data = await response.json();
|
|
1382
|
+
return data.content[0]?.text?.trim() || "";
|
|
1383
|
+
}
|
|
1384
|
+
async function callOllamaAPI(prompt, model, maxTokens) {
|
|
1385
|
+
try {
|
|
1386
|
+
const response = await fetch(AI_PROVIDERS.ollama.endpoint, {
|
|
1387
|
+
method: "POST",
|
|
1388
|
+
headers: { "Content-Type": "application/json" },
|
|
1389
|
+
body: JSON.stringify({
|
|
1390
|
+
model,
|
|
1391
|
+
prompt,
|
|
1392
|
+
stream: false,
|
|
1393
|
+
options: {
|
|
1394
|
+
num_predict: maxTokens,
|
|
1395
|
+
temperature: 0.3
|
|
1396
|
+
}
|
|
1397
|
+
})
|
|
1398
|
+
});
|
|
1399
|
+
if (!response.ok) {
|
|
1400
|
+
throw new Error(`Ollama \u672A\u8FD0\u884C\u6216\u6A21\u578B\u672A\u5B89\u88C5`);
|
|
1401
|
+
}
|
|
1402
|
+
const data = await response.json();
|
|
1403
|
+
return data.response?.trim() || "";
|
|
1404
|
+
} catch (error) {
|
|
1405
|
+
throw new Error(
|
|
1406
|
+
`Ollama \u8FDE\u63A5\u5931\u8D25\u3002\u8BF7\u786E\u4FDD\uFF1A
|
|
1407
|
+
1. \u5DF2\u5B89\u88C5 Ollama (https://ollama.com)
|
|
1408
|
+
2. \u8FD0\u884C 'ollama serve'
|
|
1409
|
+
3. \u4E0B\u8F7D\u6A21\u578B 'ollama pull ${model}'`
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
async function generateAICommitMessage(config2) {
|
|
1414
|
+
const aiConfig = config2.aiCommit || {};
|
|
1415
|
+
const provider = aiConfig.provider || "groq";
|
|
1416
|
+
const language = aiConfig.language || "zh-CN";
|
|
1417
|
+
const maxTokens = aiConfig.maxTokens || 200;
|
|
1418
|
+
const diff = getGitDiff();
|
|
1419
|
+
if (!diff) {
|
|
1420
|
+
throw new Error("\u6CA1\u6709\u68C0\u6D4B\u5230\u4EE3\u7801\u66F4\u6539");
|
|
1421
|
+
}
|
|
1422
|
+
const maxDiffLength = 4e3;
|
|
1423
|
+
const truncatedDiff = diff.length > maxDiffLength ? diff.slice(0, maxDiffLength) + "\n..." : diff;
|
|
1424
|
+
const prompt = buildPrompt(truncatedDiff, language);
|
|
1425
|
+
const providerInfo = AI_PROVIDERS[provider];
|
|
1426
|
+
if (!providerInfo) {
|
|
1427
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684 AI \u63D0\u4F9B\u5546: ${provider}`);
|
|
1428
|
+
}
|
|
1429
|
+
const model = aiConfig.model || providerInfo.defaultModel;
|
|
1430
|
+
const apiKey = aiConfig.apiKey || "";
|
|
1431
|
+
if (providerInfo.needsKey && !apiKey) {
|
|
1432
|
+
throw new Error(
|
|
1433
|
+
`${providerInfo.name} \u9700\u8981 API key\u3002\u8BF7\u8FD0\u884C 'gw init' \u914D\u7F6E AI commit\uFF0C\u6216\u5728 .gwrc.json \u4E2D\u8BBE\u7F6E aiCommit.apiKey`
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
switch (provider) {
|
|
1437
|
+
case "github":
|
|
1438
|
+
return await callGitHubAPI(prompt, apiKey, model, maxTokens);
|
|
1439
|
+
case "groq":
|
|
1440
|
+
return await callGroqAPI(prompt, apiKey, model, maxTokens);
|
|
1441
|
+
case "openai":
|
|
1442
|
+
return await callOpenAIAPI(prompt, apiKey, model, maxTokens);
|
|
1443
|
+
case "claude":
|
|
1444
|
+
return await callClaudeAPI(prompt, apiKey, model, maxTokens);
|
|
1445
|
+
case "ollama":
|
|
1446
|
+
return await callOllamaAPI(prompt, model, maxTokens);
|
|
1447
|
+
default:
|
|
1448
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684 AI \u63D0\u4F9B\u5546: ${provider}`);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
function isAICommitAvailable(config2) {
|
|
1452
|
+
const aiConfig = config2.aiCommit;
|
|
1453
|
+
if (!aiConfig) return true;
|
|
1454
|
+
if (aiConfig.enabled === false) return false;
|
|
1455
|
+
return true;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/commands/commit.ts
|
|
1086
1459
|
var DEFAULT_COMMIT_TYPES = [
|
|
1087
1460
|
{ type: "feat", emoji: "\u2728", description: "\u65B0\u529F\u80FD" },
|
|
1088
1461
|
{ type: "fix", emoji: "\u{1F41B}", description: "\u4FEE\u590D Bug" },
|
|
@@ -1185,6 +1558,94 @@ async function commit() {
|
|
|
1185
1558
|
}
|
|
1186
1559
|
divider();
|
|
1187
1560
|
}
|
|
1561
|
+
const aiAvailable = isAICommitAvailable(config2);
|
|
1562
|
+
let commitMode = "manual";
|
|
1563
|
+
if (aiAvailable) {
|
|
1564
|
+
commitMode = await select6({
|
|
1565
|
+
message: "\u9009\u62E9 commit \u65B9\u5F0F:",
|
|
1566
|
+
choices: [
|
|
1567
|
+
{
|
|
1568
|
+
name: "\u{1F916} AI \u81EA\u52A8\u751F\u6210 commit message",
|
|
1569
|
+
value: "ai",
|
|
1570
|
+
description: "\u4F7F\u7528 AI \u5206\u6790\u4EE3\u7801\u53D8\u66F4\u81EA\u52A8\u751F\u6210"
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
name: "\u270D\uFE0F \u624B\u52A8\u7F16\u5199 commit message",
|
|
1574
|
+
value: "manual",
|
|
1575
|
+
description: "\u4F20\u7EDF\u7684\u4EA4\u4E92\u5F0F\u8F93\u5165\u65B9\u5F0F"
|
|
1576
|
+
}
|
|
1577
|
+
],
|
|
1578
|
+
theme
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
let message;
|
|
1582
|
+
if (commitMode === "ai") {
|
|
1583
|
+
const spinner2 = ora4("AI \u6B63\u5728\u5206\u6790\u4EE3\u7801\u53D8\u66F4...").start();
|
|
1584
|
+
try {
|
|
1585
|
+
const aiMessage = await generateAICommitMessage(config2);
|
|
1586
|
+
spinner2.succeed("AI \u751F\u6210\u5B8C\u6210");
|
|
1587
|
+
console.log("");
|
|
1588
|
+
console.log("AI \u751F\u6210\u7684 commit message:");
|
|
1589
|
+
console.log(colors.green(aiMessage));
|
|
1590
|
+
divider();
|
|
1591
|
+
const useAI = await select6({
|
|
1592
|
+
message: "\u4F7F\u7528\u8FD9\u4E2A commit message?",
|
|
1593
|
+
choices: [
|
|
1594
|
+
{ name: "\u2705 \u4F7F\u7528", value: true },
|
|
1595
|
+
{ name: "\u274C \u4E0D\u4F7F\u7528\uFF0C\u5207\u6362\u5230\u624B\u52A8\u6A21\u5F0F", value: false }
|
|
1596
|
+
],
|
|
1597
|
+
theme
|
|
1598
|
+
});
|
|
1599
|
+
if (useAI) {
|
|
1600
|
+
message = aiMessage;
|
|
1601
|
+
} else {
|
|
1602
|
+
spinner2.info("\u5207\u6362\u5230\u624B\u52A8\u6A21\u5F0F");
|
|
1603
|
+
commitMode = "manual";
|
|
1604
|
+
}
|
|
1605
|
+
} catch (error) {
|
|
1606
|
+
spinner2.fail("AI \u751F\u6210\u5931\u8D25");
|
|
1607
|
+
console.log(
|
|
1608
|
+
colors.red(error instanceof Error ? error.message : String(error))
|
|
1609
|
+
);
|
|
1610
|
+
console.log(colors.yellow("\n\u5207\u6362\u5230\u624B\u52A8\u6A21\u5F0F..."));
|
|
1611
|
+
divider();
|
|
1612
|
+
commitMode = "manual";
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
if (commitMode === "manual") {
|
|
1616
|
+
message = await buildManualCommitMessage(config2);
|
|
1617
|
+
}
|
|
1618
|
+
divider();
|
|
1619
|
+
console.log("\u63D0\u4EA4\u4FE1\u606F\u9884\u89C8:");
|
|
1620
|
+
console.log(colors.green(message));
|
|
1621
|
+
divider();
|
|
1622
|
+
const shouldCommit = await select6({
|
|
1623
|
+
message: "\u786E\u8BA4\u63D0\u4EA4?",
|
|
1624
|
+
choices: [
|
|
1625
|
+
{ name: "\u2705 \u786E\u8BA4\u63D0\u4EA4", value: true },
|
|
1626
|
+
{ name: "\u274C \u53D6\u6D88", value: false }
|
|
1627
|
+
],
|
|
1628
|
+
theme
|
|
1629
|
+
});
|
|
1630
|
+
if (!shouldCommit) {
|
|
1631
|
+
console.log(colors.yellow("\u5DF2\u53D6\u6D88"));
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
const spinner = ora4("\u6B63\u5728\u63D0\u4EA4...").start();
|
|
1635
|
+
try {
|
|
1636
|
+
const escapedMessage = message.replace(/"/g, '\\"');
|
|
1637
|
+
execSync5(`git commit -m "${escapedMessage}"`, { stdio: "pipe" });
|
|
1638
|
+
spinner.succeed("\u63D0\u4EA4\u6210\u529F");
|
|
1639
|
+
const commitHash = execOutput("git rev-parse --short HEAD");
|
|
1640
|
+
console.log(colors.dim(`commit: ${commitHash}`));
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
spinner.fail("\u63D0\u4EA4\u5931\u8D25");
|
|
1643
|
+
if (error instanceof Error) {
|
|
1644
|
+
console.log(colors.red(error.message));
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
async function buildManualCommitMessage(config2) {
|
|
1188
1649
|
const commitTypes = getCommitTypes(config2);
|
|
1189
1650
|
const typeChoice = await select6({
|
|
1190
1651
|
message: "\u9009\u62E9\u63D0\u4EA4\u7C7B\u578B:",
|
|
@@ -1211,9 +1672,12 @@ async function commit() {
|
|
|
1211
1672
|
message: "\u8F93\u5165\u8BE6\u7EC6\u63CF\u8FF0 (\u53EF\u8DF3\u8FC7):",
|
|
1212
1673
|
theme
|
|
1213
1674
|
});
|
|
1214
|
-
const hasBreaking = await
|
|
1675
|
+
const hasBreaking = await select6({
|
|
1215
1676
|
message: "\u662F\u5426\u5305\u542B\u7834\u574F\u6027\u53D8\u66F4 (BREAKING CHANGE)?",
|
|
1216
|
-
|
|
1677
|
+
choices: [
|
|
1678
|
+
{ name: "\u5426", value: false },
|
|
1679
|
+
{ name: "\u662F", value: true }
|
|
1680
|
+
],
|
|
1217
1681
|
theme
|
|
1218
1682
|
});
|
|
1219
1683
|
let breakingDesc = "";
|
|
@@ -1251,32 +1715,7 @@ BREAKING CHANGE: ${breakingDesc}`;
|
|
|
1251
1715
|
${issues}`;
|
|
1252
1716
|
}
|
|
1253
1717
|
}
|
|
1254
|
-
|
|
1255
|
-
console.log("\u63D0\u4EA4\u4FE1\u606F\u9884\u89C8:");
|
|
1256
|
-
console.log(colors.green(message));
|
|
1257
|
-
divider();
|
|
1258
|
-
const shouldCommit = await confirm2({
|
|
1259
|
-
message: "\u786E\u8BA4\u63D0\u4EA4?",
|
|
1260
|
-
default: true,
|
|
1261
|
-
theme
|
|
1262
|
-
});
|
|
1263
|
-
if (!shouldCommit) {
|
|
1264
|
-
console.log(colors.yellow("\u5DF2\u53D6\u6D88"));
|
|
1265
|
-
return;
|
|
1266
|
-
}
|
|
1267
|
-
const spinner = ora4("\u6B63\u5728\u63D0\u4EA4...").start();
|
|
1268
|
-
try {
|
|
1269
|
-
const escapedMessage = message.replace(/"/g, '\\"');
|
|
1270
|
-
execSync5(`git commit -m "${escapedMessage}"`, { stdio: "pipe" });
|
|
1271
|
-
spinner.succeed("\u63D0\u4EA4\u6210\u529F");
|
|
1272
|
-
const commitHash = execOutput("git rev-parse --short HEAD");
|
|
1273
|
-
console.log(colors.dim(`commit: ${commitHash}`));
|
|
1274
|
-
} catch (error) {
|
|
1275
|
-
spinner.fail("\u63D0\u4EA4\u5931\u8D25");
|
|
1276
|
-
if (error instanceof Error) {
|
|
1277
|
-
console.log(colors.red(error.message));
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1718
|
+
return message;
|
|
1280
1719
|
}
|
|
1281
1720
|
|
|
1282
1721
|
// src/commands/help.ts
|
|
@@ -1488,12 +1927,29 @@ function writeCache(cache) {
|
|
|
1488
1927
|
// src/index.ts
|
|
1489
1928
|
process.on("uncaughtException", (err) => {
|
|
1490
1929
|
if (err instanceof ExitPromptError) {
|
|
1930
|
+
console.log("");
|
|
1491
1931
|
process.exit(0);
|
|
1492
1932
|
}
|
|
1493
1933
|
console.error(err);
|
|
1494
1934
|
process.exit(1);
|
|
1495
1935
|
});
|
|
1496
|
-
|
|
1936
|
+
process.on("unhandledRejection", (reason) => {
|
|
1937
|
+
if (reason instanceof ExitPromptError) {
|
|
1938
|
+
console.log("");
|
|
1939
|
+
process.exit(0);
|
|
1940
|
+
}
|
|
1941
|
+
console.error("\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD:", reason);
|
|
1942
|
+
process.exit(1);
|
|
1943
|
+
});
|
|
1944
|
+
process.on("SIGINT", () => {
|
|
1945
|
+
console.log("");
|
|
1946
|
+
process.exit(0);
|
|
1947
|
+
});
|
|
1948
|
+
process.on("SIGTERM", () => {
|
|
1949
|
+
console.log("");
|
|
1950
|
+
process.exit(0);
|
|
1951
|
+
});
|
|
1952
|
+
var version = true ? "0.2.6" : "0.0.0-dev";
|
|
1497
1953
|
async function mainMenu() {
|
|
1498
1954
|
await checkForUpdates(version, "@zjex/git-workflow");
|
|
1499
1955
|
console.log(
|