@gilbert_oliveira/commit-wizard 1.0.26 → 1.2.0
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 +265 -35
- package/dist/ai-service.d.ts +36 -0
- package/dist/ai-service.js +156 -0
- package/dist/ai-service.js.map +1 -0
- package/dist/commit-splitter.d.ts +48 -0
- package/dist/commit-splitter.js +227 -0
- package/dist/commit-splitter.js.map +1 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +84 -0
- package/dist/config.js.map +1 -0
- package/dist/diff-processor.d.ts +39 -0
- package/dist/diff-processor.js +156 -0
- package/dist/diff-processor.js.map +1 -0
- package/dist/git-utils.d.ts +64 -0
- package/dist/git-utils.js +185 -0
- package/dist/git-utils.js.map +1 -0
- package/dist/index.d.ts +2 -16
- package/dist/index.js +435 -204
- package/dist/index.js.map +1 -1
- package/package.json +40 -4
- package/src/index.ts +0 -279
- package/tsconfig.json +0 -24
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
4
|
import os from 'os';
|
|
@@ -6,241 +6,472 @@ import path from 'path';
|
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
import ora from 'ora';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
process.exit(
|
|
9
|
+
import { loadConfig, saveConfig, createConfigExample } from './config.js';
|
|
10
|
+
import { AIService } from './ai-service.js';
|
|
11
|
+
import { GitUtils } from './git-utils.js';
|
|
12
|
+
import { DiffProcessor } from './diff-processor.js';
|
|
13
|
+
import { CommitSplitter } from './commit-splitter.js';
|
|
14
|
+
// 🛑 Tratamento de interrupção (Ctrl+C)
|
|
15
|
+
process.on('SIGINT', () => {
|
|
16
|
+
console.log(chalk.yellow('\n\n👋 Processo interrompido pelo usuário. Até mais!'));
|
|
17
|
+
process.exit(0);
|
|
18
|
+
});
|
|
19
|
+
process.on('SIGTERM', () => {
|
|
20
|
+
console.log(chalk.yellow('\n\n🛑 Processo terminado. Até mais!'));
|
|
21
|
+
process.exit(0);
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Exibe informações sobre o sistema e configuração
|
|
25
|
+
*/
|
|
26
|
+
function displaySystemInfo(config, gitUtils) {
|
|
27
|
+
console.log(chalk.cyan.bold('\n📊 Informações do Sistema:'));
|
|
28
|
+
console.log(chalk.white(`• Modelo AI: ${config.model}`));
|
|
29
|
+
console.log(chalk.white(`• Linguagem: ${config.language}`));
|
|
30
|
+
console.log(chalk.white(`• Temperatura: ${config.temperature}`));
|
|
31
|
+
console.log(chalk.white(`• Tokens máximos: ${config.maxTokens}`));
|
|
32
|
+
console.log(chalk.white(`• Auto commit: ${config.autoCommit ? '✅' : '❌'}`));
|
|
33
|
+
console.log(chalk.white(`• Emojis: ${config.includeEmoji ? '✅' : '❌'}`));
|
|
34
|
+
if (gitUtils.isGitRepository()) {
|
|
35
|
+
const branches = gitUtils.getBranches();
|
|
36
|
+
console.log(chalk.white(`• Branch atual: ${branches.current}`));
|
|
37
|
+
console.log(chalk.white(`• Alterações pendentes: ${gitUtils.hasUncommittedChanges() ? '⚠️' : '✅'}`));
|
|
38
|
+
}
|
|
22
39
|
}
|
|
23
40
|
/**
|
|
24
|
-
*
|
|
25
|
-
* @param prompt Texto que será enviado como mensagem do usuário.
|
|
26
|
-
* @param mode Define o contexto: 'commit' para gerar mensagem de commit ou outro valor para resumo.
|
|
27
|
-
* @returns Resposta da API (string com a mensagem ou o resumo).
|
|
41
|
+
* Menu interativo para configuração
|
|
28
42
|
*/
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
async function configurationMenu() {
|
|
44
|
+
const currentConfig = loadConfig();
|
|
45
|
+
console.log(chalk.blue.bold('\n⚙️ Menu de Configuração'));
|
|
46
|
+
const { action } = await inquirer.prompt([
|
|
47
|
+
{
|
|
48
|
+
type: 'list',
|
|
49
|
+
name: 'action',
|
|
50
|
+
message: 'O que você gostaria de configurar?',
|
|
51
|
+
choices: [
|
|
52
|
+
{ name: '🤖 Modelo de IA', value: 'model' },
|
|
53
|
+
{ name: '🌍 Idioma', value: 'language' },
|
|
54
|
+
{ name: '🎛️ Temperatura', value: 'temperature' },
|
|
55
|
+
{ name: '📊 Tokens máximos', value: 'maxTokens' },
|
|
56
|
+
{ name: '😀 Incluir emojis', value: 'includeEmoji' },
|
|
57
|
+
{ name: '🚀 Auto commit', value: 'autoCommit' },
|
|
58
|
+
{ name: '🚫 Padrões de exclusão', value: 'excludePatterns' },
|
|
59
|
+
{ name: '📄 Criar arquivo exemplo', value: 'createExample' },
|
|
60
|
+
{ name: '💾 Salvar configuração global', value: 'saveGlobal' },
|
|
61
|
+
{ name: '🔙 Voltar', value: 'back' },
|
|
62
|
+
],
|
|
48
63
|
},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
]);
|
|
65
|
+
if (action === 'back')
|
|
66
|
+
return;
|
|
67
|
+
if (action === 'createExample') {
|
|
68
|
+
createConfigExample();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const newConfig = {};
|
|
72
|
+
switch (action) {
|
|
73
|
+
case 'model': {
|
|
74
|
+
const { model } = await inquirer.prompt([
|
|
75
|
+
{
|
|
76
|
+
type: 'list',
|
|
77
|
+
name: 'model',
|
|
78
|
+
message: 'Escolha o modelo de IA:',
|
|
79
|
+
choices: [
|
|
80
|
+
{ name: 'GPT-4o (Mais recente)', value: 'gpt-4o' },
|
|
81
|
+
{ name: 'GPT-4o Mini (Mais rápido e barato)', value: 'gpt-4o-mini' },
|
|
82
|
+
{ name: 'GPT-4 Turbo', value: 'gpt-4-turbo' },
|
|
83
|
+
{ name: 'GPT-3.5 Turbo', value: 'gpt-3.5-turbo' },
|
|
84
|
+
],
|
|
85
|
+
default: currentConfig.model,
|
|
86
|
+
},
|
|
87
|
+
]);
|
|
88
|
+
newConfig.model = model;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case 'language': {
|
|
92
|
+
const { language } = await inquirer.prompt([
|
|
93
|
+
{
|
|
94
|
+
type: 'list',
|
|
95
|
+
name: 'language',
|
|
96
|
+
message: 'Escolha o idioma:',
|
|
97
|
+
choices: [
|
|
98
|
+
{ name: 'Português', value: 'pt' },
|
|
99
|
+
{ name: 'English', value: 'en' },
|
|
100
|
+
],
|
|
101
|
+
default: currentConfig.language,
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
newConfig.language = language;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case 'temperature': {
|
|
108
|
+
const { temperature } = await inquirer.prompt([
|
|
109
|
+
{
|
|
110
|
+
type: 'input',
|
|
111
|
+
name: 'temperature',
|
|
112
|
+
message: 'Digite a temperatura (0.0 - 1.0):',
|
|
113
|
+
default: currentConfig.temperature.toString(),
|
|
114
|
+
validate: (input) => {
|
|
115
|
+
const num = parseFloat(input);
|
|
116
|
+
return !isNaN(num) && num >= 0 && num <= 1 ? true : 'Digite um número entre 0.0 e 1.0';
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
]);
|
|
120
|
+
newConfig.temperature = parseFloat(temperature);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case 'maxTokens': {
|
|
124
|
+
const { maxTokens } = await inquirer.prompt([
|
|
125
|
+
{
|
|
126
|
+
type: 'input',
|
|
127
|
+
name: 'maxTokens',
|
|
128
|
+
message: 'Digite o número máximo de tokens:',
|
|
129
|
+
default: currentConfig.maxTokens.toString(),
|
|
130
|
+
validate: (input) => {
|
|
131
|
+
const num = parseInt(input);
|
|
132
|
+
return !isNaN(num) && num > 0 ? true : 'Digite um número positivo';
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
]);
|
|
136
|
+
newConfig.maxTokens = parseInt(maxTokens);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case 'includeEmoji': {
|
|
140
|
+
const { includeEmoji } = await inquirer.prompt([
|
|
141
|
+
{
|
|
142
|
+
type: 'confirm',
|
|
143
|
+
name: 'includeEmoji',
|
|
144
|
+
message: 'Incluir emojis nas mensagens de commit?',
|
|
145
|
+
default: currentConfig.includeEmoji,
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
148
|
+
newConfig.includeEmoji = includeEmoji;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'autoCommit': {
|
|
152
|
+
const { autoCommit } = await inquirer.prompt([
|
|
153
|
+
{
|
|
154
|
+
type: 'confirm',
|
|
155
|
+
name: 'autoCommit',
|
|
156
|
+
message: 'Fazer commit automaticamente sem confirmação?',
|
|
157
|
+
default: currentConfig.autoCommit,
|
|
158
|
+
},
|
|
159
|
+
]);
|
|
160
|
+
newConfig.autoCommit = autoCommit;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case 'excludePatterns': {
|
|
164
|
+
const { excludePatterns } = await inquirer.prompt([
|
|
165
|
+
{
|
|
166
|
+
type: 'input',
|
|
167
|
+
name: 'excludePatterns',
|
|
168
|
+
message: 'Padrões de exclusão (separados por vírgula):',
|
|
169
|
+
default: currentConfig.excludePatterns.join(', '),
|
|
170
|
+
},
|
|
171
|
+
]);
|
|
172
|
+
newConfig.excludePatterns = excludePatterns.split(',').map((p) => p.trim());
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case 'saveGlobal':
|
|
176
|
+
saveConfig(currentConfig, true);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (Object.keys(newConfig).length > 0) {
|
|
180
|
+
saveConfig(newConfig);
|
|
181
|
+
console.log(chalk.green('✅ Configuração atualizada com sucesso!'));
|
|
182
|
+
}
|
|
57
183
|
}
|
|
58
184
|
/**
|
|
59
|
-
*
|
|
60
|
-
* Utiliza o gpt-tokenizer para garantir que cada chunk não exceda o limite de tokens.
|
|
61
|
-
* @param diff O diff completo em formato de string.
|
|
62
|
-
* @param maxTokens Quantidade máxima de tokens permitida para cada chunk (padrão: 1000 tokens).
|
|
63
|
-
* @returns Array de strings, cada uma representando um chunk.
|
|
185
|
+
* Função principal do commit wizard
|
|
64
186
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
return chunks;
|
|
80
|
-
}
|
|
81
|
-
// Pré-prompt para a geração da mensagem de commit conforme as convenções
|
|
82
|
-
const COMMIT_PROMPT = `
|
|
83
|
-
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/.
|
|
84
|
-
A mensagem deve começar com um tipo de commit, como:
|
|
85
|
-
feat: para novas funcionalidades
|
|
86
|
-
fix: para correções de bugs
|
|
87
|
-
chore: para alterações que não afetam a funcionalidade
|
|
88
|
-
docs: para mudanças na documentação
|
|
89
|
-
style: para alterações no estilo do código (como formatação)
|
|
90
|
-
refactor: para alterações no código que não alteram a funcionalidade
|
|
91
|
-
perf: para melhorias de desempenho
|
|
92
|
-
test: para alterações nos testes
|
|
93
|
-
ci: para mudanças no pipeline de integração contínua
|
|
94
|
-
|
|
95
|
-
Exemplo:
|
|
96
|
-
feat(auth): adicionar suporte ao login com Google
|
|
97
|
-
|
|
98
|
-
Caso o commit seja uma alteração significativa (breaking change), inclua um título com \`!\` após o tipo de commit e adicione a explicação em \`BREAKING CHANGE\`:
|
|
99
|
-
feat!(auth): reestruturar fluxo de login
|
|
100
|
-
BREAKING CHANGE: A API de login foi alterada e não é compatível com versões anteriores.
|
|
101
|
-
|
|
102
|
-
Gere também uma descrição mais detalhada do commit, se necessário.
|
|
103
|
-
|
|
104
|
-
Use sempre linguagem imperativa e primeira pessoa do singular, como:
|
|
105
|
-
- "adiciona recurso"
|
|
106
|
-
- "corrige bug"
|
|
107
|
-
- "remove arquivo"
|
|
108
|
-
|
|
109
|
-
Lembre-se: os textos fora do Conventional Commit devem ser em português.
|
|
110
|
-
`;
|
|
111
|
-
async function main() {
|
|
112
|
-
// Verifica se o diretório é um repositório git.
|
|
113
|
-
try {
|
|
114
|
-
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
|
|
187
|
+
async function runCommitWizard() {
|
|
188
|
+
const config = loadConfig();
|
|
189
|
+
if (!config.apiKey) {
|
|
190
|
+
console.log(chalk.redBright('\n🚨 Erro: A variável de ambiente ') +
|
|
191
|
+
chalk.yellow('OPENAI_API_KEY') +
|
|
192
|
+
chalk.redBright(' não está definida.\n'));
|
|
193
|
+
console.log(chalk.white('→ Defina sua chave com: ') +
|
|
194
|
+
chalk.cyan('export OPENAI_API_KEY="sua-chave"') +
|
|
195
|
+
chalk.white(' ou configure no seu ') +
|
|
196
|
+
chalk.cyan('.bashrc') +
|
|
197
|
+
chalk.white(' ou ') +
|
|
198
|
+
chalk.cyan('.zshrc\n'));
|
|
199
|
+
process.exit(1);
|
|
115
200
|
}
|
|
116
|
-
|
|
201
|
+
const gitUtils = new GitUtils(config.excludePatterns);
|
|
202
|
+
const aiService = new AIService(config);
|
|
203
|
+
const diffProcessor = new DiffProcessor(aiService, config.maxTokens);
|
|
204
|
+
// Verifica se é um repositório Git
|
|
205
|
+
if (!gitUtils.isGitRepository()) {
|
|
117
206
|
console.error(chalk.red('❌ Este diretório não é um repositório git.'));
|
|
118
207
|
process.exit(1);
|
|
119
208
|
}
|
|
120
|
-
//
|
|
121
|
-
|
|
209
|
+
// Obtém status dos arquivos staged
|
|
210
|
+
const gitStatus = gitUtils.getStagedStatus();
|
|
211
|
+
if (!gitStatus.hasStagedFiles) {
|
|
212
|
+
console.log(chalk.yellow('⚠️ Não há alterações staged para o commit.'));
|
|
213
|
+
console.log(chalk.cyan('💡 Dica: Use ') +
|
|
214
|
+
chalk.white('git add <arquivos>') +
|
|
215
|
+
chalk.cyan(' para adicionar arquivos ao stage.'));
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
218
|
+
// Analisa complexidade do diff
|
|
219
|
+
const complexity = diffProcessor.analyzeDiffComplexity(gitStatus.diff);
|
|
220
|
+
const stats = diffProcessor.extractDiffStats(gitStatus.diff);
|
|
221
|
+
const hasBreakingChanges = diffProcessor.detectBreakingChanges(gitStatus.diff);
|
|
222
|
+
console.log(chalk.cyan.bold('\n📈 Análise do Diff:'));
|
|
223
|
+
console.log(chalk.white(`• Arquivos alterados: ${stats.files.length}`));
|
|
224
|
+
console.log(chalk.white(`• Linhas adicionadas: ${chalk.green(`+${stats.additions}`)}`));
|
|
225
|
+
console.log(chalk.white(`• Linhas removidas: ${chalk.red(`-${stats.deletions}`)}`));
|
|
226
|
+
console.log(chalk.white(`• Tokens estimados: ${complexity.tokenCount}`));
|
|
227
|
+
console.log(chalk.white(`• Complexidade: ${complexity.complexity === 'simple' ? '🟢 Simples' : complexity.complexity === 'moderate' ? '🟡 Moderada' : '🔴 Complexa'}`));
|
|
228
|
+
if (hasBreakingChanges) {
|
|
229
|
+
console.log(chalk.yellow('⚠️ Possíveis breaking changes detectadas!'));
|
|
230
|
+
}
|
|
231
|
+
// Processa o diff
|
|
232
|
+
console.log(chalk.cyan('\n🔍 Iniciando análise...'));
|
|
122
233
|
try {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
234
|
+
const processedDiff = await diffProcessor.processLargeDiff(gitStatus.diff);
|
|
235
|
+
// Gera mensagem de commit
|
|
236
|
+
const generateSpinner = ora('🤖 Gerando mensagem de commit com IA...').start();
|
|
237
|
+
const response = await aiService.generateCommitMessage(processedDiff);
|
|
238
|
+
generateSpinner.succeed('✨ Mensagem de commit gerada com sucesso.');
|
|
239
|
+
if (response.usage) {
|
|
240
|
+
console.log(chalk.dim(`\n💰 Tokens utilizados: ${response.usage.totalTokens} (prompt: ${response.usage.promptTokens}, resposta: ${response.usage.completionTokens})`));
|
|
241
|
+
}
|
|
242
|
+
console.log(chalk.greenBright('\n✨ Mensagem de commit gerada:'));
|
|
243
|
+
console.log(chalk.yellowBright(response.content));
|
|
244
|
+
// Se auto commit estiver ativado, faz o commit diretamente
|
|
245
|
+
if (config.autoCommit) {
|
|
246
|
+
console.log(chalk.blue('\n🚀 Auto commit ativado, realizando commit...'));
|
|
247
|
+
gitUtils.commit(response.content);
|
|
248
|
+
console.log(chalk.green.bold('✅ Commit realizado com sucesso.'));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// Menu de ações
|
|
252
|
+
const { action } = await inquirer.prompt([
|
|
253
|
+
{
|
|
254
|
+
type: 'list',
|
|
255
|
+
name: 'action',
|
|
256
|
+
message: chalk.blue.bold('O que deseja fazer com a mensagem de commit?'),
|
|
257
|
+
choices: [
|
|
258
|
+
{ name: '📌 Confirmar e commitar', value: 'confirm' },
|
|
259
|
+
{ name: '📝 Editar a mensagem antes de commitar', value: 'edit' },
|
|
260
|
+
{ name: '🔄 Regenerar mensagem', value: 'regenerate' },
|
|
261
|
+
{ name: '📋 Copiar para clipboard', value: 'copy' },
|
|
262
|
+
{ name: '🚫 Cancelar o commit', value: 'cancel' },
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
]);
|
|
266
|
+
if (action === 'regenerate') {
|
|
267
|
+
// Recursão para regenerar
|
|
268
|
+
return runCommitWizard();
|
|
269
|
+
}
|
|
270
|
+
if (action === 'copy') {
|
|
271
|
+
try {
|
|
272
|
+
execSync(`echo "${response.content}" | pbcopy`, { stdio: 'ignore' });
|
|
273
|
+
console.log(chalk.green('📋 Mensagem copiada para o clipboard!'));
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
console.log(chalk.yellow('⚠️ Não foi possível copiar para o clipboard. Mensagem:'));
|
|
277
|
+
console.log(response.content);
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (action === 'cancel') {
|
|
282
|
+
console.log(chalk.yellow('🚫 Commit cancelado pelo usuário.'));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
let finalMessage = response.content;
|
|
286
|
+
if (action === 'edit') {
|
|
287
|
+
const tempFilePath = path.join(os.tmpdir(), 'COMMIT_EDITMSG');
|
|
288
|
+
fs.writeFileSync(tempFilePath, response.content);
|
|
289
|
+
console.log(chalk.cyan('📝 Abrindo editor para edição da mensagem...'));
|
|
290
|
+
const editor = process.env.EDITOR || 'nano';
|
|
291
|
+
try {
|
|
292
|
+
execSync(`${editor} ${tempFilePath}`, { stdio: 'inherit' });
|
|
293
|
+
finalMessage = fs.readFileSync(tempFilePath, 'utf8').trim();
|
|
294
|
+
fs.unlinkSync(tempFilePath);
|
|
295
|
+
if (!finalMessage) {
|
|
296
|
+
console.error(chalk.red('❌ Nenhuma mensagem inserida, commit cancelado.'));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
console.error(chalk.red('❌ Erro ao abrir o editor:'), error);
|
|
302
|
+
fs.unlinkSync(tempFilePath);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Realiza o commit
|
|
307
|
+
const additionalArgs = process.argv
|
|
308
|
+
.slice(2)
|
|
309
|
+
.filter(arg => !['--config', '--info'].includes(arg));
|
|
310
|
+
gitUtils.commit(finalMessage, additionalArgs);
|
|
311
|
+
console.log(chalk.green.bold('✅ Commit realizado com sucesso.'));
|
|
312
|
+
// Mostra informações do commit criado
|
|
313
|
+
try {
|
|
314
|
+
const commitInfo = gitUtils.getLastCommitInfo();
|
|
315
|
+
console.log(chalk.dim(`\n📝 Commit ${commitInfo.hash.substring(0, 7)}: ${commitInfo.message}`));
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// Ignora erros ao obter info do commit
|
|
127
319
|
}
|
|
128
320
|
}
|
|
129
321
|
catch (error) {
|
|
130
|
-
console.error(chalk.red('❌ Erro
|
|
322
|
+
console.error(chalk.red('❌ Erro durante o commit:'), error);
|
|
131
323
|
process.exit(1);
|
|
132
324
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Função para multi-commit inteligente
|
|
328
|
+
*/
|
|
329
|
+
async function runMultiCommit() {
|
|
330
|
+
const config = loadConfig();
|
|
331
|
+
if (!config.apiKey) {
|
|
332
|
+
console.log(chalk.redBright('\n🚨 Erro: A variável de ambiente ') +
|
|
333
|
+
chalk.yellow('OPENAI_API_KEY') +
|
|
334
|
+
chalk.redBright(' não está definida.\n'));
|
|
335
|
+
process.exit(1);
|
|
137
336
|
}
|
|
138
|
-
|
|
139
|
-
|
|
337
|
+
const gitUtils = new GitUtils(config.excludePatterns);
|
|
338
|
+
const aiService = new AIService(config);
|
|
339
|
+
const commitSplitter = new CommitSplitter(gitUtils, config);
|
|
340
|
+
// Verifica se é um repositório Git
|
|
341
|
+
if (!gitUtils.isGitRepository()) {
|
|
342
|
+
console.error(chalk.red('❌ Este diretório não é um repositório git.'));
|
|
140
343
|
process.exit(1);
|
|
141
344
|
}
|
|
142
|
-
//
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
345
|
+
// Obtém status dos arquivos staged
|
|
346
|
+
const gitStatus = gitUtils.getStagedStatus();
|
|
347
|
+
if (!gitStatus.hasStagedFiles) {
|
|
348
|
+
console.log(chalk.yellow('⚠️ Não há alterações staged para o commit.'));
|
|
349
|
+
console.log(chalk.cyan('💡 Dica: Use ') +
|
|
350
|
+
chalk.white('git add <arquivos>') +
|
|
351
|
+
chalk.cyan(' para adicionar arquivos ao stage.'));
|
|
352
|
+
process.exit(0);
|
|
353
|
+
}
|
|
354
|
+
console.log(chalk.blue.bold('\n🎯 Multi-Commit Inteligente'));
|
|
355
|
+
console.log(chalk.gray('Dividindo alterações em commits organizados por contexto...\n'));
|
|
356
|
+
try {
|
|
357
|
+
// Analisa e divide o diff
|
|
358
|
+
const splitResult = await commitSplitter.analyzeAndSplit();
|
|
359
|
+
if (splitResult.groups.length <= 1) {
|
|
360
|
+
console.log(chalk.yellow('📝 Apenas um contexto detectado. Fazendo commit normal...'));
|
|
361
|
+
return runCommitWizard();
|
|
362
|
+
}
|
|
363
|
+
// Exibe preview dos grupos propostos
|
|
364
|
+
console.log(chalk.cyan.bold(`\n📋 ${splitResult.groups.length} commits propostos:`));
|
|
365
|
+
for (let i = 0; i < splitResult.groups.length; i++) {
|
|
366
|
+
const group = splitResult.groups[i];
|
|
367
|
+
console.log(chalk.white(`\n${i + 1}. ${group.emoji} ${group.type}: ${group.description}`));
|
|
368
|
+
console.log(chalk.gray(` Arquivos (${group.files.length}): ${group.files.join(', ')}`));
|
|
369
|
+
}
|
|
370
|
+
// Confirma se quer prosseguir
|
|
371
|
+
const { proceed } = await inquirer.prompt([
|
|
372
|
+
{
|
|
373
|
+
type: 'confirm',
|
|
374
|
+
name: 'proceed',
|
|
375
|
+
message: 'Deseja prosseguir com estes commits?',
|
|
376
|
+
default: true,
|
|
377
|
+
},
|
|
378
|
+
]);
|
|
379
|
+
if (!proceed) {
|
|
380
|
+
console.log(chalk.yellow('🚫 Multi-commit cancelado pelo usuário.'));
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
// Executa commits sequencialmente
|
|
384
|
+
let successCount = 0;
|
|
385
|
+
const totalCommits = splitResult.groups.length;
|
|
386
|
+
for (let i = 0; i < splitResult.groups.length; i++) {
|
|
387
|
+
const group = splitResult.groups[i];
|
|
388
|
+
console.log(chalk.blue(`\n[${i + 1}/${totalCommits}] Processando: ${group.emoji} ${group.type}`));
|
|
389
|
+
try {
|
|
390
|
+
// Remove todos os arquivos do stage
|
|
391
|
+
execSync('git reset HEAD .');
|
|
392
|
+
// Adiciona apenas os arquivos deste grupo
|
|
393
|
+
for (const file of group.files) {
|
|
394
|
+
execSync(`git add "${file}"`);
|
|
395
|
+
}
|
|
396
|
+
// Gera mensagem de commit específica para este grupo
|
|
397
|
+
const response = await aiService.generateCommitMessage(group.diff);
|
|
398
|
+
let commitMessage = response.content;
|
|
399
|
+
// Adiciona emoji se configurado
|
|
400
|
+
if (config.includeEmoji && !commitMessage.startsWith(group.emoji)) {
|
|
401
|
+
commitMessage = `${group.emoji} ${commitMessage}`;
|
|
402
|
+
}
|
|
403
|
+
// Realiza o commit
|
|
404
|
+
gitUtils.commit(commitMessage);
|
|
405
|
+
console.log(chalk.green(`✅ Commit ${i + 1}: ${commitMessage.split('\n')[0]}`));
|
|
406
|
+
successCount++;
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
console.error(chalk.red(`❌ Erro no commit ${i + 1}:`), error);
|
|
410
|
+
const { continueOnError } = await inquirer.prompt([
|
|
411
|
+
{
|
|
412
|
+
type: 'confirm',
|
|
413
|
+
name: 'continueOnError',
|
|
414
|
+
message: 'Deseja continuar com os próximos commits?',
|
|
415
|
+
default: false,
|
|
416
|
+
},
|
|
417
|
+
]);
|
|
418
|
+
if (!continueOnError) {
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
160
421
|
}
|
|
161
|
-
spinnerSummary.succeed("Resumo do commit gerado.");
|
|
162
422
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
423
|
+
// Resultado final
|
|
424
|
+
console.log(chalk.cyan.bold(`\n🎉 Multi-commit concluído!`));
|
|
425
|
+
console.log(chalk.white(`• Commits realizados: ${successCount}/${totalCommits}`));
|
|
426
|
+
if (successCount > 0) {
|
|
427
|
+
console.log(chalk.green('✨ Histórico organizado com sucesso!'));
|
|
167
428
|
}
|
|
168
|
-
inputForCommit = partialSummaries.join("\n\n");
|
|
169
|
-
}
|
|
170
|
-
// Gera a mensagem de commit com o pré-prompt e o diff (ou seus resumos).
|
|
171
|
-
const finalPrompt = `${COMMIT_PROMPT}\n\nDiff:\n\n${inputForCommit}`;
|
|
172
|
-
const spinnerCommit = ora('Gerando mensagem de commit com base no diff...').start();
|
|
173
|
-
let generatedMessage;
|
|
174
|
-
try {
|
|
175
|
-
generatedMessage = await callOpenAI(finalPrompt, 'commit');
|
|
176
|
-
// Remove os delimitadores de bloco de código (```)
|
|
177
|
-
generatedMessage = generatedMessage.replace(/```/g, '').trim();
|
|
178
|
-
spinnerCommit.succeed('Mensagem de commit gerada com sucesso.');
|
|
179
429
|
}
|
|
180
430
|
catch (error) {
|
|
181
|
-
|
|
182
|
-
console.error(chalk.red('❌ Erro ao gerar a mensagem de commit:'), error);
|
|
431
|
+
console.error(chalk.red('❌ Erro durante o multi-commit:'), error);
|
|
183
432
|
process.exit(1);
|
|
184
433
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const editor = process.env.EDITOR || 'nano';
|
|
207
|
-
try {
|
|
208
|
-
execSync(`${editor} ${tempFilePath}`, { stdio: 'inherit' });
|
|
209
|
-
}
|
|
210
|
-
catch (error) {
|
|
211
|
-
console.error(chalk.red('❌ Erro ao abrir o editor:'), error);
|
|
212
|
-
process.exit(1);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
else if (action === 'cancel') {
|
|
216
|
-
console.log(chalk.yellow('🚫 Commit cancelado pelo usuário.'));
|
|
217
|
-
fs.unlinkSync(tempFilePath);
|
|
218
|
-
process.exit(0);
|
|
219
|
-
}
|
|
220
|
-
// Lê a mensagem final (após eventual edição).
|
|
221
|
-
const finalMessage = fs.readFileSync(tempFilePath, 'utf8').trim();
|
|
222
|
-
if (!finalMessage) {
|
|
223
|
-
console.error(chalk.red('❌ Nenhuma mensagem inserida, commit cancelado.'));
|
|
224
|
-
fs.unlinkSync(tempFilePath);
|
|
225
|
-
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Função principal
|
|
437
|
+
*/
|
|
438
|
+
async function main() {
|
|
439
|
+
const args = process.argv.slice(2);
|
|
440
|
+
// Verifica argumentos de linha de comando
|
|
441
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
442
|
+
console.log(chalk.blue.bold('\n🧙♂️ Commit Wizard - Gerador de commits com IA\n'));
|
|
443
|
+
console.log(chalk.white('Uso: commit-wizard [opções]\n'));
|
|
444
|
+
console.log(chalk.white('Opções:'));
|
|
445
|
+
console.log(chalk.white(' --config, -c Abrir menu de configuração'));
|
|
446
|
+
console.log(chalk.white(' --info, -i Mostrar informações do sistema'));
|
|
447
|
+
console.log(chalk.white(' --split, -s Dividir em múltiplos commits por contexto'));
|
|
448
|
+
console.log(chalk.white(' --help, -h Mostrar esta ajuda'));
|
|
449
|
+
console.log(chalk.white('\nExemplos:'));
|
|
450
|
+
console.log(chalk.cyan(' commit-wizard # Gerar commit normal'));
|
|
451
|
+
console.log(chalk.cyan(' commit-wizard --split # Multi-commit por contexto'));
|
|
452
|
+
console.log(chalk.cyan(' commit-wizard --config # Configurar o wizard'));
|
|
453
|
+
console.log(chalk.cyan(' commit-wizard --info # Ver informações do sistema'));
|
|
454
|
+
return;
|
|
226
455
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
// Realiza o commit com a mensagem final.
|
|
231
|
-
try {
|
|
232
|
-
execSync(`git commit -F ${tempFilePath} ${gitArgs}`, { stdio: 'inherit' });
|
|
233
|
-
console.log(chalk.green.bold('✅ Commit realizado com sucesso.'));
|
|
456
|
+
if (args.includes('--config') || args.includes('-c')) {
|
|
457
|
+
await configurationMenu();
|
|
458
|
+
return;
|
|
234
459
|
}
|
|
235
|
-
|
|
236
|
-
|
|
460
|
+
if (args.includes('--info') || args.includes('-i')) {
|
|
461
|
+
const config = loadConfig();
|
|
462
|
+
const gitUtils = new GitUtils(config.excludePatterns);
|
|
463
|
+
displaySystemInfo(config, gitUtils);
|
|
464
|
+
return;
|
|
237
465
|
}
|
|
238
|
-
|
|
239
|
-
|
|
466
|
+
if (args.includes('--split') || args.includes('-s')) {
|
|
467
|
+
await runMultiCommit();
|
|
468
|
+
return;
|
|
240
469
|
}
|
|
470
|
+
// Executa o wizard principal
|
|
471
|
+
await runCommitWizard();
|
|
241
472
|
}
|
|
242
|
-
main().catch(
|
|
243
|
-
console.error(chalk.red('❌ Erro durante
|
|
473
|
+
main().catch(err => {
|
|
474
|
+
console.error(chalk.red('❌ Erro durante execução:'), err);
|
|
244
475
|
process.exit(1);
|
|
245
476
|
});
|
|
246
477
|
//# sourceMappingURL=index.js.map
|