Releaser funciona con tres tipos de proyecto Elixir:
- Poncho project (multi-app con grupos, como cfdi-elixir)
- Umbrella project (multi-app estándar de Elixir)
- Proyecto single (un solo mix.exs sin apps/)
Instalación
Agrega releaser a tu root mix.exs:
defp deps do
[
{:releaser, "~> 0.1", only: :dev, runtime: false}
]
endmix deps.get
Configuración por tipo de proyecto
Poncho project (apps agrupados)
mi_proyecto/
├── mix.exs ← root
├── apps/
│ ├── cfdi/ ← grupo
│ │ ├── xml/mix.exs ← app
│ │ ├── csd/mix.exs ← app
│ │ └── complementos/mix.exs ← app
│ ├── sat/ ← grupo
│ │ ├── auth/mix.exs ← app
│ │ └── pacs/mix.exs ← app
│ └── clir/
│ └── openssl/mix.exs ← appConfiguración en el root mix.exs:
def project do
[
app: :mi_proyecto,
version: "0.1.0",
deps: deps(),
releaser: [
apps_root: "apps" # default, busca mix.exs recursivamente
]
]
endEn cada app que quieras publicar a Hex:
# apps/cfdi/xml/mix.exs
def project do
[
app: :cfdi_xml,
version: "4.0.18",
deps: deps(),
description: "XML builder para CFDI",
releaser: [publish: true] # ← marcado como publicable
]
endUmbrella project (estándar de Elixir)
mi_umbrella/
├── mix.exs ← root umbrella
├── apps/
│ ├── core/mix.exs ← app
│ ├── api/mix.exs ← app
│ └── worker/mix.exs ← appConfiguración idéntica. Releaser detecta ambos layouts automáticamente:
# mix.exs (root)
def project do
[
app: :mi_umbrella,
version: "0.1.0",
apps_path: "apps", # esto es de umbrella
deps: deps(),
releaser: [
apps_root: "apps"
]
]
endEn cada app:
# apps/core/mix.exs
def project do
[
app: :core,
version: "1.0.0",
build_path: "../../_build",
deps_path: "../../deps",
lockfile: "../../mix.lock",
deps: deps(),
description: "Core business logic",
releaser: [publish: true]
]
endProyecto single (sin apps/)
Para un proyecto con un solo mix.exs:
mi_libreria/
├── mix.exs
├── lib/
└── test/# mix.exs
def project do
[
app: :mi_libreria,
version: "1.0.0",
deps: deps(),
description: "Mi librería",
releaser: [
apps_root: ".", # ← apunta al directorio actual
publish: true
]
]
endEn este caso Releaser detecta un solo app y funciona para bump + changelog + publish (sin cascade porque no hay dependientes).
Dos caminos: manual o automatizado
Releaser soporta dos flujos de trabajo igualmente válidos. Elige según tu equipo y proyecto — puedes empezar con uno y migrar al otro sin romper nada.
Camino A — Releases manuales (default)
No requiere configuración adicional. Tú decides cada bump:
mix releaser.bump patch # 1.0.0 → 1.0.1
mix releaser.bump minor # 1.0.1 → 1.1.0
mix releaser.bump major # 1.1.0 → 2.0.0
Formato de commits libre, sin hooks, sin CI obligatorio. Ver Manual releases.
Camino B — Conventional Commits (opt-in)
Si adoptas Conventional Commits
(feat:, fix:, feat!:), Releaser puede leer tu historial de git y
decidir el bump automáticamente:
releaser: [
commits: [enabled: true]
]# Analiza sin aplicar
mix releaser.bump --suggest
# Aplica automáticamente
mix releaser.bump --from-commits --mode prerelease --tag dev
Incluye hook de git para rechazar commits mal formateados, GitHub Actions template, y flujo gitflow automatizado. Ver Conventional Commits.
Comparación rápida
| Camino A (manual) | Camino B (commits) | |
|---|---|---|
| Config extra | Ninguna | commits: [enabled: true] |
| Quién decide el bump | Tú | El parser de git log |
| Pre-commit hook | — | Opcional (strict) |
| GitHub Actions | Opcional | Opcional (template incluido) |
Todo el resto del releaser (cascade, publish, pre-releases, changelog) funciona igual en ambos caminos.
Publish policy
Qué es publish: true
El flag releaser: [publish: true] en el mix.exs de cada app controla:
Con publish: true | Sin publish: true |
|---|---|
Se publica a Hex con mix releaser.publish | NO se publica |
| Recibe cascade bump cuando sus deps cambian | NO recibe cascade bump |
Aparece en mix releaser.publish --dry-run | No aparece en el plan de publish |
Aparece en mix releaser.status con su estado | Aparece como "private" |
Se incluye en mix releaser.bump --list | Se incluye en --list |
Se incluye en mix releaser.graph | Se incluye en el grafo |
Reglas de publicación
┌───────────────────────────────────────────────────────────────────────┐
│ Reglas de publish │
├───────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Solo apps con publish: true se publican │
│ │
│ 2. Al publicar un app, también se publican sus DEPENDIENTES │
│ (quienes dependen de él), no sus dependencias │
│ │
│ 3. Las dependencias que no son publicables (publish: false) │
│ se resuelven contra la versión que ya está en Hex │
│ │
│ 4. El cascade de bump solo aplica a apps publicables │
│ │
└───────────────────────────────────────────────────────────────────────┘Ejemplo: ¿qué pasa cuando modifico un app?
Caso 1: Modifico cfdi_xml (nadie depende de él)
$ mix releaser.bump cfdi_xml patch --dry-run
Version changes:
cfdi_xml 4.0.18 → 4.0.19 (direct)
# Solo cfdi_xml — nadie más depende de él
$ mix releaser.publish --only cfdi_xml --dry-run
# Solo publica cfdi_xml
# Sus deps (cfdi_csd, saxon_he, etc.) ya están en Hex
Caso 2: Modifico cfdi_csd (cfdi_xml depende de él)
$ mix releaser.bump cfdi_csd patch --dry-run
Version changes:
cfdi_csd 4.0.16 → 4.0.17 (direct)
cfdi_xml 4.0.18 → 4.0.19 (cascade) ← depende de csd, publish: true
# sat_auth NO aparece — tiene publish: false
$ mix releaser.publish --only cfdi_csd --dry-run
# Publica: cfdi_csd (nivel 1), luego cfdi_xml (nivel 2)
# sat_auth NO se publica — no tiene publish: true
Caso 3: Modifico clir_openssl (toda la cadena depende)
$ mix releaser.bump clir_openssl patch --dry-run
Version changes:
clir_openssl 0.0.17 → 0.0.18 (direct)
cfdi_csd 4.0.16 → 4.0.17 (cascade)
cfdi_xml 4.0.18 → 4.0.19 (cascade)
# Solo los publicables cascadean
Escenarios de policy
App interno que nunca se publica
# apps/sat/scraper/mix.exs — herramienta interna de scraping
def project do
[
app: :sat_scraper,
version: "0.0.1",
deps: deps()
# Sin releaser: [publish: true] → privado, no se publica nunca
]
endApp que YA estaba en Hex pero ya no se publicará más
Si cfdi_transform ya está en Hex como 4.0.14 y decides no publicar más versiones:
- Quita
releaser: [publish: true]de sumix.exs(o simplemente no lo pongas) - La versión
4.0.14sigue disponible en Hex - Los apps que dependen de él usan
{:cfdi_transform, "~> 4.0"}— resuelve a4.0.14 - No recibe cascade bumps
- No se intenta publicar
App nuevo que se publicará por primera vez
# apps/cfdi/nuevo/mix.exs
def project do
[
app: :cfdi_nuevo,
version: "0.1.0",
deps: deps(),
description: "Nuevo paquete", # requerido por Hex
releaser: [publish: true]
]
endAl ejecutar mix releaser.publish, se publica por primera vez.
mix releaser.status lo mostrará como "unpublished".
Primeros pasos después de configurar
# 1. Ver todos los apps descubiertos
$ mix releaser.bump --list
# 2. Ver el grafo de dependencias
$ mix releaser.graph
# 3. Ver qué está pendiente de publicar
$ mix releaser.status
# 4. Probar un bump (sin aplicar cambios)
$ mix releaser.bump mi_app patch --tag dev --dry-run
# 5. Probar el plan de publicación
$ mix releaser.publish --dry-run
Siguiente lectura
- Pre-release Tags — ciclo dev → beta → rc → release
- Publishing to Hex — cómo funciona la publicación topológica
- Changelog and Hooks — automatizar git tags y changelogs
- Monorepo Patterns — estructuras de proyecto y patrones avanzados