Monorepo versioning, changelog, and Hex publishing for Elixir poncho/umbrella projects.
The only Hex package that handles versioning + publishing for Elixir monorepos with internal dependencies. Think Rush (Node.js) but for Elixir.
| Feature | Releaser | Versioce | GitHub Tag Bump |
|---|---|---|---|
| SemVer bump (patch/minor/major) | Yes | Yes | Yes |
| Pre-release tags (dev, beta, rc) | Yes | Partial | Partial |
| Same tag increments (dev.1 β dev.2) | Yes | No | No |
| Tag change keeps base (dev β beta) | Yes | No | No |
| Release (strip tag) | Yes | No | No |
| Cascade bumps to dependents | Yes | No | No |
| Dependency graph (visual) | Yes | No | No |
| Topological Hex publishing | Yes | No | No |
| Release status (local vs Hex) | Yes | No | No |
| Changelog from git commits | Yes | Yes | No |
| Git hooks (commit + tag) | Yes | Yes | No |
| Multi-file version sync | Yes | Yes | No |
| Build metadata | Yes | Yes | No |
| Explicit version set | Yes | Yes | Yes |
| Monorepo / poncho support | Yes | No | No |
Add to your root mix.exs:
defp deps do
[
{:releaser, "~> 0.1", only: :dev, runtime: false}
]
end# List all apps and versions
mix releaser.bump --list
# See dependency graph
mix releaser.graph
# Bump a package
mix releaser.bump my_app patch
# Check what needs publishing
mix releaser.status
# Publish everything to Hex
mix releaser.publish --dry-runmix releaser.bump my_app patch # 4.0.17 β 4.0.18
mix releaser.bump my_app minor # 4.0.17 β 4.1.0
mix releaser.bump my_app major # 4.0.17 β 5.0.0mix releaser.bump my_app 2.0.0 # set to exact versionmix releaser.bump my_app patch --build 20260420 # 4.0.18+20260420mix releaser.bump --all patch # bump every appFull lifecycle support for pre-release versions following SemVer 2.0.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Version lifecycle β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β 4.0.17 (current stable) β
β β β
β βββ releaser.bump my_app patch --tag dev β
β β β 4.0.18-dev.1 bump base + add tag β
β β β
β βββ releaser.bump my_app patch --tag dev β
β β β 4.0.18-dev.2 same tag = increment only β
β β β
β βββ releaser.bump my_app patch --tag dev β
β β β 4.0.18-dev.3 another dev fix β
β β β
β βββ releaser.bump my_app patch --tag beta β
β β β 4.0.18-beta.1 tag change = keeps base β
β β β
β βββ releaser.bump my_app patch --tag beta β
β β β 4.0.18-beta.2 beta fix β
β β β
β βββ releaser.bump my_app patch --tag rc β
β β β 4.0.18-rc.1 release candidate β
β β β
β βββ releaser.bump my_app release β
β β β 4.0.18 strip tag = stable release β
β β β
β 4.0.18 (new stable) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Situation | Command | Result |
|---|---|---|
Clean 4.0.17 |
patch --tag dev |
4.0.18-dev.1 (bump + tag) |
Same tag -dev.2 |
patch --tag dev |
4.0.18-dev.3 (increment) |
Different tag -dev.3 |
patch --tag beta |
4.0.18-beta.1 (keep base) |
Any tag -beta.2 |
release |
4.0.18 (strip tag) |
| Tag | Usage |
|---|---|
dev |
Active development, may break things |
alpha |
First internal test version |
beta |
Feature-complete, may have bugs |
rc |
Release candidate, production-ready except bugs |
SemVer ordering: alpha < beta < dev < rc < stable
When you bump a package, all packages that depend on it automatically receive a patch bump:
mix releaser.bump clir_openssl minor --dry-runVersion changes:
clir_openssl 0.0.17 β 0.1.0 (direct)
cfdi_csd 4.0.16 β 4.0.17 (cascade)
sat_auth 1.0.1 β 1.0.2 (cascade)
cfdi_xml 4.0.18 β 4.0.19 (cascade)
cfdi_cancelacion 0.0.1 β 0.0.2 (cascade)
cfdi_descarga 0.0.1 β 0.0.2 (cascade)
Disable with --no-cascade.
mix releaser.graphββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Dependency Graph β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββ Level 0 (no internal deps) βββ
β cfdi_catalogos v4.0.16
β clir_openssl v0.0.17
β saxon_he v12.5.2
β ... (28 apps)
β βΌ
βββ Level 1 βββ
β cfdi_csd v4.0.16
β ββ depends on: clir_openssl
β βΌ
βββ Level 2 βββ
β cfdi_xml v4.0.18
β ββ depends on: cfdi_csd, cfdi_transform, ...
β sat_auth v1.0.1
β ββ depends on: cfdi_csd
β βΌ
βββ Level 3 βββ
β cfdi_cancelacion v0.0.1
β ββ depends on: sat_auth
βββ end βββ
Show dependents of a specific app:
mix releaser.graph cfdi_csdDependents of cfdi_csd:
ββ sat_auth
ββ cfdi_cancelacion
ββ cfdi_descarga
ββ cfdi_xml
Publishes all packages in topological order (dependencies first).
Automatically replaces path: deps with Hex versions and restores
after publishing.
# See the publish plan
mix releaser.publish --dry-run
# Publish everything
mix releaser.publish
# Bump + publish
mix releaser.publish --bump patch
# Only specific apps (+ their deps automatically)
mix releaser.publish --only cfdi_xml
# Publish to a Hex organization
mix releaser.publish --org myorgFor each package (in dependency order):
- Backup
mix.exs - Bump version (if
--bump) - Replace
{:dep, path: "..."}β{:dep, "~> X.Y"} - Inject
package/0if missing mix hex.publish --yes- Restore original
mix.exs(always, even on failure)
Compare local versions against what's published on Hex:
mix releaser.statusPackage Local Hex Status
cfdi_xml 4.0.19 4.0.18 ahead
cfdi_csd 4.0.16 4.0.16 published
cfdi_complementos 4.0.18-dev.1 4.0.17 pre-release
my_new_app 0.1.0 β unpublished
2 package(s) need publishing.
Generate changelogs from git commits using conventional commit prefixes:
# Generate for all apps
mix releaser.changelog
# Generate for one app
mix releaser.changelog cfdi_xml
# Preview without writing
mix releaser.changelog --dry-run
# From a specific ref
mix releaser.changelog --from v4.0.17Commits should follow conventional commits:
feat: add CartaPorte 3.1 support
fix: correct XML encoding for special characters
refactor: extract version parsing to struct
breaking: remove deprecated cer/key modules
Output follows Keep a Changelog format.
Pre and post-bump hooks for custom automation.
| Hook | Type | What it does |
|---|---|---|
Releaser.Hooks.GitTag |
post | git add + git commit + git tag |
Releaser.Hooks.ChangelogHook |
post | Generate/update CHANGELOG.md |
defmodule MyProject.NotifySlack do
@behaviour Releaser.Hooks.PostHook
@impl true
def run(%{app: app, new_version: version, changes: changes}) do
# Send Slack notification...
:ok
end
endmix releaser.bump my_app patch --no-hooksAll config lives in your root mix.exs under the :releaser key:
def project do
[
app: :my_project,
version: "0.1.0",
deps: deps(),
releaser: [
# Root directory containing apps (default: "apps")
apps_root: "apps",
# Additional files to sync version in
version_files: [
{"README.md", ~r/@version (\S+)/},
{"Dockerfile", ~r/ARG VERSION=(\S+)/}
],
# Changelog configuration
changelog: [
path: "CHANGELOG.md",
anchors: %{
"feat" => "Added",
"fix" => "Fixed",
"refactor" => "Changed",
"docs" => "Documentation",
"perf" => "Performance",
"breaking" => "Breaking Changes"
}
],
# Pre/post hooks
hooks: [
pre: [],
post: [Releaser.Hooks.GitTag, Releaser.Hooks.ChangelogHook]
],
# Hex publishing defaults
publisher: [
org: nil,
package_defaults: [
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/me/project"},
files: ~w(lib mix.exs README.md LICENSE)
]
]
]
]
end# Versioning
mix releaser.bump <app> <major|minor|patch> # bump with cascade
mix releaser.bump <app> <major|minor|patch> --tag dev
mix releaser.bump <app> release # strip pre-release
mix releaser.bump <app> 2.0.0 # explicit version
mix releaser.bump --list # list versions
mix releaser.bump --all patch # bump all apps
# Graph
mix releaser.graph # full dependency graph
mix releaser.graph <app> # dependents of app
# Publishing
mix releaser.publish # publish all to Hex
mix releaser.publish --dry-run # show plan
mix releaser.publish --only app1,app2 # only these + deps
mix releaser.publish --bump patch # bump before publish
mix releaser.publish --org myorg # Hex organization
# Status
mix releaser.status # local vs Hex comparison
# Changelog
mix releaser.changelog # generate for all
mix releaser.changelog <app> # generate for one
mix releaser.changelog --from v1.0.0 # from specific ref
# Global options (all commands)
--dry-run # preview without changes
--no-hooks # skip pre/post hooks# 1. Start development
mix releaser.bump my_app patch --tag dev
# 2. Iterate
mix releaser.bump my_app patch --tag dev # dev.1 β dev.2
# 3. Promote to beta
mix releaser.bump my_app patch --tag beta # dev.3 β beta.1
# 4. Release candidate
mix releaser.bump my_app patch --tag rc
# 5. Final release
mix releaser.bump my_app release # rc.1 β stable
# 6. Check what needs publishing
mix releaser.status
# 7. Publish
mix releaser.publishMIT