2024-02-19 20:37:09 +01:00
|
|
|
|
#!/usr/bin/env nodejsscript
|
|
|
|
|
/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */
|
|
|
|
|
$.is_fatal= true;
|
|
|
|
|
const token_file= "~/.config/openai.token";
|
|
|
|
|
let token;
|
|
|
|
|
const gitmoji_list= {
|
|
|
|
|
build: "building_construction",
|
|
|
|
|
chore: "bricks",
|
|
|
|
|
ci: "construction_worker",
|
|
|
|
|
docs: "memo",
|
|
|
|
|
feat: "sparkles",
|
|
|
|
|
fix: "bug",
|
|
|
|
|
perf: "zap",
|
|
|
|
|
refactor: "recycle",
|
|
|
|
|
revert: "rewind",
|
|
|
|
|
style: "art",
|
|
|
|
|
test: "white_check_mark"
|
|
|
|
|
};
|
|
|
|
|
const git3moji_list= {
|
|
|
|
|
build: "tv",
|
|
|
|
|
chore: "tv",
|
|
|
|
|
ci: "tv",
|
|
|
|
|
docs: "abc",
|
|
|
|
|
feat: "zap",
|
|
|
|
|
fix: "bug",
|
|
|
|
|
perf: "zap",
|
|
|
|
|
refactor: "cop",
|
|
|
|
|
style: "zap",
|
|
|
|
|
test: "cop"
|
|
|
|
|
};
|
|
|
|
|
const conventional_desc= [
|
|
|
|
|
"Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)",
|
|
|
|
|
"Other changes that don't modify src or test files",
|
|
|
|
|
"Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)",
|
|
|
|
|
"Documentation only changes",
|
|
|
|
|
"A new feature",
|
|
|
|
|
"A bug Fix",
|
|
|
|
|
"A code change that improves performance",
|
|
|
|
|
"A code change that neither fixes a bug nor adds a feature",
|
|
|
|
|
"Reverts a previous commit",
|
|
|
|
|
"Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)",
|
|
|
|
|
"Adding missing tests or correcting existing tests"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$.api("", true)
|
2024-02-28 11:22:33 +01:00
|
|
|
|
.version("2024-02-28")
|
2024-02-19 20:37:09 +01:00
|
|
|
|
.describe([
|
|
|
|
|
"Utility to use ChatGPT to generate a commit message from COMMIT_EDITMSG file.",
|
|
|
|
|
`Don't forget to set the token in ${token_file} file.`,
|
|
|
|
|
"",
|
|
|
|
|
"Conventional/gitmoji commits should follow https://www.conventionalcommits.org/en/v1.0.0/ (https://github.com/pvdlg/conventional-commit-types).",
|
|
|
|
|
"For the gitmoji the <type> uses emojis as follows:",
|
|
|
|
|
...Object.entries(gitmoji_list).map(( v, i )=> echo.format("%c"+v[0]+` (${v[1]}): ${conventional_desc[i]}`, "display: list-item")),
|
|
|
|
|
...Object.entries(git3moji_list).map(( v, i )=> echo.format("%c"+v[0]+` (${v[1]}): ${conventional_desc[i]}`, "display: list-item"))
|
|
|
|
|
])
|
|
|
|
|
.option("--format, -f", [ "Use one of the following formats to generate the commit message: [regular (default), conventional, gitmoji, git3moji]",
|
|
|
|
|
"For gitmoji see: https://gitmoji.dev/"
|
|
|
|
|
])
|
|
|
|
|
.action(async function({ format= "regular" }= {}){
|
|
|
|
|
const question= questionChatGPT(format);
|
|
|
|
|
const response= (await pipe(
|
|
|
|
|
()=> s.cat("./.git/COMMIT_EDITMSG"),
|
|
|
|
|
s=> s.slice(s.indexOf("diff --git")),
|
|
|
|
|
diffToChunks(3900-545), //the worst scenario of ★ +new lines
|
|
|
|
|
ch=> ch.map(pipe( question, requestCommitMessage )),
|
|
|
|
|
ch=> Promise.all(ch)
|
|
|
|
|
)())
|
|
|
|
|
.map(pipe(
|
|
|
|
|
j=> j.choices[0].text.trim(),
|
2024-02-28 11:22:33 +01:00
|
|
|
|
convertToArray,
|
2024-05-12 10:19:02 +02:00
|
|
|
|
format==="regular" || format==="conventional" ? i=> i : i=> gitmoji(i, format==="git3moji"),
|
2024-02-19 20:37:09 +01:00
|
|
|
|
a=> a.join("\n")
|
|
|
|
|
))
|
|
|
|
|
.join("\n\n");
|
|
|
|
|
echo(response);
|
|
|
|
|
$.exit(0);
|
|
|
|
|
})
|
|
|
|
|
.parse();
|
|
|
|
|
|
|
|
|
|
function diffToChunks(max_tokens){ return function(input){
|
|
|
|
|
if(input.length < max_tokens)
|
|
|
|
|
return [ input ];
|
|
|
|
|
|
|
|
|
|
return input.split(/(?=diff --git)/g)
|
|
|
|
|
.flatMap(function(input){
|
|
|
|
|
if(input.length < max_tokens)
|
|
|
|
|
return [ input ];
|
|
|
|
|
|
|
|
|
|
const [ file, ...diffs ]= input.split(/\n(?=@@)/g);
|
|
|
|
|
if(file.includes("new file"))
|
|
|
|
|
return [ file ];
|
|
|
|
|
return diffs
|
|
|
|
|
.filter(chunk=> chunk.length < max_tokens)
|
|
|
|
|
.reduce(function(chunks, chunk){
|
|
|
|
|
const i= chunks.length-1;
|
|
|
|
|
if(chunks[i].length + chunk.length < max_tokens-1)
|
|
|
|
|
chunks[i]+= "\n"+chunk;
|
|
|
|
|
else
|
|
|
|
|
chunks.push(file+"\n"+chunk);
|
|
|
|
|
return chunks;
|
|
|
|
|
}, [ file ])
|
|
|
|
|
.filter(chunk=> chunk.length < max_tokens);
|
|
|
|
|
});
|
|
|
|
|
}; }
|
2024-02-28 11:22:33 +01:00
|
|
|
|
function convertToArray(text){
|
|
|
|
|
// console.log(text);
|
|
|
|
|
return text.split("\n")
|
|
|
|
|
.map(line=> line.trim())
|
|
|
|
|
.filter(line=> line.trim())
|
|
|
|
|
.map(function(line){
|
|
|
|
|
if(/^[0-9-].? /.test(line)) line= line.slice(line.indexOf(" ")+1);
|
|
|
|
|
if(/^["']/.test(line[0])) line= line.slice(1);
|
|
|
|
|
if(/["']$/.test(line[line.length-1])) line= line.slice(0, -1);
|
|
|
|
|
return line;
|
|
|
|
|
})
|
|
|
|
|
;
|
2024-02-19 20:37:09 +01:00
|
|
|
|
}
|
|
|
|
|
function questionChatGPT(format){ return function(diff){
|
|
|
|
|
const msg= [
|
|
|
|
|
[
|
|
|
|
|
"I would like to ask you to act like a git commit message writer.",
|
2024-02-28 11:22:33 +01:00
|
|
|
|
"I will enter a git diff, and your job is to convert it into a useful commit message",
|
|
|
|
|
"Make 3 options, one option per line.",
|
2024-02-19 20:37:09 +01:00
|
|
|
|
"Do not preface the commit with anything, use a concise, precise, present-tense, complete sentence.",
|
|
|
|
|
"The length should be fewer than 50 characters if possible.",
|
|
|
|
|
].join(" ") //340chars★
|
|
|
|
|
];
|
|
|
|
|
if(format!=="regular")
|
|
|
|
|
msg.push(
|
|
|
|
|
[
|
|
|
|
|
"It should follow the conventional commits.",
|
|
|
|
|
"The format is <type in lowercase>: <description>.",
|
|
|
|
|
"A type can be one of the following: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, or test.",
|
|
|
|
|
].join(" ") //203chars★
|
|
|
|
|
);
|
|
|
|
|
msg.push("", diff);
|
|
|
|
|
return msg.join("\n");
|
|
|
|
|
}; }
|
|
|
|
|
function gitmoji(candidates, is_three= false){
|
|
|
|
|
return candidates.map(message=> message.trim().replace(/^[^:]*:/, toGitmoji));
|
|
|
|
|
|
|
|
|
|
function toGitmoji(name){
|
|
|
|
|
const candidate= ( is_three ? git3moji_list : gitmoji_list )[name.slice(0, -1)];
|
|
|
|
|
return !candidate ? name : `:${candidate}:`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function requestCommitMessage(prompt){
|
|
|
|
|
if(!token) token= s.cat(token_file).stdout.trim();
|
2024-02-22 14:55:09 +01:00
|
|
|
|
const model= "gpt-3.5-turbo-instruct";
|
2024-02-19 20:37:09 +01:00
|
|
|
|
return fetch(`https://api.openai.com/v1/engines/${model}/completions`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"Authorization": "Bearer "+token
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
max_tokens: 1000,
|
|
|
|
|
temperature: 0.1,
|
|
|
|
|
prompt
|
|
|
|
|
}),
|
|
|
|
|
signal: AbortSignal.timeout(10000)
|
|
|
|
|
}).then(r=> r.json());
|
|
|
|
|
}
|
|
|
|
|
// vim: set tabstop=4 shiftwidth=4 textwidth=250 noexpandtab :
|
|
|
|
|
// vim>60: set foldmethod=indent foldlevel=1 foldnestmax=2:
|