@gilbert_oliveira/commit-wizard 1.0.9 → 1.0.10
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/package.json +14 -10
- package/src/index.ts +95 -162
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gilbert_oliveira/commit-wizard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "O **Commit Wizard** é uma ferramenta automatizada para geração de mensagens de commit com base na convenção de **Conventional Commits**. Ele ajuda a garantir que suas mensagens de commit sigam um padrão consistente e facilite a comunicação de mudanças no código.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,10 @@
|
|
|
27
27
|
"husky",
|
|
28
28
|
"lint-staged"
|
|
29
29
|
],
|
|
30
|
-
"author": "Gilbert Oliveira",
|
|
30
|
+
"author": "Gilbert Oliveira <contato@gilbert.dev.br>",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
31
34
|
"license": "MIT",
|
|
32
35
|
"bugs": {
|
|
33
36
|
"url": "https://github.com/gilbert-oliveira/commit-wizard/issues"
|
|
@@ -37,19 +40,20 @@
|
|
|
37
40
|
"access": "public"
|
|
38
41
|
},
|
|
39
42
|
"devDependencies": {
|
|
43
|
+
"@types/bun": "latest",
|
|
40
44
|
"@types/node": "^22.14.1",
|
|
41
|
-
"
|
|
45
|
+
"eslint": "^9.24.0",
|
|
46
|
+
"semver": "^7.7.1",
|
|
47
|
+
"typescript": "^5.8.3"
|
|
42
48
|
},
|
|
43
49
|
"dependencies": {
|
|
44
|
-
"
|
|
45
|
-
"ora": "^8.2.0",
|
|
46
|
-
"@types/bun": "latest",
|
|
47
|
-
"typescript": "^5.0.0",
|
|
48
|
-
"chalk": "^5.3.0",
|
|
50
|
+
"chalk": "^5.4.1",
|
|
49
51
|
"child_process": "^1.0.2",
|
|
50
52
|
"fs": "^0.0.1-security",
|
|
51
|
-
"
|
|
53
|
+
"gpt-tokenizer": "^1.0.5",
|
|
54
|
+
"inquirer": "^12.5.2",
|
|
55
|
+
"ora": "^8.2.0",
|
|
52
56
|
"os": "^0.1.2",
|
|
53
57
|
"path": "^0.12.7"
|
|
54
58
|
}
|
|
55
|
-
}
|
|
59
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,92 +1,62 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
2
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import fs from 'fs';
|
|
4
6
|
import inquirer from 'inquirer';
|
|
5
7
|
import os from 'os';
|
|
6
8
|
import path from 'path';
|
|
7
|
-
import fs from 'fs';
|
|
8
|
-
import { execSync } from 'child_process';
|
|
9
|
-
import ora from 'ora';
|
|
10
|
-
import { encode, decode } from 'gpt-tokenizer';
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
// Função para verificar se o comando 'cody' está disponível
|
|
11
|
+
function isCodyInstalled(): boolean {
|
|
12
|
+
try {
|
|
13
|
+
execSync('cody --version', { stdio: 'ignore' });
|
|
14
|
+
return true;
|
|
15
|
+
} catch (error) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
15
18
|
}
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// Escolhe o prompt inicial de acordo com o modo.
|
|
27
|
-
const systemPrompt =
|
|
28
|
-
mode === 'commit'
|
|
29
|
-
? "Você é um assistente que gera mensagens de commit seguindo a convenção do Conventional Commits."
|
|
30
|
-
: "Você é um assistente que resume alterações de código de forma breve, usando linguagem imperativa em português.";
|
|
31
|
-
|
|
32
|
-
const body = {
|
|
33
|
-
model: "gpt-4-turbo",
|
|
34
|
-
messages: [
|
|
35
|
-
{ role: "system", content: systemPrompt },
|
|
36
|
-
{ role: "user", content: prompt }
|
|
37
|
-
],
|
|
38
|
-
temperature: 0.2
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const response = await fetch(url, {
|
|
42
|
-
method: "POST",
|
|
43
|
-
headers: {
|
|
44
|
-
"Content-Type": "application/json",
|
|
45
|
-
"Authorization": `Bearer ${OPENAI_API_KEY}`
|
|
46
|
-
},
|
|
47
|
-
body: JSON.stringify(body)
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
if (!response.ok) {
|
|
51
|
-
throw new Error(`Erro na API OpenAI: ${response.statusText}`);
|
|
20
|
+
// Função para instalar o 'cody' automaticamente
|
|
21
|
+
function installCody(): void {
|
|
22
|
+
console.log(chalk.blue('🚀 Instalando o cody automaticamente...'));
|
|
23
|
+
try {
|
|
24
|
+
execSync('npm i -g @sourcegraph/cody', { stdio: 'inherit' });
|
|
25
|
+
console.log(chalk.green('✅ Cody instalado com sucesso!'));
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(chalk.red('❌ Erro ao instalar o Cody:'), (error as Error).message);
|
|
28
|
+
process.exit(1);
|
|
52
29
|
}
|
|
53
|
-
|
|
54
|
-
const data = await response.json();
|
|
55
|
-
// Retorna a resposta do primeiro "choice".
|
|
56
|
-
return data.choices[0].message.content.trim();
|
|
57
30
|
}
|
|
58
31
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* @param maxTokens Quantidade máxima de tokens permitida para cada chunk (padrão: 1000 tokens).
|
|
64
|
-
* @returns Array de strings, cada uma representando um chunk.
|
|
65
|
-
*/
|
|
66
|
-
export function chunkDiff(diff: string, maxTokens: number = 1000): string[] {
|
|
67
|
-
// Codifica o diff para obter o array de tokens.
|
|
68
|
-
const tokens = encode(diff);
|
|
32
|
+
// Verifica se o 'cody' está instalado, caso contrário, instala
|
|
33
|
+
if (!isCodyInstalled()) {
|
|
34
|
+
installCody();
|
|
35
|
+
}
|
|
69
36
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
37
|
+
// Função para verificar se está logado no cody
|
|
38
|
+
function isCodyLoggedIn(): boolean {
|
|
39
|
+
try {
|
|
40
|
+
execSync('cody auth whoami', { stdio: 'ignore' });
|
|
41
|
+
return true;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return false;
|
|
73
44
|
}
|
|
45
|
+
}
|
|
74
46
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
47
|
+
// Verificar se o usuário está logado no Cody, se não estiver, roda o comando para logar
|
|
48
|
+
if (!isCodyLoggedIn()) {
|
|
49
|
+
console.log(chalk.blue('🔑 Realize o login no Cody...'));
|
|
50
|
+
try {
|
|
51
|
+
execSync('cody auth login --web', { stdio: 'inherit' });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(chalk.red('❌ Erro ao realizar o login no Cody:'), (error as Error).message);
|
|
54
|
+
process.exit(1);
|
|
82
55
|
}
|
|
83
|
-
|
|
84
|
-
return chunks;
|
|
85
56
|
}
|
|
86
57
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const COMMIT_PROMPT = `
|
|
58
|
+
// Define o prompt do Cody para geração da mensagem de commit
|
|
59
|
+
const CODY_PROMPT = `
|
|
90
60
|
Por favor, escreva a mensagem de commit para este diff usando a convenção de Conventional Commits: https://www.conventionalcommits.org/en/v1.0.0/.
|
|
91
61
|
A mensagem deve começar com um tipo de commit, como:
|
|
92
62
|
feat: para novas funcionalidades
|
|
@@ -116,96 +86,59 @@ Use sempre linguagem imperativa e primeira pessoa do singular, como:
|
|
|
116
86
|
Lembre-se: os textos fora do Conventional Commit devem ser em português.
|
|
117
87
|
`;
|
|
118
88
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
|
|
123
|
-
} catch {
|
|
124
|
-
console.error(chalk.red('❌ Este diretório não é um repositório git.'));
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
89
|
+
interface CommitAction {
|
|
90
|
+
action: 'confirm' | 'edit' | 'cancel';
|
|
91
|
+
}
|
|
127
92
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
stagedFiles = execSync(
|
|
132
|
-
'git diff --cached --name-only -- . ":(exclude)*.lock"',
|
|
133
|
-
{ encoding: 'utf8' }
|
|
134
|
-
).toString().trim();
|
|
135
|
-
if (!stagedFiles) {
|
|
136
|
-
console.log(chalk.yellow('⚠️ Não há alterações staged para o commit.'));
|
|
137
|
-
process.exit(0);
|
|
138
|
-
}
|
|
139
|
-
} catch (error) {
|
|
140
|
-
console.error(chalk.red('❌ Erro ao verificar alterações staged:'), error);
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
93
|
+
async function ccm(): Promise<void> {
|
|
94
|
+
const prompt = inquirer.createPromptModule();
|
|
143
95
|
|
|
144
|
-
//
|
|
145
|
-
let diff: string;
|
|
96
|
+
// Verifica se o repositório git está inicializado
|
|
146
97
|
try {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
{ encoding: 'utf8' }
|
|
150
|
-
);
|
|
98
|
+
console.log(chalk.blue('🔄 Verificando se o diretório é um repositório git...'));
|
|
99
|
+
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
|
|
151
100
|
} catch (error) {
|
|
152
|
-
console.error(chalk.red('❌
|
|
153
|
-
|
|
101
|
+
console.error(chalk.red('❌ Este diretório não é um repositório git.'));
|
|
102
|
+
return;
|
|
154
103
|
}
|
|
155
104
|
|
|
156
|
-
//
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (chunks.length === 1) {
|
|
162
|
-
inputForCommit = chunks[0];
|
|
163
|
-
} else {
|
|
164
|
-
// Se houver vários chunks, gera um resumo para todos eles utilizando um único spinner.
|
|
165
|
-
const partialSummaries: string[] = [];
|
|
166
|
-
const chunkSummaryPrefix =
|
|
167
|
-
"A partir do diff abaixo, extraia um resumo breve das alterações (use linguagem imperativa e em português):";
|
|
168
|
-
|
|
169
|
-
// Inicia um spinner único para todo o processo
|
|
170
|
-
const spinnerSummary = ora("Gerando resumo do commit.").start();
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
for (const chunk of chunks) {
|
|
174
|
-
const prompt = `${chunkSummaryPrefix}\n\n${chunk}`;
|
|
175
|
-
const summary = await callOpenAI(prompt, 'resumo');
|
|
176
|
-
partialSummaries.push(summary);
|
|
177
|
-
}
|
|
178
|
-
spinnerSummary.succeed("Resumo do commit gerado.");
|
|
179
|
-
} catch (error) {
|
|
180
|
-
spinnerSummary.fail("Erro ao gerar resumo do commit.");
|
|
181
|
-
console.error(chalk.red('❌ Erro ao gerar resumo para o commit:'), error);
|
|
182
|
-
process.exit(1);
|
|
183
|
-
}
|
|
184
|
-
inputForCommit = partialSummaries.join("\n\n");
|
|
105
|
+
// Verifica se há alterações staged
|
|
106
|
+
const stagedChanges = execSync('git diff --cached --name-only').toString().trim();
|
|
107
|
+
if (!stagedChanges) {
|
|
108
|
+
console.log(chalk.yellow('⚠️ Não há alterações staged para o commit.'));
|
|
109
|
+
return;
|
|
185
110
|
}
|
|
186
111
|
|
|
187
|
-
//
|
|
188
|
-
const
|
|
189
|
-
const
|
|
112
|
+
// Cria arquivos temporários para armazenar o prompt e o diff
|
|
113
|
+
const tempPromptPath = path.join(os.tmpdir(), 'CODY_PROMPT.txt');
|
|
114
|
+
const tempDiffPath = path.join(os.tmpdir(), 'CODY_DIFF.patch');
|
|
115
|
+
fs.writeFileSync(tempPromptPath, CODY_PROMPT);
|
|
116
|
+
fs.writeFileSync(tempDiffPath, execSync("git diff --cached --ignore-all-space | grep '^[+-]'").toString());
|
|
190
117
|
|
|
118
|
+
// Gera a mensagem do commit usando o diff salvo no arquivo temporário
|
|
191
119
|
let generatedMessage: string;
|
|
192
120
|
try {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
121
|
+
console.log(chalk.blue.bold('⌛ Gerando mensagem de commit com o Cody...'));
|
|
122
|
+
const response = execSync(
|
|
123
|
+
`cody chat --context-file ${tempDiffPath} --stdin -m "$(cat ${tempPromptPath})"`
|
|
124
|
+
).toString();
|
|
125
|
+
|
|
126
|
+
// Extrai o bloco de código delimitado por ``` usando regex
|
|
127
|
+
const match = response.match(/```([\s\S]*?)```/);
|
|
128
|
+
generatedMessage = match ? match[1].trim() : response.trim();
|
|
129
|
+
|
|
130
|
+
console.log(chalk.greenBright('\n✨ Mensagem de commit gerada automaticamente:'));
|
|
131
|
+
console.log(chalk.yellowBright(generatedMessage));
|
|
197
132
|
} catch (error) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
133
|
+
console.error(chalk.red('❌ Erro ao gerar mensagem de commit com o Cody:'), (error as Error).message);
|
|
134
|
+
return;
|
|
135
|
+
} finally {
|
|
136
|
+
fs.unlinkSync(tempPromptPath); // Remove o arquivo temporário do prompt
|
|
137
|
+
fs.unlinkSync(tempDiffPath); // Remove o arquivo temporário do diff
|
|
201
138
|
}
|
|
202
139
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// Pergunta ao usuário se deseja confirmar, editar ou cancelar o commit.
|
|
207
|
-
const promptModule = inquirer.createPromptModule();
|
|
208
|
-
const { action } = await promptModule<{ action: 'confirm' | 'edit' | 'cancel' }>([
|
|
140
|
+
// Pergunta ao usuário se ele quer editar, confirmar ou cancelar o commit
|
|
141
|
+
const { action }: CommitAction = await prompt([
|
|
209
142
|
{
|
|
210
143
|
type: 'list',
|
|
211
144
|
name: 'action',
|
|
@@ -218,7 +151,7 @@ async function main(): Promise<void> {
|
|
|
218
151
|
},
|
|
219
152
|
]);
|
|
220
153
|
|
|
221
|
-
//
|
|
154
|
+
// Caminho temporário para salvar a mensagem gerada
|
|
222
155
|
const tempFilePath = path.join(os.tmpdir(), 'COMMIT_EDITMSG');
|
|
223
156
|
fs.writeFileSync(tempFilePath, generatedMessage);
|
|
224
157
|
|
|
@@ -228,39 +161,39 @@ async function main(): Promise<void> {
|
|
|
228
161
|
try {
|
|
229
162
|
execSync(`${editor} ${tempFilePath}`, { stdio: 'inherit' });
|
|
230
163
|
} catch (error) {
|
|
231
|
-
console.error(chalk.red('❌ Erro ao abrir o editor:'), error);
|
|
232
|
-
|
|
164
|
+
console.error(chalk.red('❌ Erro ao abrir o editor:'), (error as Error).message);
|
|
165
|
+
return;
|
|
233
166
|
}
|
|
234
167
|
} else if (action === 'cancel') {
|
|
235
168
|
console.log(chalk.yellow('🚫 Commit cancelado pelo usuário.'));
|
|
236
169
|
fs.unlinkSync(tempFilePath);
|
|
237
|
-
|
|
170
|
+
return;
|
|
238
171
|
}
|
|
239
172
|
|
|
240
|
-
// Lê a mensagem
|
|
173
|
+
// Lê a mensagem do arquivo temporário após a edição
|
|
241
174
|
const finalMessage = fs.readFileSync(tempFilePath, 'utf8').trim();
|
|
175
|
+
|
|
176
|
+
// Verifica se a mensagem está vazia
|
|
242
177
|
if (!finalMessage) {
|
|
243
|
-
console.
|
|
178
|
+
console.log(chalk.red('❌ Nenhuma mensagem inserida, commit cancelado.'));
|
|
244
179
|
fs.unlinkSync(tempFilePath);
|
|
245
|
-
|
|
180
|
+
return;
|
|
246
181
|
}
|
|
247
182
|
|
|
248
|
-
// Captura
|
|
183
|
+
// Captura os argumentos adicionais passados ao script
|
|
249
184
|
const gitArgs = process.argv.slice(2).join(' ');
|
|
250
185
|
console.log(chalk.blue('🔍 Argumentos adicionais para o commit:'), gitArgs);
|
|
251
186
|
|
|
252
|
-
// Realiza o commit com a mensagem final
|
|
187
|
+
// Realiza o commit com a mensagem final e os argumentos adicionais
|
|
253
188
|
try {
|
|
254
|
-
execSync(`git commit -F ${tempFilePath} ${gitArgs}
|
|
189
|
+
execSync(`git commit -F ${tempFilePath} ${gitArgs}`);
|
|
255
190
|
console.log(chalk.green.bold('✅ Commit realizado com sucesso.'));
|
|
256
191
|
} catch (error) {
|
|
257
|
-
console.error(chalk.red('❌ Erro ao realizar o commit:'), error);
|
|
192
|
+
console.error(chalk.red('❌ Erro ao realizar o commit:'), (error as Error).message);
|
|
258
193
|
} finally {
|
|
259
194
|
fs.unlinkSync(tempFilePath);
|
|
260
195
|
}
|
|
261
196
|
}
|
|
262
197
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
process.exit(1);
|
|
266
|
-
});
|
|
198
|
+
// Chama a função principal
|
|
199
|
+
ccm().catch((err) => console.error(chalk.red('❌ Erro durante o commit:'), err));
|