@gilbert_oliveira/commit-wizard 2.12.3-canary.1 → 2.13.0-canary.2
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 +2 -0
- package/dist/commit-wizard.js +46 -38
- package/package.json +1 -1
- package/src/bin/commit-wizard.ts +20 -0
- package/src/commitlint/index.ts +99 -0
- package/src/config/index.ts +11 -0
- package/src/core/index.ts +77 -0
- package/src/utils/args.ts +16 -8
package/README.md
CHANGED
|
@@ -55,6 +55,8 @@ commit-wizard --yes # Aceitar automaticamente
|
|
|
55
55
|
commit-wizard --smart-split # Smart Split com IA
|
|
56
56
|
commit-wizard --split # Split manual por arquivo
|
|
57
57
|
commit-wizard --dry-run # Visualizar sem commitar
|
|
58
|
+
commit-wizard --validate # Habilitar validação com commitlint
|
|
59
|
+
commit-wizard --init-commitlint # Criar configuração padrão do commitlint
|
|
58
60
|
```
|
|
59
61
|
|
|
60
62
|
## ⚙️ Configuração
|
package/dist/commit-wizard.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
`).filter(
|
|
4
|
-
`).filter(n=>n.length>0),
|
|
2
|
+
var Je=Object.defineProperty;var w=(e,t)=>()=>(e&&(t=e(e=0)),t);var G=(e,t)=>{for(var i in t)Je(e,i,{get:t[i],enumerable:!0})};import $t from"path";import{fileURLToPath as Et}from"url";var f=w(()=>{"use strict"});import{existsSync as de,readFileSync as pe}from"fs";import{join as ge}from"path";function ye(e){let t;try{t=ge(process.cwd(),".commit-wizardrc")}catch{t="/tmp/.commit-wizardrc"}let i=ge(process.env.HOME||process.env.USERPROFILE||"/tmp",".commit-wizardrc"),o={...Ke};try{if(de(i)){let s=pe(i,"utf-8"),a=JSON.parse(s);o=he(o,a)}}catch{console.warn("\u26A0\uFE0F Erro ao ler configura\xE7\xE3o global: Erro desconhecido")}let n=e||t;try{if(de(n)){let s=pe(n,"utf-8"),a=JSON.parse(s);o=he(o,a)}}catch{console.warn("\u26A0\uFE0F Erro ao ler .commit-wizardrc: Erro desconhecido")}return o.openai.apiKey=process.env.OPENAI_API_KEY,process.env.COMMIT_WIZARD_DEBUG,process.env.COMMIT_WIZARD_DRY_RUN==="true"&&(o.dryRun=!0),o}function he(e,t){return{...e,...t,openai:{...e.openai,...t.openai},smartSplit:{...e.smartSplit,...t.smartSplit},cache:{...e.cache,...t.cache},commitlint:{...e.commitlint,...t.commitlint}}}function Ce(e){let t=[];return e.openai.apiKey||t.push("OPENAI_API_KEY n\xE3o encontrada nas vari\xE1veis de ambiente"),(e.openai.maxTokens<10||e.openai.maxTokens>4e3)&&t.push("maxTokens deve estar entre 10 e 4000"),(e.openai.temperature<0||e.openai.temperature>2)&&t.push("temperature deve estar entre 0 e 2"),["pt","en","es","fr","de","it","ja","ko","zh"].includes(e.language)||t.push("language deve ser um idioma suportado (pt, en, es, fr, de, it, ja, ko, zh)"),["conventional","simple","detailed"].includes(e.commitStyle)||t.push("commitStyle deve ser conventional, simple ou detailed"),e.smartSplit.minGroupSize<1&&t.push("smartSplit.minGroupSize deve ser pelo menos 1"),(e.smartSplit.maxGroups<1||e.smartSplit.maxGroups>10)&&t.push("smartSplit.maxGroups deve estar entre 1 e 10"),(e.smartSplit.confidenceThreshold<0||e.smartSplit.confidenceThreshold>1)&&t.push("smartSplit.confidenceThreshold deve estar entre 0 e 1"),e.cache.ttl<1&&t.push("cache.ttl deve ser pelo menos 1 minuto"),e.cache.maxSize<1&&t.push("cache.maxSize deve ser pelo menos 1"),t}var Ke,ve=w(()=>{"use strict";f();Ke={openai:{model:"gpt-4o",maxTokens:150,temperature:.7,timeout:3e4,retries:2},language:"pt",commitStyle:"conventional",autoCommit:!1,splitCommits:!1,dryRun:!1,smartSplit:{enabled:!0,minGroupSize:1,maxGroups:5,confidenceThreshold:.7},cache:{enabled:!0,ttl:60,maxSize:100},commitlint:{enabled:!0}}});var M={};G(M,{escapeShellArg:()=>D,executeCommit:()=>z,executeFileCommit:()=>k,getDiffStats:()=>B,getFileDiff:()=>Be,getGitStatus:()=>K,isGitRepository:()=>J});import{execSync as E}from"child_process";function D(e){return`'${e.replace(/'/g,`'"'"'`)}'`}function J(){try{return E("git rev-parse --git-dir",{stdio:"ignore"}),!0}catch{return!1}}function K(){try{let t=E("git diff --cached --name-only",{encoding:"utf-8",stdio:"pipe"}).trim().split(`
|
|
3
|
+
`).filter(o=>o.length>0),i=t.length>0?E("git diff --cached",{encoding:"utf-8",stdio:"pipe"}):"";return{hasStaged:t.length>0,stagedFiles:t,diff:i.trim()}}catch(e){throw new Error(`Erro ao obter status do Git: ${e instanceof Error?e.message:"Erro desconhecido"}`)}}function Be(e){try{return E(`git diff --cached -- "${e}"`,{encoding:"utf-8",stdio:"pipe"})}catch(t){throw new Error(`Erro ao obter diff do arquivo ${e}: ${t instanceof Error?t.message:"Erro desconhecido"}`)}}function z(e){try{let t=D(e);return E(`git commit -m ${t}`,{stdio:"pipe"}),{success:!0,hash:E("git rev-parse HEAD",{encoding:"utf-8",stdio:"pipe"}).trim(),message:e}}catch(t){return{success:!1,error:t instanceof Error?t.message:"Erro desconhecido ao executar commit"}}}function k(e,t){try{let i=D(t),o=D(e);return E(`git commit ${o} -m ${i}`,{stdio:"pipe"}),{success:!0,hash:E("git rev-parse HEAD",{encoding:"utf-8",stdio:"pipe"}).trim(),message:t}}catch(i){return{success:!1,error:i instanceof Error?i.message:"Erro desconhecido ao executar commit do arquivo"}}}function B(){try{let t=E("git diff --cached --numstat",{encoding:"utf-8",stdio:"pipe"}).trim().split(`
|
|
4
|
+
`).filter(n=>n.length>0),i=0,o=0;return t.forEach(n=>{let[s,a]=n.split(" ");s&&s!=="-"&&(i+=parseInt(s)||0),a&&a!=="-"&&(o+=parseInt(a)||0)}),{added:i,removed:o,files:t.length}}catch{return{added:0,removed:0,files:0}}}var $=w(()=>{"use strict";f()});var Ee={};G(Ee,{buildPrompt:()=>be,detectCommitType:()=>we,extractCommitTypeFromMessage:()=>Se,generateCommitMessage:()=>Ae,generateWithRetry:()=>T,processOpenAIMessage:()=>$e,smartFilterDiff:()=>xe});function xe(e,t){if(e.length<=t)return e;let i=[/^diff --git a\/package-lock\.json/m,/^diff --git a\/yarn\.lock/m,/^diff --git a\/pnpm-lock\.yaml/m,/^diff --git a\/composer\.lock/m,/^diff --git a\/Gemfile\.lock/m,/^diff --git a\/Cargo\.lock/m,/^diff --git a\/poetry\.lock/m,/^diff --git a\/go\.sum/m,/^diff --git a\/requirements(?:\.txt)?\.lock/m,/^diff --git a\/.*\.min\.js/m,/^diff --git a\/.*\.min\.css/m,/^diff --git a\/.*\.map/m,/^diff --git a\/dist\//m,/^diff --git a\/build\//m],o=e.split(/(?=^diff --git)/m).filter(m=>m.trim()),n=[],s=[];o.forEach(m=>{i.some(h=>h.test(m))?s.push(m):n.push(m)});let a="",c=t;for(let m of n)if(m.length<=c)a+=m,c-=m.length;else if(c>200){a+=m.substring(0,c-50),c=t-a.length;break}else break;if(!a&&s.length>0&&c>0)for(let m of s)if(m.length<=c)a+=m,c-=m.length;else if(c>200){a+=m.substring(0,c-50),c=t-a.length;break}else break;let r=`
|
|
5
5
|
... (diff otimizado para focar em mudan\xE7as principais)`;if(c>200&&s.length>0){let m=`
|
|
6
6
|
|
|
7
|
-
... (${s.length} arquivo(s) de depend\xEAncias/build omitido(s): ${s.map(h=>{let
|
|
7
|
+
... (${s.length} arquivo(s) de depend\xEAncias/build omitido(s): ${s.map(h=>{let x=h.match(/^diff --git a\/(.*?) b\//);return x?x[1]:""}).filter(Boolean).join(", ")})`,d=c-r.length;m.length<d&&(a+=m)}return a.trim()!==e.trim()?a+r:a}function be(e,t,i){let o=t.language==="pt"?"portugu\xEAs":"english",n=Ye(t.commitStyle,t.language),a=xe(e,6e3),c=i.length>10?`${i.length} arquivos: ${i.slice(0,5).join(", ")}...`:i.join(", ");return`Gere mensagem de commit em ${o} (${t.commitStyle}).
|
|
8
8
|
|
|
9
9
|
Arquivos: ${c}
|
|
10
10
|
|
|
@@ -15,7 +15,7 @@ Diff:
|
|
|
15
15
|
${a}
|
|
16
16
|
\`\`\`
|
|
17
17
|
|
|
18
|
-
Mensagem:`}function
|
|
18
|
+
Mensagem:`}function Ye(e,t){let i={pt:{conventional:`- Use formato: tipo(escopo): descri\xE7\xE3o
|
|
19
19
|
- Tipos v\xE1lidos: feat, fix, docs, style, refactor, test, chore, build, ci
|
|
20
20
|
- Exemplo: "feat(auth): adicionar valida\xE7\xE3o de email"
|
|
21
21
|
- Mantenha a primeira linha com at\xE9 50 caracteres`,simple:`- Use formato simples e direto
|
|
@@ -33,19 +33,19 @@ Mensagem:`}function Le(e,t){let o={pt:{conventional:`- Use formato: tipo(escopo)
|
|
|
33
33
|
- Maximum 50 characters`,detailed:`- First line: summary under 50 characters
|
|
34
34
|
- Add explanatory body if needed
|
|
35
35
|
- Use imperative mood
|
|
36
|
-
- Be descriptive but concise`}},
|
|
36
|
+
- Be descriptive but concise`}},o=t==="pt"?"pt":"en";return i[o][e]||i[o].conventional}function Se(e){let t={feat:/^(feat|feature)(\([^)]+\))?:/i,fix:/^(fix|bugfix)(\([^)]+\))?:/i,docs:/^(docs|documentation)(\([^)]+\))?:/i,style:/^(style|format)(\([^)]+\))?:/i,refactor:/^(refactor|refactoring)(\([^)]+\))?:/i,test:/^(test|testing)(\([^)]+\))?:/i,chore:/^(chore|maintenance)(\([^)]+\))?:/i,build:/^(build|ci)(\([^)]+\))?:/i,ci:/^(ci|continuous-integration)(\([^)]+\))?:/i};for(let[i,o]of Object.entries(t))if(o.test(e))return i;return null}function we(e,t){let i=e.toLowerCase(),o=t.join(" ").toLowerCase();return o.includes("test")||o.includes("spec")||i.includes("test(")?"test":o.includes("readme")||o.includes(".md")||o.includes("docs")?"docs":o.includes("package.json")||o.includes("dockerfile")||o.includes(".yml")||o.includes(".yaml")||o.includes("webpack")||o.includes("tsconfig")?"build":o.includes(".css")||o.includes(".scss")||i.includes("style")||i.includes("format")?"style":i.includes("fix")||i.includes("bug")||i.includes("error")||i.includes("issue")?"fix":i.includes("add")||i.includes("new")||i.includes("create")||i.includes("implement")?"feat":i.includes("refactor")||i.includes("restructure")||i.includes("rename")?"refactor":"chore"}function $e(e){if(e.startsWith("```")){let t=e.match(/^```[^\n`]*\n([\s\S]*?)\n\s*```([\s\S]*)$/);if(t){let i=t[1].trim(),o=t[2].trim();e=o?`${i}
|
|
37
37
|
|
|
38
|
-
${
|
|
39
|
-
Mensagem: "${e.message}"`,"\u{1F4AD} Sugest\xE3o de Commit");let t=await
|
|
38
|
+
${o}`:i}else e.match(/^```[\s\S]*```$/)&&(e=e.replace(/^```(?:plaintext|javascript|typescript|python|java|html|css|json|xml|yaml|yml|bash|shell|text)?\s*/,"").replace(/\s*```$/,""))}return e=e.trim(),e}async function Ae(e,t,i){try{if(!t.openai.apiKey)return{success:!1,error:"Chave da OpenAI n\xE3o encontrada. Configure OPENAI_API_KEY nas vari\xE1veis de ambiente."};let o=be(e,t,i),n=await fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{Authorization:`Bearer ${t.openai.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({model:t.openai.model,messages:[{role:"user",content:o}],max_tokens:Math.min(t.openai.maxTokens,150),temperature:t.openai.temperature})});if(!n.ok){let l=await n.json().catch(()=>({}));return{success:!1,error:`Erro da OpenAI (${n.status}): ${l.error?.message||"Erro desconhecido"}`}}let a=(await n.json()).choices?.[0]?.message?.content?.trim();if(!a)return{success:!1,error:"OpenAI retornou resposta vazia"};a=$e(a);let c=Se(a),r=we(e,i);return{success:!0,suggestion:{message:a,type:c||r,confidence:.8}}}catch(o){return{success:!1,error:`Erro ao conectar com OpenAI: ${o instanceof Error?o.message:"Erro desconhecido"}`}}}async function T(e,t,i,o=3){let n="";for(let s=0;s<o;s++){let a=await Ae(e,t,i);if(a.success)return a;n=a.error||"Erro desconhecido",s<o-1&&await new Promise(c=>setTimeout(c,Math.pow(2,s)*1e3))}return{success:!1,error:`Falha ap\xF3s ${o} tentativas. \xDAltimo erro: ${n}`}}var Y=w(()=>{"use strict";f()});var X={};G(X,{askContinueCommits:()=>Z,confirmCommit:()=>tt,copyToClipboard:()=>_,editCommitMessage:()=>N,selectFilesForCommit:()=>Q,showCancellation:()=>F,showCommitPreview:()=>L,showCommitResult:()=>v});import{text as Qe,select as Ze,confirm as q,log as P,note as Me,cancel as Xe,isCancel as R}from"@clack/prompts";import et from"clipboardy";async function L(e){Me(`Tipo: ${e.type}
|
|
39
|
+
Mensagem: "${e.message}"`,"\u{1F4AD} Sugest\xE3o de Commit");let t=await Ze({message:"O que voc\xEA gostaria de fazer?",options:[{value:"commit",label:"\u2705 Fazer commit com esta mensagem",hint:"Executar git commit imediatamente"},{value:"edit",label:"\u270F\uFE0F Editar mensagem",hint:"Modificar a mensagem antes de commitar"},{value:"copy",label:"\u{1F4CB} Copiar para clipboard",hint:"Copiar mensagem e sair sem commitar"},{value:"cancel",label:"\u274C Cancelar",hint:"Sair sem fazer nada"}]});return R(t)?{action:"cancel"}:{action:t}}async function N(e){let t=await Qe({message:"Edite a mensagem do commit:",initialValue:e,placeholder:"Digite a mensagem do commit...",validate:o=>{if(!o||o.trim().length===0)return"A mensagem n\xE3o pode estar vazia";if(o.trim().length>72)return"A mensagem est\xE1 muito longa (m\xE1ximo 72 caracteres recomendado)"}});if(R(t))return{action:"cancel"};let i=await q({message:`Confirma a mensagem editada: "${t}"?`});return R(i)||!i?{action:"cancel"}:{action:"commit",message:t}}async function _(e){try{return await et.write(e),P.success("\u2705 Mensagem copiada para a \xE1rea de transfer\xEAncia!"),!0}catch(t){return P.error(`\u274C Erro ao copiar: ${t instanceof Error?t.message:"Erro desconhecido"}`),!1}}async function tt(e){Me(`"${e}"`,"\u{1F680} Confirmar Commit");let t=await q({message:"Executar o commit agora?"});return R(t)?!1:t}function v(e,t,i){e&&t?(P.success("\u2705 Commit realizado com sucesso!"),P.info(`\u{1F517} Hash: ${t.substring(0,8)}`)):P.error(`\u274C Erro ao realizar commit: ${i||"Erro desconhecido"}`)}async function Q(e){P.info("\u{1F4CB} Modo Split: Selecione os arquivos para este commit");let t=[];for(let i of e){let o=await q({message:`Incluir "${i}" neste commit?`});if(R(o))break;o&&t.push(i)}return t}async function Z(e){if(e.length===0)return!1;P.info(`\u{1F4C4} Arquivos restantes: ${e.join(", ")}`);let t=await q({message:"Gerar commit para os arquivos restantes?"});return R(t)?!1:t}function F(){Xe("Opera\xE7\xE3o cancelada pelo usu\xE1rio")}var O=w(()=>{"use strict";f()});var Fe={};G(Fe,{chooseSplitMode:()=>te,confirmGroupCommit:()=>nt,showSmartSplitGroups:()=>ot,showSmartSplitProgress:()=>st});import{select as Pe,confirm as it,log as ze,note as Re,isCancel as ee}from"@clack/prompts";async function te(){let e=await Pe({message:"Como voc\xEA gostaria de organizar os commits?",options:[{value:"smart",label:"\u{1F9E0} Smart Split (Recomendado)",hint:"IA analisa contexto e agrupa automaticamente"},{value:"manual",label:"\u270B Split Manual",hint:"Voc\xEA escolhe arquivos manualmente"},{value:"cancel",label:"\u274C Cancelar",hint:"Voltar ao modo normal"}]});return ee(e)?{action:"cancel"}:e==="manual"?{action:"manual"}:e==="smart"?{action:"proceed"}:{action:"cancel"}}async function ot(e){Re(`Identificamos ${e.length} grupo(s) l\xF3gico(s) para seus commits:
|
|
40
40
|
|
|
41
|
-
`+e.map((o
|
|
42
|
-
\u{1F4C4} ${
|
|
43
|
-
\u{1F4A1} ${
|
|
44
|
-
\u{1F3AF} Confian\xE7a: ${Math.round(
|
|
41
|
+
`+e.map((i,o)=>`${o+1}. **${i.name}**
|
|
42
|
+
\u{1F4C4} ${i.files.join(", ")}
|
|
43
|
+
\u{1F4A1} ${i.description}
|
|
44
|
+
\u{1F3AF} Confian\xE7a: ${Math.round(i.confidence*100)}%`).join(`
|
|
45
45
|
|
|
46
|
-
`),"\u{1F9E0} An\xE1lise de Contexto");let t=await
|
|
46
|
+
`),"\u{1F9E0} An\xE1lise de Contexto");let t=await Pe({message:"O que voc\xEA gostaria de fazer?",options:[{value:"proceed",label:"\u2705 Prosseguir com esta organiza\xE7\xE3o",hint:"Usar os grupos como sugeridos pela IA"},{value:"manual",label:"\u270B Fazer split manual",hint:"Escolher arquivos manualmente"},{value:"cancel",label:"\u274C Cancelar",hint:"Voltar ao modo normal"}]});return ee(t)?{action:"cancel"}:t==="proceed"?{action:"proceed",groups:e}:{action:t}}async function nt(e,t){Re(`**Grupo:** ${e.name}
|
|
47
47
|
**Arquivos:** ${e.files.join(", ")}
|
|
48
|
-
**Mensagem:** "${t}"`,"\u{1F680} Confirmar Commit do Grupo");let
|
|
48
|
+
**Mensagem:** "${t}"`,"\u{1F680} Confirmar Commit do Grupo");let i=await it({message:`Fazer commit para "${e.name}"?`});return ee(i)?!1:i}function st(e,t,i){let o=Math.round(e/t*100),n="\u2588".repeat(Math.floor(o/10))+"\u2591".repeat(10-Math.floor(o/10));ze.info(`\u{1F504} Progresso: [${n}] ${o}% (${e}/${t})`),ze.info(`\u{1F4CB} Processando: ${i}`)}var ie=w(()=>{"use strict";f()});import rt from"crypto";function je(e){Ie=new oe(e)}function Ge(){return Ie}function ke(e,t){let i=Ge();return i?i.get(e,t):{hit:!1}}function Oe(e,t,i){let o=Ge();o&&o.set(e,t,i)}var oe,Ie,ne=w(()=>{"use strict";f();oe=class{cache=new Map;config;constructor(t){this.config=t}generateHash(t,i){let o={files:t.sort(),diff:i.substring(0,1e3),model:this.config.openai.model,temperature:this.config.openai.temperature};return rt.createHash("md5").update(JSON.stringify(o)).digest("hex")}get(t,i){if(!this.config.cache.enabled)return{hit:!1};let o=this.generateHash(t,i),n=this.cache.get(o);if(!n)return{hit:!1};let s=Date.now(),a=this.config.cache.ttl*60*1e3;return s-n.timestamp>a?(this.cache.delete(o),{hit:!1}):{hit:!0,groups:n.groups}}set(t,i,o){if(!this.config.cache.enabled||(this.cache.size>=this.config.cache.maxSize&&this.cleanup(),this.cache.size>=this.config.cache.maxSize))return;let n=this.generateHash(t,i),s={groups:o,timestamp:Date.now(),hash:n};this.cache.set(n,s)}cleanup(){let t=Date.now(),i=this.config.cache.ttl*60*1e3;for(let[o,n]of this.cache.entries())t-n.timestamp>i&&this.cache.delete(o);if(this.cache.size>=this.config.cache.maxSize){let n=Array.from(this.cache.entries()).sort((s,a)=>s[1].timestamp-a[1].timestamp).slice(0,Math.ceil(this.config.cache.maxSize*.5));for(let[s]of n)this.cache.delete(s)}}clear(){this.cache.clear()}getStats(){return{size:this.cache.size,maxSize:this.config.cache.maxSize,enabled:this.config.cache.enabled}}},Ie=null});import{log as g}from"@clack/prompts";function at(e,t){let o=t.length>8e3?t.substring(0,8e3)+`
|
|
49
49
|
... (diff truncado)`:t,n=e.length,s=e.reduce((c,r)=>{let l=r.split(".").pop()||"sem-extensao";return c[l]=(c[l]||0)+1,c},{}),a=Object.entries(s).map(([c,r])=>`${c}: ${r}`).join(", ");return`Analise os arquivos modificados e agrupe em commits l\xF3gicos.
|
|
50
50
|
|
|
51
51
|
ARQUIVOS (${n}): ${e.join(", ")}
|
|
@@ -53,7 +53,7 @@ TIPOS: ${a}
|
|
|
53
53
|
|
|
54
54
|
DIFF RESUMIDO:
|
|
55
55
|
\`\`\`
|
|
56
|
-
${
|
|
56
|
+
${o}
|
|
57
57
|
\`\`\`
|
|
58
58
|
|
|
59
59
|
Agrupe arquivos relacionados. M\xE1ximo 5 grupos. Responda em JSON:
|
|
@@ -67,10 +67,10 @@ Agrupe arquivos relacionados. M\xE1ximo 5 grupos. Responda em JSON:
|
|
|
67
67
|
"confidence": 0.8
|
|
68
68
|
}
|
|
69
69
|
]
|
|
70
|
-
}`}function
|
|
70
|
+
}`}function ct(e){let t=e.reduce((o,n)=>{let s=n.split("/").slice(0,-1).join("/")||"root";return o[s]||(o[s]=[]),o[s].push(n),o},{});return`Agrupe estes arquivos em commits l\xF3gicos baseado nos diret\xF3rios:
|
|
71
71
|
|
|
72
72
|
ARQUIVOS POR DIRET\xD3RIO:
|
|
73
|
-
${Object.entries(t).map(([
|
|
73
|
+
${Object.entries(t).map(([o,n])=>`${o}: ${n.length} arquivo(s)`).join(`
|
|
74
74
|
`)}
|
|
75
75
|
|
|
76
76
|
LISTA COMPLETA: ${e.join(", ")}
|
|
@@ -86,48 +86,54 @@ Agrupe por funcionalidade relacionada. M\xE1ximo 5 grupos. JSON:
|
|
|
86
86
|
"confidence": 0.7
|
|
87
87
|
}
|
|
88
88
|
]
|
|
89
|
-
}`}async function
|
|
90
|
-
... (diff truncado)`:a}catch{return""}}).filter(s=>s.length>0);if(
|
|
89
|
+
}`}async function mt(e,t,i){try{if(!i.openai.apiKey)return{success:!1,error:"Chave da OpenAI n\xE3o encontrada"};let o=ke(e,t);if(o.hit&&o.groups)return{success:!0,groups:o.groups};let s=t.length>6e3,a=s?ct(e):at(e,t);s&&console.warn(`\u26A0\uFE0F Diff muito grande (${t.length} chars), usando an\xE1lise baseada em nomes de arquivos`);let c=await fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{Authorization:`Bearer ${i.openai.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({model:i.openai.model,messages:[{role:"user",content:a}],max_tokens:800,temperature:.3})});if(!c.ok){let p=await c.json().catch(()=>({}));return{success:!1,error:`Erro da OpenAI (${c.status}): ${p.error?.message||"Erro desconhecido"}`}}let l=(await c.json()).choices?.[0]?.message?.content?.trim();if(!l)return{success:!1,error:"OpenAI retornou resposta vazia"};let m=l.match(/\{[\s\S]*\}/);if(!m)return{success:!1,error:"Resposta da OpenAI n\xE3o cont\xE9m JSON v\xE1lido"};let d=JSON.parse(m[0]);if(!d.groups||!Array.isArray(d.groups))return{success:!1,error:"Formato de resposta inv\xE1lido da OpenAI"};let h=d.groups.flatMap(p=>p.files||[]),x=e.filter(p=>!h.includes(p));x.length>0&&(d.groups[0].files=[...d.groups[0].files||[],...x]);let y=d.groups.map(p=>({id:p.id||`group-${Math.random().toString(36).substr(2,9)}`,name:p.name||"Grupo sem nome",description:p.description||"Sem descri\xE7\xE3o",files:p.files||[],diff:"",confidence:p.confidence||.5}));return Oe(e,t,y),{success:!0,groups:y}}catch(o){return{success:!1,error:`Erro ao analisar contexto: ${o instanceof Error?o.message:"Erro desconhecido"}`}}}async function lt(e){let{getFileDiff:t}=await Promise.resolve().then(()=>($(),M)),i=e.files.map(s=>{try{let a=t(s),c=4e3;return a.length>c?a.substring(0,c)+`
|
|
90
|
+
... (diff truncado)`:a}catch{return""}}).filter(s=>s.length>0);if(i.length===0&&e.files.length>0){let{execSync:s}=await import("child_process"),a=e.files.filter(r=>{try{return s(`test -f "${r}"`,{stdio:"ignore"}),s(`git status --porcelain -- "${r}"`,{encoding:"utf-8",stdio:"pipe"}).trim().startsWith("??")}catch{return!1}});if(a.length>0)return a.map(r=>{try{let l=s(`cat "${r}"`,{encoding:"utf-8",stdio:"pipe"}),m=2e3,d=l.length>m?l.substring(0,m)+`
|
|
91
91
|
... (conte\xFAdo truncado)`:l;return`diff --git a/${r} b/${r}
|
|
92
92
|
new file mode 100644
|
|
93
93
|
index 0000000..${Math.random().toString(36).substr(2,7)}
|
|
94
94
|
--- /dev/null
|
|
95
95
|
+++ b/${r}
|
|
96
|
-
@@ -0,0 +1,${
|
|
96
|
+
@@ -0,0 +1,${d.split(`
|
|
97
97
|
`).length} @@
|
|
98
|
-
${
|
|
98
|
+
${d.split(`
|
|
99
99
|
`).map(h=>`+${h}`).join(`
|
|
100
100
|
`)}`}catch{return""}}).filter(r=>r.length>0).join(`
|
|
101
101
|
`);let c=e.files.filter(r=>{try{return s(`test -f "${r}"`,{stdio:"ignore"}),s("git diff --cached --name-only",{encoding:"utf-8",stdio:"pipe"}).trim().split(`
|
|
102
|
-
`).includes(r)}catch{return!1}});if(c.length>0)return c.map(r=>{try{let l=s(`cat "${r}"`,{encoding:"utf-8",stdio:"pipe"}),m=2e3,
|
|
102
|
+
`).includes(r)}catch{return!1}});if(c.length>0)return c.map(r=>{try{let l=s(`cat "${r}"`,{encoding:"utf-8",stdio:"pipe"}),m=2e3,d=l.length>m?l.substring(0,m)+`
|
|
103
103
|
... (conte\xFAdo truncado)`:l;return`diff --git a/${r} b/${r}
|
|
104
104
|
index 0000000..${Math.random().toString(36).substr(2,7)} 100644
|
|
105
105
|
--- a/${r}
|
|
106
106
|
+++ b/${r}
|
|
107
|
-
@@ -1 +1,${
|
|
107
|
+
@@ -1 +1,${d.split(`
|
|
108
108
|
`).length} @@
|
|
109
|
-
${
|
|
109
|
+
${d.split(`
|
|
110
110
|
`).map(h=>`+${h}`).join(`
|
|
111
111
|
`)}`}catch{return""}}).filter(r=>r.length>0).join(`
|
|
112
|
-
`)}let i
|
|
113
|
-
`),n=8e3;return
|
|
114
|
-
... (diff total truncado)`:
|
|
115
|
-
\u{1F504} Processando grupo ${n+1}/${
|
|
116
|
-
`)
|
|
112
|
+
`)}let o=i.join(`
|
|
113
|
+
`),n=8e3;return o.length>n?o.substring(0,n)+`
|
|
114
|
+
... (diff total truncado)`:o}async function se(e,t,i){i.silent||g.info("\u{1F9E0} Modo Smart Split ativado - Agrupando arquivos por contexto"),i.silent||g.info("\u{1F916} Analisando contexto das mudan\xE7as...");let o=await mt(e.stagedFiles,e.diff,t);if(!o.success){i.silent||g.error(`\u274C Erro na an\xE1lise de contexto: ${o.error}`);return}if(!o.groups||o.groups.length===0){i.silent||g.error("\u274C Nenhum grupo foi criado pela an\xE1lise");return}if(i.silent||(g.success(`\u2705 ${o.groups.length} grupo(s) identificado(s):`),o.groups.forEach((n,s)=>{g.info(` ${s+1}. ${n.name} (${n.files.length} arquivo(s))`),g.info(` \u{1F4C4} ${n.files.join(", ")}`)})),!i.yes&&!i.silent){let{showSmartSplitGroups:n}=await Promise.resolve().then(()=>(ie(),Fe)),s=await n(o.groups);if(s.action==="cancel"){i.silent||g.info("\u274C Opera\xE7\xE3o cancelada pelo usu\xE1rio");return}if(s.action==="manual"){let a={...i,split:!0,smartSplit:!1},{main:c}=await Promise.resolve().then(()=>(re(),Te));await c(a);return}}for(let n=0;n<o.groups.length;n++){let s=o.groups[n];if(!s){i.silent||g.error(`\u274C Grupo ${n+1} \xE9 undefined`);continue}i.silent||g.info(`
|
|
115
|
+
\u{1F504} Processando grupo ${n+1}/${o.groups.length}: ${s.name}`);let a=await lt(s);if(!a){i.silent||(g.warn(`\u26A0\uFE0F Nenhum diff encontrado para o grupo: ${s.name}`),g.info(` \u{1F4C4} Arquivos: ${s.files.join(", ")}`),g.info(" \u{1F4A1} Poss\xEDvel causa: arquivos novos, deletados/recriados, ou sem mudan\xE7as"));continue}i.silent||g.info(`\u{1F916} Gerando commit para: ${s.name}`);let{generateWithRetry:c}=await Promise.resolve().then(()=>(Y(),Ee)),r=await c(a,t,s.files);if(!r.success){i.silent||g.error(`\u274C Erro ao gerar commit para ${s.name}: ${r.error}`);continue}if(!r.suggestion){i.silent||g.error(`\u274C Nenhuma sugest\xE3o gerada para ${s.name}`);continue}if(t.dryRun){i.silent||(g.info(`\u{1F50D} Dry Run - Grupo: ${s.name}`),g.info(`\u{1F4C4} Arquivos: ${s.files.join(", ")}`),g.info(`\u{1F4AD} Mensagem: "${r.suggestion.message}"`));continue}if(i.yes){let{executeFileCommit:l}=await Promise.resolve().then(()=>($(),M)),m;if(s.files.length===1&&s.files[0])m=l(s.files[0],r.suggestion.message||"");else{let{execSync:d}=await import("child_process"),{escapeShellArg:h}=await Promise.resolve().then(()=>($(),M));try{let x=s.files.map(C=>h(C)).join(" "),y=h(r.suggestion.message||"");d(`git commit ${x} -m ${y}`,{stdio:"pipe"}),m={success:!0,hash:d("git rev-parse HEAD",{encoding:"utf-8",stdio:"pipe"}).trim(),message:r.suggestion.message||""}}catch(x){m={success:!1,error:x instanceof Error?x.message:"Erro desconhecido ao executar commit"}}}v(m.success,m.hash,m.error)}else{let{showCommitPreview:l,editCommitMessage:m,copyToClipboard:d,showCancellation:h}=await Promise.resolve().then(()=>(O(),X));switch((await l(r.suggestion)).action){case"commit":{let{executeFileCommit:y}=await Promise.resolve().then(()=>($(),M)),p,C=r.suggestion.message||"Atualiza\xE7\xE3o de arquivos";if(s.files.length===1&&s.files[0])p=y(s.files[0],C);else{let{execSync:I}=await import("child_process"),{escapeShellArg:j}=await Promise.resolve().then(()=>($(),M));try{let A=s.files.map(W=>j(W)).join(" "),H=j(C);I(`git commit ${A} -m ${H}`,{stdio:"pipe"}),p={success:!0,hash:I("git rev-parse HEAD",{encoding:"utf-8",stdio:"pipe"}).trim(),message:C}}catch(A){p={success:!1,error:A instanceof Error?A.message:"Erro desconhecido ao executar commit"}}}v(p.success,p.hash,p.error);break}case"edit":{let y=await m(r.suggestion.message);if(y.action==="commit"&&y.message){let{executeFileCommit:p}=await Promise.resolve().then(()=>($(),M)),C;if(s.files.length===1&&s.files[0])C=p(s.files[0],y.message||"");else{let{execSync:I}=await import("child_process"),{escapeShellArg:j}=await Promise.resolve().then(()=>($(),M));try{let A=s.files.map(W=>j(W)).join(" "),H=j(y.message||"");I(`git commit ${A} -m ${H}`,{stdio:"pipe"}),C={success:!0,hash:I("git rev-parse HEAD",{encoding:"utf-8",stdio:"pipe"}).trim(),message:y.message||""}}catch(A){C={success:!1,error:A instanceof Error?A.message:"Erro desconhecido ao executar commit"}}}v(C.success,C.hash,C.error)}break}case"copy":{await d(r.suggestion.message),i.silent||g.info("\u{1F3AF} Mensagem copiada para clipboard");break}case"cancel":{h();return}}}if(n<o.groups.length-1&&!i.yes){let{askContinueCommits:l}=await Promise.resolve().then(()=>(O(),X)),m=o.groups.slice(n+1).filter(h=>h!==void 0).map(h=>h.name);if(!await l(m))break}}i.silent||g.success("\u2705 Smart Split conclu\xEDdo!")}var De=w(()=>{"use strict";f();ne();O()});import{existsSync as ae,writeFileSync as ut}from"fs";import{join as U}from"path";import{spawnSync as ft}from"child_process";function qe(e){let t=e||process.cwd();for(let i of dt){let o=U(t,i);if(ae(o))return o}return null}function ce(e){let t=e||process.cwd(),i=U(t,"node_modules",".bin","commitlint");return ae(i)}function Le(e,t){let i=t||process.cwd(),o=U(i,"node_modules",".bin","commitlint"),n=ft(o,[],{input:e,encoding:"utf-8",cwd:i});if(n.status===0)return{valid:!0,errors:[],warnings:[]};let a=((n.stdout||"")+(n.stderr||"")).split(`
|
|
116
|
+
`),c=[],r=[];for(let l of a){let m=l.trim();m&&(m.startsWith("\u2716")||m.includes("[error]")?c.push(m):(m.startsWith("\u26A0")||m.includes("[warning]"))&&r.push(m))}if(c.length===0&&n.status!==0){let l=a.map(m=>m.trim()).filter(m=>m.length>0&&!m.startsWith("\u29D7")&&!m.startsWith("\u2714"));c.push(...l)}return{valid:!1,errors:c,warnings:r}}function Ne(e){let t=e||process.cwd(),i=U(t,"commitlint.config.js");if(ae(i))throw new Error("commitlint.config.js j\xE1 existe neste diret\xF3rio. Remova-o antes de continuar.");ut(i,`export default {
|
|
117
|
+
extends: ['@commitlint/config-conventional'],
|
|
118
|
+
};
|
|
119
|
+
`,"utf-8")}var dt,me=w(()=>{"use strict";f();dt=["commitlint.config.js","commitlint.config.ts","commitlint.config.mjs","commitlint.config.cjs",".commitlintrc",".commitlintrc.js",".commitlintrc.json",".commitlintrc.yml",".commitlintrc.yaml"]});var Te={};G(Te,{main:()=>ue});import{log as u}from"@clack/prompts";function pt(e,t){return!t.commitlint.enabled||!ce()?!1:e.validate?!0:qe()!==null}function le(e,t){let i=Le(e);return i.valid?(t||u.success("\u2705 Mensagem validada pelo commitlint!"),!0):(u.error("\u274C A mensagem n\xE3o passou na valida\xE7\xE3o do commitlint:"),i.errors.forEach(o=>u.error(` ${o}`)),i.warnings.length>0&&i.warnings.forEach(o=>u.info(` \u26A0\uFE0F ${o}`)),!1)}async function ue(e={silent:!1,yes:!1,auto:!1,split:!1,smartSplit:!1,dryRun:!1,help:!1,version:!1,validate:!1,initCommitlint:!1}){e.silent||u.info("\u{1F680} Commit Wizard iniciado!"),J()||(u.error("\u274C N\xE3o foi encontrado um reposit\xF3rio Git neste diret\xF3rio."),e.silent||u.info("\u{1F4A1} Execute o comando em um diret\xF3rio com reposit\xF3rio Git inicializado."),process.exit(1)),e.silent||u.info("\u2699\uFE0F Carregando configura\xE7\xE3o...");let t=ye();je(t),e.split&&(t.splitCommits=!0),e.dryRun&&(t.dryRun=!0);let i=Ce(t);i.length>0&&(u.error("\u274C Erros na configura\xE7\xE3o:"),i.forEach(c=>u.error(` \u2022 ${c}`)),process.exit(1)),e.silent||u.success(`\u2705 Configura\xE7\xE3o carregada (modelo: ${t.openai.model}, idioma: ${t.language})`);let o=pt(e,t);e.silent||(e.validate&&!ce()?u.info("\u26A0\uFE0F --validate especificado mas commitlint n\xE3o est\xE1 instalado. Valida\xE7\xE3o ignorada."):o&&u.info("\u{1F50D} Valida\xE7\xE3o commitlint ativa")),e.silent||u.info("\u{1F4CB} Verificando arquivos staged...");let n=K();n.hasStaged||(u.warn("\u26A0\uFE0F Nenhum arquivo foi encontrado no stage."),e.silent||u.info("\u{1F4A1} Use `git add <arquivo>` para adicionar arquivos ao stage antes de gerar o commit."),process.exit(0));let s=B();if(e.silent||(u.success(`\u2705 Encontrados ${n.stagedFiles.length} arquivo(s) staged:`),n.stagedFiles.forEach(c=>u.info(` \u{1F4C4} ${c}`)),u.info(`\u{1F4CA} Estat\xEDsticas: +${s.added} -${s.removed} linhas`)),t.splitCommits||e.smartSplit){if(e.yes)return await se(n,t,e);switch((await te()).action){case"proceed":return await se(n,t,e);case"manual":return await gt(n,t,e);case"cancel":F();return}}e.silent||u.info("\u{1F916} Gerando mensagem de commit com IA...");let a=await T(n.diff,t,n.stagedFiles);if(a.success||(u.error(`\u274C Erro ao gerar commit: ${a.error}`),process.exit(1)),a.suggestion||(u.error("\u274C Nenhuma sugest\xE3o foi gerada"),process.exit(1)),e.silent||u.success("\u2728 Mensagem de commit gerada!"),t.dryRun){u.info("\u{1F50D} Modo Dry Run - Mensagem gerada:"),u.info(`"${a.suggestion.message}"`),u.info("\u{1F4A1} Execute sem --dry-run para fazer o commit");return}if(e.yes){o&&(le(a.suggestion.message,e.silent)||process.exit(1));let c=z(a.suggestion.message);v(c.success,c.hash,c.error);return}for(;;)switch((await L(a.suggestion)).action){case"commit":{if(o&&!le(a.suggestion.message,e.silent))break;let r=z(a.suggestion.message);v(r.success,r.hash,r.error);return}case"edit":{let r=await N(a.suggestion.message);if(r.action==="cancel"){F();return}if(r.action==="commit"&&r.message){if(o&&!le(r.message,e.silent))break;let l=z(r.message);v(l.success,l.hash,l.error);return}break}case"copy":{await _(a.suggestion.message),e.silent||u.info('\u{1F3AF} Voc\xEA pode usar a mensagem copiada com: git commit -m "mensagem"');return}case"cancel":{F();return}}}async function gt(e,t,i){i.silent||u.info("\u{1F504} Modo Split ativado - Commits separados por arquivo");let o=[...e.stagedFiles];for(;o.length>0;){let n=i.yes?[o[0]]:await Q(o);if(n.length===0){i.silent||u.info("\u274C Nenhum arquivo selecionado");break}let{getFileDiff:s}=await Promise.resolve().then(()=>($(),M)),a=n.filter(r=>r!==void 0).map(r=>{try{return s(r)}catch(l){return u.error(`\u274C Erro ao obter diff do arquivo ${r}: ${l instanceof Error?l.message:"Erro desconhecido"}`),""}}).filter(r=>r.length>0).join(`
|
|
120
|
+
`);if(!a){i.silent||u.warn("\u26A0\uFE0F Nenhum diff encontrado para os arquivos selecionados"),o=o.filter(r=>!n.includes(r));continue}i.silent||u.info(`\u{1F916} Gerando commit para: ${n.join(", ")}`);let c=await T(a,t,n.filter(r=>r!==void 0));if(!c.success){u.error(`\u274C Erro ao gerar commit: ${c.error}`),o=o.filter(r=>!n.includes(r));continue}if(!c.suggestion){u.error("\u274C Nenhuma sugest\xE3o foi gerada"),o=o.filter(r=>!n.includes(r));continue}if(t.dryRun){u.info(`\u{1F50D} Dry Run - Mensagem para ${n.join(", ")}:`),u.info(`"${c.suggestion.message}"`),o=o.filter(r=>!n.includes(r));continue}if(i.yes){let r=n.length===1&&n[0]?await k(n[0],c.suggestion.message):await z(c.suggestion.message);v(r.success,r.hash,r.error)}else{let r=await L(c.suggestion);if(r.action==="commit"){let l=n.length===1&&n[0]?await k(n[0],c.suggestion.message):await z(c.suggestion.message);v(l.success,l.hash,l.error)}else if(r.action==="edit"){let l=await N(c.suggestion.message);if(l.action==="commit"&&l.message){let m=n.length===1&&n[0]?await k(n[0],l.message):await z(l.message);v(m.success,m.hash,m.error)}}else if(r.action==="copy")await _(c.suggestion.message),i.silent||u.info("\u{1F3AF} Mensagem copiada para clipboard");else if(r.action==="cancel"){F();return}}if(o=o.filter(r=>!n.includes(r)),o.length>0&&!i.yes&&!await Z(o))break}i.silent||u.success("\u2705 Modo Split conclu\xEDdo!")}var re=w(()=>{"use strict";f();ve();$();Y();O();ie();De();ne();me()});f();re();import{intro as vt,outro as xt,log as V}from"@clack/prompts";f();f();import{readFileSync as ht}from"fs";import{join as fe,dirname as yt}from"path";import{fileURLToPath as Ct}from"url";function _e(){try{let e=Ct(import.meta.url),t=yt(e),i=[fe(t,"..","package.json"),fe(t,"..","..","package.json"),fe(process.cwd(),"package.json")];for(let o of i)try{let n=ht(o,"utf-8"),s=JSON.parse(n);if(s.name==="@gilbert_oliveira/commit-wizard")return s.version}catch{continue}throw new Error("Package.json n\xE3o encontrado")}catch{return console.warn("\u26A0\uFE0F N\xE3o foi poss\xEDvel ler a vers\xE3o do package.json, usando vers\xE3o padr\xE3o"),"0.0.0"}}function Ue(e){return{silent:e.includes("--silent")||e.includes("-s"),yes:e.includes("--yes")||e.includes("-y"),auto:e.includes("--auto")||e.includes("-a"),split:e.includes("--split"),smartSplit:e.includes("--smart-split"),dryRun:e.includes("--dry-run")||e.includes("-n"),help:e.includes("--help")||e.includes("-h"),version:e.includes("--version")||e.includes("-v"),validate:e.includes("--validate"),initCommitlint:e.includes("--init-commitlint")}}function Ve(){console.log(`
|
|
117
121
|
\u{1F9D9}\u200D\u2642\uFE0F Commit Wizard - Gerador inteligente de mensagens de commit
|
|
118
122
|
|
|
119
123
|
USAGE:
|
|
120
124
|
commit-wizard [OPTIONS]
|
|
121
125
|
|
|
122
126
|
OPTIONS:
|
|
123
|
-
-s, --silent
|
|
124
|
-
-y, --yes
|
|
125
|
-
-a, --auto
|
|
126
|
-
--split
|
|
127
|
-
--smart-split
|
|
128
|
-
-n, --dry-run
|
|
129
|
-
|
|
130
|
-
-
|
|
127
|
+
-s, --silent Modo silencioso (sem logs detalhados)
|
|
128
|
+
-y, --yes Confirmar automaticamente sem prompts
|
|
129
|
+
-a, --auto Modo autom\xE1tico (--yes + --silent)
|
|
130
|
+
--split Modo split manual (commits separados por arquivo)
|
|
131
|
+
--smart-split Modo smart split (IA agrupa por contexto)
|
|
132
|
+
-n, --dry-run Visualizar mensagem sem fazer commit
|
|
133
|
+
--validate Habilitar valida\xE7\xE3o com commitlint
|
|
134
|
+
--init-commitlint Criar configura\xE7\xE3o padr\xE3o do commitlint
|
|
135
|
+
-h, --help Mostrar esta ajuda
|
|
136
|
+
-v, --version Mostrar vers\xE3o
|
|
131
137
|
|
|
132
138
|
EXAMPLES:
|
|
133
139
|
commit-wizard # Modo interativo padr\xE3o
|
|
@@ -136,6 +142,8 @@ EXAMPLES:
|
|
|
136
142
|
commit-wizard --smart-split # Smart split com IA
|
|
137
143
|
commit-wizard --dry-run # Apenas visualizar mensagem
|
|
138
144
|
commit-wizard --auto # Modo totalmente autom\xE1tico
|
|
145
|
+
commit-wizard --validate # Habilitar valida\xE7\xE3o com commitlint
|
|
146
|
+
commit-wizard --init-commitlint # Criar configura\xE7\xE3o padr\xE3o do commitlint
|
|
139
147
|
|
|
140
148
|
Para mais informa\xE7\xF5es, visite: https://github.com/user/commit-wizard
|
|
141
|
-
`)}function
|
|
149
|
+
`)}function He(){let e=_e();console.log(`commit-wizard v${e}`)}me();async function bt(){try{let e=Ue(process.argv.slice(2));if(e.help&&(Ve(),process.exit(0)),e.version&&(He(),process.exit(0)),e.initCommitlint){try{Ne(),V.success("\u2705 commitlint.config.js criado com sucesso!"),V.info("\u{1F4A1} Instale as depend\xEAncias necess\xE1rias: npm install --save-dev @commitlint/cli @commitlint/config-conventional")}catch(t){V.error(`\u274C ${t instanceof Error?t.message:"Erro ao criar configura\xE7\xE3o do commitlint"}`),process.exit(1)}process.exit(0)}e.auto&&(e.silent=!0,e.yes=!0),e.silent||vt("\u{1F9D9}\u200D\u2642\uFE0F Commit Wizard"),await ue(e),e.silent||xt("At\xE9 logo! \u2728")}catch(e){V.error(`Erro: ${e instanceof Error?e.message:"Erro desconhecido"}`),process.exit(1)}}bt();
|
package/package.json
CHANGED
package/src/bin/commit-wizard.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { intro, outro, log } from '@clack/prompts';
|
|
4
4
|
import { main } from '@core/index';
|
|
5
5
|
import { parseArgs, showHelp, showVersion } from '../utils/args';
|
|
6
|
+
import { initCommitlintConfig } from '../commitlint/index';
|
|
6
7
|
|
|
7
8
|
async function run() {
|
|
8
9
|
try {
|
|
@@ -21,6 +22,25 @@ async function run() {
|
|
|
21
22
|
process.exit(0);
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
// Inicializar configuração do commitlint
|
|
26
|
+
if (args.initCommitlint) {
|
|
27
|
+
try {
|
|
28
|
+
initCommitlintConfig();
|
|
29
|
+
log.success(
|
|
30
|
+
'✅ commitlint.config.js criado com sucesso!'
|
|
31
|
+
);
|
|
32
|
+
log.info(
|
|
33
|
+
'💡 Instale as dependências necessárias: npm install --save-dev @commitlint/cli @commitlint/config-conventional'
|
|
34
|
+
);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
log.error(
|
|
37
|
+
`❌ ${error instanceof Error ? error.message : 'Erro ao criar configuração do commitlint'}`
|
|
38
|
+
);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
// Modo automático = silent + yes
|
|
25
45
|
if (args.auto) {
|
|
26
46
|
args.silent = true;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { spawnSync } from 'child_process';
|
|
4
|
+
|
|
5
|
+
export interface CommitlintResult {
|
|
6
|
+
valid: boolean;
|
|
7
|
+
errors: string[];
|
|
8
|
+
warnings: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const COMMITLINT_CONFIG_FILES = [
|
|
12
|
+
'commitlint.config.js',
|
|
13
|
+
'commitlint.config.ts',
|
|
14
|
+
'commitlint.config.mjs',
|
|
15
|
+
'commitlint.config.cjs',
|
|
16
|
+
'.commitlintrc',
|
|
17
|
+
'.commitlintrc.js',
|
|
18
|
+
'.commitlintrc.json',
|
|
19
|
+
'.commitlintrc.yml',
|
|
20
|
+
'.commitlintrc.yaml',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export function findCommitlintConfig(cwd?: string): string | null {
|
|
24
|
+
const dir = cwd || process.cwd();
|
|
25
|
+
for (const file of COMMITLINT_CONFIG_FILES) {
|
|
26
|
+
const fullPath = join(dir, file);
|
|
27
|
+
if (existsSync(fullPath)) {
|
|
28
|
+
return fullPath;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isCommitlintInstalled(cwd?: string): boolean {
|
|
35
|
+
const dir = cwd || process.cwd();
|
|
36
|
+
const binPath = join(dir, 'node_modules', '.bin', 'commitlint');
|
|
37
|
+
return existsSync(binPath);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function validateCommitMessage(
|
|
41
|
+
message: string,
|
|
42
|
+
cwd?: string
|
|
43
|
+
): CommitlintResult {
|
|
44
|
+
const dir = cwd || process.cwd();
|
|
45
|
+
const binPath = join(dir, 'node_modules', '.bin', 'commitlint');
|
|
46
|
+
|
|
47
|
+
const result = spawnSync(binPath, [], {
|
|
48
|
+
input: message,
|
|
49
|
+
encoding: 'utf-8',
|
|
50
|
+
cwd: dir,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (result.status === 0) {
|
|
54
|
+
return { valid: true, errors: [], warnings: [] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const output = (result.stdout || '') + (result.stderr || '');
|
|
58
|
+
const lines = output.split('\n');
|
|
59
|
+
const errors: string[] = [];
|
|
60
|
+
const warnings: string[] = [];
|
|
61
|
+
|
|
62
|
+
for (const line of lines) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
if (!trimmed) continue;
|
|
65
|
+
if (trimmed.startsWith('✖') || trimmed.includes('[error]')) {
|
|
66
|
+
errors.push(trimmed);
|
|
67
|
+
} else if (trimmed.startsWith('⚠') || trimmed.includes('[warning]')) {
|
|
68
|
+
warnings.push(trimmed);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// If we couldn't parse individual errors, include raw output
|
|
73
|
+
if (errors.length === 0 && result.status !== 0) {
|
|
74
|
+
const rawErrors = lines
|
|
75
|
+
.map((l) => l.trim())
|
|
76
|
+
.filter((l) => l.length > 0 && !l.startsWith('⧗') && !l.startsWith('✔'));
|
|
77
|
+
errors.push(...rawErrors);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { valid: false, errors, warnings };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function initCommitlintConfig(cwd?: string): void {
|
|
84
|
+
const dir = cwd || process.cwd();
|
|
85
|
+
const configPath = join(dir, 'commitlint.config.js');
|
|
86
|
+
|
|
87
|
+
if (existsSync(configPath)) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
'commitlint.config.js já existe neste diretório. Remova-o antes de continuar.'
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const content = `export default {
|
|
94
|
+
extends: ['@commitlint/config-conventional'],
|
|
95
|
+
};
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
writeFileSync(configPath, content, 'utf-8');
|
|
99
|
+
}
|
package/src/config/index.ts
CHANGED
|
@@ -30,6 +30,9 @@ export interface Config {
|
|
|
30
30
|
ttl: number; // Time to live em minutos
|
|
31
31
|
maxSize: number; // Número máximo de entradas
|
|
32
32
|
};
|
|
33
|
+
commitlint: {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
};
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
const DEFAULT_CONFIG: Config = {
|
|
@@ -56,6 +59,9 @@ const DEFAULT_CONFIG: Config = {
|
|
|
56
59
|
ttl: 60, // 1 hora
|
|
57
60
|
maxSize: 100,
|
|
58
61
|
},
|
|
62
|
+
commitlint: {
|
|
63
|
+
enabled: true,
|
|
64
|
+
},
|
|
59
65
|
};
|
|
60
66
|
|
|
61
67
|
// Adicionar interface para userConfig
|
|
@@ -68,6 +74,7 @@ interface UserConfig {
|
|
|
68
74
|
dryRun?: boolean;
|
|
69
75
|
smartSplit?: Partial<Config['smartSplit']>;
|
|
70
76
|
cache?: Partial<Config['cache']>;
|
|
77
|
+
commitlint?: Partial<Config['commitlint']>;
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
export function loadConfig(configPath?: string): Config {
|
|
@@ -142,6 +149,10 @@ function mergeConfig(defaultConfig: Config, userConfig: UserConfig): Config {
|
|
|
142
149
|
...defaultConfig.cache,
|
|
143
150
|
...userConfig.cache,
|
|
144
151
|
},
|
|
152
|
+
commitlint: {
|
|
153
|
+
...defaultConfig.commitlint,
|
|
154
|
+
...userConfig.commitlint,
|
|
155
|
+
},
|
|
145
156
|
};
|
|
146
157
|
}
|
|
147
158
|
|
package/src/core/index.ts
CHANGED
|
@@ -22,6 +22,45 @@ import { handleSmartSplitMode } from './smart-split';
|
|
|
22
22
|
import { initializeCache } from './cache';
|
|
23
23
|
import type { CLIArgs } from '../utils/args';
|
|
24
24
|
import type { Config } from '../config/index';
|
|
25
|
+
import {
|
|
26
|
+
isCommitlintInstalled,
|
|
27
|
+
findCommitlintConfig,
|
|
28
|
+
validateCommitMessage,
|
|
29
|
+
} from '../commitlint/index';
|
|
30
|
+
|
|
31
|
+
function shouldRunCommitlint(args: CLIArgs, config: Config): boolean {
|
|
32
|
+
// Explicitly disabled in config
|
|
33
|
+
if (!config.commitlint.enabled) return false;
|
|
34
|
+
|
|
35
|
+
// Commitlint binary must be installed
|
|
36
|
+
if (!isCommitlintInstalled()) return false;
|
|
37
|
+
|
|
38
|
+
// Explicitly enabled via --validate flag
|
|
39
|
+
if (args.validate) return true;
|
|
40
|
+
|
|
41
|
+
// Auto-detect: validate if a commitlint config file exists
|
|
42
|
+
return findCommitlintConfig() !== null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function runCommitlintValidation(
|
|
46
|
+
message: string,
|
|
47
|
+
silent: boolean
|
|
48
|
+
): boolean {
|
|
49
|
+
const result = validateCommitMessage(message);
|
|
50
|
+
if (result.valid) {
|
|
51
|
+
if (!silent) {
|
|
52
|
+
log.success('✅ Mensagem validada pelo commitlint!');
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
log.error('❌ A mensagem não passou na validação do commitlint:');
|
|
58
|
+
result.errors.forEach((err) => log.error(` ${err}`));
|
|
59
|
+
if (result.warnings.length > 0) {
|
|
60
|
+
result.warnings.forEach((warn) => log.info(` ⚠️ ${warn}`));
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
25
64
|
|
|
26
65
|
export async function main(
|
|
27
66
|
args: CLIArgs = {
|
|
@@ -33,6 +72,8 @@ export async function main(
|
|
|
33
72
|
dryRun: false,
|
|
34
73
|
help: false,
|
|
35
74
|
version: false,
|
|
75
|
+
validate: false,
|
|
76
|
+
initCommitlint: false,
|
|
36
77
|
}
|
|
37
78
|
) {
|
|
38
79
|
if (!args.silent) {
|
|
@@ -81,6 +122,18 @@ export async function main(
|
|
|
81
122
|
);
|
|
82
123
|
}
|
|
83
124
|
|
|
125
|
+
// Informar sobre validação commitlint
|
|
126
|
+
const useCommitlint = shouldRunCommitlint(args, config);
|
|
127
|
+
if (!args.silent) {
|
|
128
|
+
if (args.validate && !isCommitlintInstalled()) {
|
|
129
|
+
log.info(
|
|
130
|
+
'⚠️ --validate especificado mas commitlint não está instalado. Validação ignorada.'
|
|
131
|
+
);
|
|
132
|
+
} else if (useCommitlint) {
|
|
133
|
+
log.info('🔍 Validação commitlint ativa');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
84
137
|
// Verificar arquivos staged
|
|
85
138
|
if (!args.silent) {
|
|
86
139
|
log.info('📋 Verificando arquivos staged...');
|
|
@@ -164,6 +217,15 @@ export async function main(
|
|
|
164
217
|
|
|
165
218
|
// Modo automático: commit direto
|
|
166
219
|
if (args.yes) {
|
|
220
|
+
if (useCommitlint) {
|
|
221
|
+
const valid = runCommitlintValidation(
|
|
222
|
+
result.suggestion.message,
|
|
223
|
+
args.silent
|
|
224
|
+
);
|
|
225
|
+
if (!valid) {
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
167
229
|
const commitResult = executeCommit(result.suggestion.message);
|
|
168
230
|
showCommitResult(
|
|
169
231
|
commitResult.success,
|
|
@@ -179,6 +241,14 @@ export async function main(
|
|
|
179
241
|
|
|
180
242
|
switch (uiAction.action) {
|
|
181
243
|
case 'commit': {
|
|
244
|
+
// Validar com commitlint se necessário
|
|
245
|
+
if (useCommitlint) {
|
|
246
|
+
const valid = runCommitlintValidation(
|
|
247
|
+
result.suggestion.message,
|
|
248
|
+
args.silent
|
|
249
|
+
);
|
|
250
|
+
if (!valid) break; // Voltar ao menu para editar
|
|
251
|
+
}
|
|
182
252
|
// Commit direto com mensagem gerada
|
|
183
253
|
const commitResult = executeCommit(result.suggestion.message);
|
|
184
254
|
showCommitResult(
|
|
@@ -196,6 +266,13 @@ export async function main(
|
|
|
196
266
|
return;
|
|
197
267
|
}
|
|
198
268
|
if (editAction.action === 'commit' && editAction.message) {
|
|
269
|
+
if (useCommitlint) {
|
|
270
|
+
const valid = runCommitlintValidation(
|
|
271
|
+
editAction.message,
|
|
272
|
+
args.silent
|
|
273
|
+
);
|
|
274
|
+
if (!valid) break; // Voltar ao menu para editar
|
|
275
|
+
}
|
|
199
276
|
const editCommitResult = executeCommit(editAction.message);
|
|
200
277
|
showCommitResult(
|
|
201
278
|
editCommitResult.success,
|
package/src/utils/args.ts
CHANGED
|
@@ -7,6 +7,8 @@ export interface CLIArgs {
|
|
|
7
7
|
dryRun: boolean;
|
|
8
8
|
help: boolean;
|
|
9
9
|
version: boolean;
|
|
10
|
+
validate: boolean;
|
|
11
|
+
initCommitlint: boolean;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export function parseArgs(args: string[]): CLIArgs {
|
|
@@ -19,6 +21,8 @@ export function parseArgs(args: string[]): CLIArgs {
|
|
|
19
21
|
dryRun: args.includes('--dry-run') || args.includes('-n'),
|
|
20
22
|
help: args.includes('--help') || args.includes('-h'),
|
|
21
23
|
version: args.includes('--version') || args.includes('-v'),
|
|
24
|
+
validate: args.includes('--validate'),
|
|
25
|
+
initCommitlint: args.includes('--init-commitlint'),
|
|
22
26
|
};
|
|
23
27
|
}
|
|
24
28
|
|
|
@@ -30,14 +34,16 @@ USAGE:
|
|
|
30
34
|
commit-wizard [OPTIONS]
|
|
31
35
|
|
|
32
36
|
OPTIONS:
|
|
33
|
-
-s, --silent
|
|
34
|
-
-y, --yes
|
|
35
|
-
-a, --auto
|
|
36
|
-
--split
|
|
37
|
-
--smart-split
|
|
38
|
-
-n, --dry-run
|
|
39
|
-
|
|
40
|
-
-
|
|
37
|
+
-s, --silent Modo silencioso (sem logs detalhados)
|
|
38
|
+
-y, --yes Confirmar automaticamente sem prompts
|
|
39
|
+
-a, --auto Modo automático (--yes + --silent)
|
|
40
|
+
--split Modo split manual (commits separados por arquivo)
|
|
41
|
+
--smart-split Modo smart split (IA agrupa por contexto)
|
|
42
|
+
-n, --dry-run Visualizar mensagem sem fazer commit
|
|
43
|
+
--validate Habilitar validação com commitlint
|
|
44
|
+
--init-commitlint Criar configuração padrão do commitlint
|
|
45
|
+
-h, --help Mostrar esta ajuda
|
|
46
|
+
-v, --version Mostrar versão
|
|
41
47
|
|
|
42
48
|
EXAMPLES:
|
|
43
49
|
commit-wizard # Modo interativo padrão
|
|
@@ -46,6 +52,8 @@ EXAMPLES:
|
|
|
46
52
|
commit-wizard --smart-split # Smart split com IA
|
|
47
53
|
commit-wizard --dry-run # Apenas visualizar mensagem
|
|
48
54
|
commit-wizard --auto # Modo totalmente automático
|
|
55
|
+
commit-wizard --validate # Habilitar validação com commitlint
|
|
56
|
+
commit-wizard --init-commitlint # Criar configuração padrão do commitlint
|
|
49
57
|
|
|
50
58
|
Para mais informações, visite: https://github.com/user/commit-wizard
|
|
51
59
|
`);
|