Conventional Commits

Copy Markdown View Source

Releaser puede leer tu historial de git y decidir automáticamente qué apps bumpear y con qué tipo (major/minor/patch).

Feature opt-in. Esta integración con Conventional Commits está desactivada por default. Si no activas commits: [enabled: true] en tu mix.exs, todo lo descrito aquí se ignora y Releaser funciona como una herramienta de releases manual (ver Manual releases).

Ambos caminos son soportados de primera clase.

Activar la feature

En tu mix.exs:

releaser: [
  apps_root: ".",
  commits: [enabled: true]
]

Con eso basta — los defaults sensatos se aplican. Si quieres personalizar:

releaser: [
  commits: [
    enabled: true,
    bump_rules: %{
      "feat" => :minor,
      "fix" => :patch,
      "perf" => :patch,
      "refactor" => :patch,
      "revert" => :patch
    },
    breaking_bump: :major,
    breaking_markers: [:bang, :body],
    scope_aliases: %{"autenticacion" => "auth"},
    no_scope: :warn
  ]
]

La tabla: tipo de commit → bump

TipoEjemploBump
featfeat(xml): add carta_porte 3.1minor
fixfix(csd): handle empty keypatch
perfperf(xml): faster parsingpatch
refactorrefactor(csd): extract Signerpatch
revertrevert(xml): revert Xpatch
docsdocs(xml): update README(ninguno)
chorechore: bump deps(ninguno)
testtest(xml): add cases(ninguno)
style / build / cici: add coverage(ninguno)

BREAKING CHANGE

Dos formas de marcar un breaking change (ambas se activan por default):

1. El ! inline

feat(xml)!: rename CFDI.sign to CFDI.sign_with_csd
fix(csd)!: remove deprecated load_key/1

Va después del tipo o el scope, antes de los dos puntos.

2. BREAKING CHANGE: en el body

feat(xml): add new signer

BREAKING CHANGE: CFDI.sign/2 was renamed to CFDI.sign_with_csd/2.

También acepta BREAKING-CHANGE: (con guion).

Cualquier commit con alguno de estos markers se bumpea a major — incluso tipos que normalmente no bumpean (docs, chore).

Agregación: muchos commits → un solo bump

Si tienes 10 commits desde el último tag, no son 10 bumps. Es UN bump del tipo más agresivo presente.

feat(xml): X1
feat(xml): X2
fix(xml): Y
refactor(xml): Z

minor (un solo bump). Si entre esos hay feat(xml)!:major.

Jerarquía: major > minor > patch > ninguno.

Scopes y apps

El scope del commit determina a qué app se aplica:

feat(xml): ...     app llamado "xml" (o "cfdi_xml" con prefix-stripping)
fix(csd): ...      app "csd" o "cfdi_csd" o "sat_csd"

Scope aliases

Si el scope no coincide con el nombre del app, configura:

commits: [
  enabled: true,
  scope_aliases: %{
    "autenticacion" => "sat_auth",
    "certificados" => "cfdi_csd"
  }
]

Prefix stripping automático

Si tu app se llama cfdi_xml y el commit es feat(xml): ..., Releaser reconoce la relación automáticamente quitando prefijos comunes (cfdi_, sat_, clir_, renapo_).

Commits sin scope

Por defecto (no_scope: :warn), se ignoran con un warning. Opciones:

  • :ignore — silencioso
  • :warn — warning en stdout (default)

Comandos

Sugerir (análisis sin aplicar)

mix releaser.bump --suggest

Output:

Analyzing commits since v4.0.18...

Apps to bump:
  xml       4.0.18  4.1.0     (minor  3 commit(s))
  csd       4.0.16  4.0.17    (patch  1 commit(s))
  auth      1.0.1   2.0.0     (major  1 commit(s))

Apps with no relevant changes:
  renapo

Run with --from-commits to apply.

Aplicar

# Todos los apps que tengan commits relevantes
mix releaser.bump --from-commits

# Solo un app
mix releaser.bump xml --from-commits

# En canal pre-release
mix releaser.bump --from-commits --mode prerelease --tag dev

# Desde un tag específico (no el último)
mix releaser.bump --from-commits --since v4.0.0

No-op (sin commits relevantes)

Si no hay commits que disparen bump, el comando termina exitosamente sin modificar nada:

$ mix releaser.bump --from-commits

No relevant commits since v4.0.18. No bump.

Esto es importante para CI: si nadie hizo un feat/fix desde el último release, el workflow no bumpea ni publica. Idempotente.

Gitflow: dev / beta / main

Con Conventional Commits activo + el workflow de GitHub Actions incluido, el flujo es:

  1. Dev trabaja en una feature branch:

    git checkout -b feature/carta-porte-31
    # ... código ...
    git commit -m "feat(xml): add carta_porte 3.1 support"
    git push -u origin feature/carta-porte-31
    # abre PR a dev
    
  2. Merge a dev → GitHub Actions detecta el push, lee el commit feat(xml), y automáticamente:

    • Bumpea xml a 4.1.0-dev.1
    • Commitea chore(release): auto-bump on dev
    • Crea tag v4.1.0-dev.1
    • Pushea
  3. Merge dev → beta → Actions bumpea a 4.1.0-beta.1.

  4. Merge beta → main → Actions bumpea a 4.1.0 estable y, si PUBLISH_TO_HEX=true, publica a Hex + crea GitHub Release.

Pre-commit hooks

Para evitar que commits mal formateados entren al repo, Releaser incluye un hook de git que valida cada mensaje.

Instalación

mix releaser.install_hooks

Eso ejecuta git config core.hooksPath .githooks — todos los hooks del repo se activan en una sola línea. Cada dev del proyecto tiene que correrlo una vez después de clonar.

Qué valida

Sigue estrictamente Conventional Commits v1.0.0:

  • Formato: <type>(<scope>)?(!)?: <subject>
  • BREAKING CHANGE: / BREAKING-CHANGE: en mayúsculas (la spec lo exige)
  • Línea en blanco obligatoria entre header y body cuando hay body
  • Subject no puede estar vacío
  • Subject no puede exceder max_subject_length (default 100)

Configuración

En tu mix.exs:

releaser: [
  commits: [
    enabled: true,
    validation: [
      # Si true, solo tipos declarados en bump_rules + allowed_types son válidos.
      strict_types: false,

      # Tipos adicionales válidos (que NO bumpean pero son aceptados).
      allowed_types: ~w[docs chore test style build ci],

      # Si true, solo scopes declarados son válidos.
      strict_scopes: false,

      # Lista explícita de scopes. Si omites, se auto-infiere de app names
      # + scope_aliases.
      allowed_scopes: nil,

      # Permitir commits sin scope (`feat: ...` sin paréntesis).
      allow_no_scope: true,

      # Longitud máxima del subject.
      max_subject_length: 100
    ]
  ]
]

Modos

Modostrict_typesstrict_scopesPara qué
Permisivo (default)falsefalseSolo valida formato. Bueno para empezar.
StricttruetrueSolo tipos/scopes declarados. Para proyectos maduros.

Ejemplos de errores

$ git commit -m "fix stuff"
✗ Commit message invalid

    fix stuff

Commit message does not match Conventional Commits format.
Expected: <type>(<scope>)?(!)?: <subject>
$ git commit -m "feat(xmll): ..."   # en modo strict
✗ Commit message invalid

    feat(xmll): ...

Unknown scope "xmll".
Allowed scopes: releaser, rel, release

Bypass (no recomendado)

git commit --no-verify -m "emergency fix"

Desinstalar

git config --unset core.hooksPath

Variables del workflow

Configura en Settings → Actions → Variables:

NombreValoresQué hace
PUBLISH_TO_HEXtrue / falsePublica a Hex en push a main. Default false.
CREATE_GH_RELEASEtrue / falseCrea GitHub Release en push a main. Default false.

Secret requerido si PUBLISH_TO_HEX=true:

  • HEX_API_KEY — Obtén una con mix hex.user whoami / mix hex.user key generate.