Releaser
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.
Features
| 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 |
Installation
Add to your rootmix.exs:
defp deps do
[
{:releaser, "~> 0.1", only: :dev, runtime: false}
]
endQuick start
# 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-runVersioning
Basic bump
mix 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.0Explicit version
mix releaser.bump my_app 2.0.0 # set to exact versionBuild metadata
mix releaser.bump my_app patch --build 20260420 # 4.0.18+20260420Bump all apps
mix releaser.bump --all patch # bump every appPre-release tags
Full lifecycle support for pre-release versions following SemVer 2.0.
Lifecycle
┌─────────────────────────────────────────────────────────────────┐
│ 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) │
│ │
└─────────────────────────────────────────────────────────────────┘Tag rules
| 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) |
Available tags
| 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
Cascade bumps
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.
Dependency graph
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_xmlPublishing to Hex
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 myorgWhat happens internally
For 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)
Release status
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.Changelog
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 modulesOutput follows Keep a Changelog format.
Hooks
Pre and post-bump hooks for custom automation.
Built-in hooks
| Hook | Type | What it does |
|---|---|---|
Releaser.Hooks.GitTag | post | git add + git commit + git tag |
Releaser.Hooks.ChangelogHook | post | Generate/update CHANGELOG.md |
Custom hooks
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
endDisable hooks
mix releaser.bump my_app patch --no-hooksConfiguration
All 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)
]
]
]
]
endAll commands
# 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 hooksRecommended workflow
# 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.publishLicense
MIT