Saltar al contenido principal

czg — Wizard interactivo de commits

Qué es

czg (cz-git) es un wizard de línea de comandos que guía al desarrollador a través de la creación de un mensaje de commit siguiendo el estándar Conventional Commits. En lugar de escribir el mensaje manualmente, el dev responde una serie de preguntas y czg construye el mensaje correctamente formateado.

En Wiedii, bun commit es el alias estándar que invoca czg.

Por qué lo usamos en Wiedii

  • Elimina el margen de error humano en el formato de commits — feat(auth): add OAuth2 login siempre va a estar bien escrito
  • Guía a devs nuevos que aún no tienen memorizado el formato Conventional Commits
  • Se integra con commitlint — el hook commit-msg valida que el mensaje generado cumple las reglas, cerrando el ciclo
  • Velocidad — czg es significativamente más rápido que alternativas como commitizen con cz-conventional-changelog

Instalación

czg va como devDependency en el package.json de cada repo. No se instala globalmente.

bun add -d czg cz-git @commitlint/cli @commitlint/config-conventional
{
"type": "module",
"scripts": {
"commit": "bun --bun git-czg"
},
"devDependencies": {
"@commitlint/cli": "21.0.1",
"@commitlint/config-conventional": "21.0.1",
"cz-git": "^1.13.1",
"czg": "^1.12.0"
},
"engines": {
"bun": "1.3.14",
"node": "Please use Bun instead of Node to install dependencies",
"npm": "Please use Bun instead of NPM to install dependencies",
"pnpm": "Please use Bun instead of PNPM to install dependencies",
"yarn": "Please use Bun instead of Yarn to install dependencies"
}
}

czg vs cz-gitcz-git es dependencia transitiva de czg, pero se declara explícitamente porque commitlint.config.js la importa directamente (import { defineConfig } from 'cz-git'). commitizen no se usa — en Wiedii solo se invoca bun commitczg.

"type": "module" — todos los repos nuevos en Wiedii usan ESM. El archivo de configuración commitlint.config.js usa import/export default en lugar de require/module.exports.

bun --bun git-czgbun --bun fuerza el runtime de Bun (más rápido que Node). git-czg es el binario que instala czg; también habilita git czg como subcomando de git.

engines — patrón Wiedii: mensajes de error en los campos de versión de otros package managers hace explícito que solo se usa Bun.


Uso

# Flujo estándar — siempre
git add .
bun commit # ejecuta: bun --bun git-czg → abre el wizard interactivo

El wizard hace las siguientes preguntas en orden:

  1. Tipofeat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
  2. Scope (opcional) — el módulo o área afectada, ej. auth, api, ui
  3. Título — descripción corta en imperativo, sin mayúscula inicial, sin punto final
  4. Cuerpo (opcional) — descripción larga con contexto adicional
  5. Breaking change — si es un cambio que rompe compatibilidad
  6. Issues relacionados (opcional) — referencia a issues de GitLab ej. #42

El resultado es un mensaje como:

feat(auth): add OAuth2 login with GitLab provider

Implements OAuth2 flow using GitLab as identity provider.
Replaces the previous username/password form.

BREAKING CHANGE: removes /api/login endpoint

Closes #42

Configuración estándar en Wiedii

// commitlint.config.js — ESM (requiere "type": "module" en package.json)
import { defineConfig } from 'cz-git'

export default defineConfig({
extends: ['@commitlint/config-conventional'],
rules: {
'subject-case': [2, 'always', 'lower-case'],
'subject-full-stop': [2, 'never', '.'],
'header-max-length': [2, 'always', 100],
},
prompt: {
messages: {
type: "Select the type of change that you're committing:",
scope: 'Denote the SCOPE of this change (optional):',
customScope: 'Denote the SCOPE of this change:',
subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n',
body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n',
breaking: 'List any BREAKING CHANGES (optional). Use "|" to break new line:\n',
footerPrefixesSelect: 'Select the ISSUES type of change (optional):',
customFooterPrefix: 'Input ISSUES prefix:',
footer: 'List any ISSUES by this change. E.g.: #31, #34:\n',
confirmCommit: 'Are you sure you want to proceed with the commit above?',
},
types: [
{ value: 'feat', name: 'feat: ✨ A new feature', emoji: '✨' },
{ value: 'fix', name: 'fix: 🐛 A bug fix', emoji: '🐛' },
{ value: 'docs', name: 'docs: 📝 Documentation only changes', emoji: '📝' },
{ value: 'style', name: 'style: 💄 Changes that do not affect the meaning of the code', emoji: '💄' },
{ value: 'refactor', name: 'refactor: ♻️ A code change that neither fixes a bug nor adds a feature', emoji: '♻️' },
{ value: 'perf', name: 'perf: ⚡️ A code change that improves performance', emoji: '⚡️' },
{ value: 'test', name: 'test: ✅ Adding missing tests or correcting existing tests', emoji: '✅' },
{ value: 'build', name: 'build: 📦️ Changes that affect the build system or external dependencies', emoji: '📦️' },
{ value: 'ci', name: 'ci: 🎡 Changes to CI configuration files and scripts', emoji: '🎡' },
{ value: 'chore', name: "chore: 🔨 Other changes that don't modify src or test files", emoji: '🔨' },
{ value: 'revert', name: 'revert: ⏪️ Reverts a previous commit', emoji: '⏪️' },
],
useEmoji: true,
emojiAlign: 'center',
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: 'bottom',
customScopesAlias: 'custom',
emptyScopesAlias: 'empty',
enableMultipleScopes: true,
scopeEnumSeparator: ',',
upperCaseSubject: false,
markBreakingChangeMode: false,
allowBreakingChanges: ['feat', 'fix'],
maxHeaderLength: 100,
maxSubjectLength: 100,
breaklineNumber: 100,
breaklineChar: '|',
skipQuestions: [],
issuePrefixes: [
{ value: 'closed', name: 'closed: ISSUES has been processed' },
{ value: 'Fixes', name: 'Fixes: 🐛 Fixes an open ISSUE' },
{ value: 'Ref', name: 'Ref: 🔗 Related ISSUES' },
],
emptyIssuePrefixAlias: 'skip',
customIssuePrefixAlias: 'custom',
allowCustomIssuePrefix: true,
allowEmptyIssuePrefix: true,
confirmColorize: true,
defaultBody: '',
defaultIssues: '',
defaultScope: '',
defaultSubject: '',
},
})

changelog.config.js

Archivo separado de commitlint.config.js. Lo consume conventional-changelog / Nx Release para generar CHANGELOG.md. Define los scopes del proyecto y el límite de longitud del mensaje.

// changelog.config.js — ESM
// Sobreescribe 'scopes' con los paquetes reales del repo
export default {
scopes: [], // ← completar con los módulos del proyecto
maxMessageLength: 100,
}

scopes debe reflejar exactamente los mismos valores definidos en commitlint.config.jsrules['scope-enum'] cuando el proyecto tiene scopes fijos.


Integración con lefthook

El hook commit-msg valida el mensaje generado por czg antes de completar el commit:

# lefthook.yaml
commit-msg:
parallel: false
jobs:
- run: mise trust
- run: mise install --yes
commands:
commitlint:
priority: 1
run: mise exec -- bun commitlint --config commitlint.config.js --edit "$1"

Si el mensaje no cumple las reglas de commitlint.config.js, el commit se rechaza con un mensaje de error claro.


Troubleshooting rápido

bun commit abre el wizard pero el commit falla con error de commitlint:

# Verificar que commitlint.config.js existe en la raíz del repo
ls commitlint.config.js

# Probar commitlint manualmente
bun commitlint --from HEAD~1

El wizard no muestra los tipos personalizados:

# Verificar que commitlint.config.js exporta el bloque `prompt`
# y que czg y cz-git están en devDependencies, no en dependencies
cat package.json | grep -E "czg|cz-git"

bun commit no encuentra czg:

# Verificar que está instalado
bun install
ls node_modules/.bin/czg

Referencias