forked from insulineru/ai-commit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
executable file
Β·205 lines (156 loc) Β· 6.37 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#!/usr/bin/env node
'use strict'
import { execSync } from "child_process";
import { ChatGPTAPI } from "chatgpt";
import inquirer from "inquirer";
import { getArgs, checkGitRepository } from "./helpers.js";
import { addGitmojiToCommitMessage } from './gitmoji.js';
import { filterApi } from "./filterApi.js";
import * as dotenv from 'dotenv';
dotenv.config();
const args = getArgs();
const REGENERATE_MSG = "β»οΈ Regenerate Commit Messages";
const apiKey = args.apiKey || process.env.OPENAI_API_KEY;
if (!apiKey) {
console.error("Please set the OPENAI_API_KEY environment variable.");
process.exit(1);
}
let template = args.template || process.env.AI_COMMIT_COMMIT_TEMPLATE
const doAddEmoji = args.emoji || process.env.AI_COMMIT_ADD_EMOJI
const api = new ChatGPTAPI({
apiKey,
});
const processTemplate = ({template, commitMessage}) => {
if(!template.includes('COMMIT_MESSAGE')) {
console.log(`Warning: template doesn't include {COMMIT_MESSAGE}`)
return commitMessage;
}
let finalCommitMessage = template.replaceAll("{COMMIT_MESSAGE}", commitMessage);
if(finalCommitMessage.includes('GIT_BRANCH')) {
const currentBranch = execSync("git branch --show-current").toString().replaceAll("\n", "");
console.log('Using currentBranch: ', currentBranch);
finalCommitMessage = finalCommitMessage.replaceAll("{GIT_BRANCH}", currentBranch)
}
return finalCommitMessage;
}
const makeCommit = (input) => {
console.log("Committing Message... π ");
execSync(`git commit -F -`, { input });
console.log("Commit Successful! π");
};
const processEmoji = (msg, doAddEmoji) => {
if(doAddEmoji) {
return addGitmojiToCommitMessage(msg);
}
return msg;
}
const generateSingleCommit = async (diff) => {
let prompt =
`Here are some best practices for writing commit messages:
- Write clear, concise, and descriptive messages that explain the changes made in the commit.
- Use the present tense and active voice in the message, for example, "Fix bug" instead of "Fixed bug."
- Use the imperative mood, which gives the message a sense of command, e.g. "Add feature" instead of "Added feature"
- Limit the subject line to 72 characters or less.
- Capitalize the subject line.
- Do not end the subject line with a period.
- The subject line should begin with a conventional commit type, followed by a colon.
- Conventional commit values are limited to the following: feat, fix, docs, test, refactor, ci, style, chore
- The subject line shall include a scope, which is placed inside parentheses, e.g. "feat(parser): add ability to parse arrays."
- Limit the body of the message to 256 characters or less.
- Use a blank line between the subject and the body of the message.
- Use the body of the message to provide additional context or explain the reasoning behind the changes.
- Avoid using general terms like "update" or "change" in the subject line, be specific about what was updated or changed.
- Explain, What was done at a glance in the subject line, and provide additional context in the body of the message.
- Why the change was necessary in the body of the message.
- The details about what was done in the body of the message.
- Any useful details concerning the change in the body of the message.
- Use a hyphen (-) for the bullet points in the body of the message.
Write a commit message that accurately summarizes the changes made in the given 'git diff' output, following the best practices listed above and the conventional commit format.
Here is the output of the 'git diff --staged'. Focus on the lines that indicate changes:
` diff;
if (args.hint !== undefined) {
prompt = `\n\nHere is context on why the change was made: ${args.hint}`;
}
if (!await filterApi({ prompt, filterFee: args['filter-fee'] })) process.exit(1);
const { text } = await api.sendMessage(prompt);
let finalCommitMessage = processEmoji(text, args.emoji);
if(args.template){
finalCommitMessage = processTemplate({
template: args.template,
commitMessage: finalCommitMessage,
})
console.log(
`Proposed Commit With Template:\n------------------------------\n${finalCommitMessage}\n------------------------------`
);
} else {
console.log(
`Proposed Commit:\n------------------------------\n${finalCommitMessage}\n------------------------------`
);
}
if (args.force) {
makeCommit(finalCommitMessage);
return;
}
const answer = await inquirer.prompt([
{
type: "confirm",
name: "continue",
message: "Do you want to continue?",
default: true,
},
]);
if (!answer.continue) {
console.log("Commit aborted by user π
ββοΈ");
process.exit(1);
}
makeCommit(finalCommitMessage);
};
const generateListCommits = async (diff, numOptions = 5) => {
const prompt =
"I want you to act as the author of a commit message in git."
`I'll enter a git diff, and your job is to convert it into a useful commit message and make ${numOptions} options that are separated by ";".`
"For each option, use the present tense, return the full sentence, and use the conventional commits specification (<type in lowercase>: <subject>):"
diff;
if (!await filterApi({ prompt, filterFee: args['filter-fee'], numCompletion: numOptions })) process.exit(1);
const { text } = await api.sendMessage(prompt);
let msgs = text.split(";").map((msg) => msg.trim()).map(msg => processEmoji(msg, args.emoji));
if(args.template) {
msgs = msgs.map(msg => processTemplate({
template: args.template,
commitMessage: msg,
}))
}
// add regenerate option
msgs.push(REGENERATE_MSG);
const answer = await inquirer.prompt([
{
type: "list",
name: "commit",
message: "Select a commit message",
choices: msgs,
},
]);
if (answer.commit === REGENERATE_MSG) {
await generateListCommits(diff);
return;
}
makeCommit(answer.commit);
};
async function generateAICommit() {
const isGitRepository = checkGitRepository();
if (!isGitRepository) {
console.error("This is not a git repository π
ββοΈ");
process.exit(1);
}
const diff = execSync("git diff --staged").toString();
// Handle empty diff
if (!diff) {
console.log("No changes to commit π
");
console.log(
"May be you forgot to add the files? Try git add . and then run this script again."
);
process.exit(1);
}
await generateSingleCommit(diff);
}
await generateAICommit();