Changelog and Hooks

Copy Markdown View Source

This guide covers automated changelog generation and the hook system for running custom logic before and after version bumps.

Changelog

Releaser generates changelogs from git commits using conventional commit prefixes.

Commit format

Write your commits like this:

feat: add CartaPorte 3.1 complement support
fix: correct UTF-8 encoding in XML attributes
refactor: extract version parsing to Version struct
docs: update publishing guide with org examples
perf: cache XSD schema parsing results
breaking: remove deprecated cer/key modules

The prefix before : maps to a changelog section.

Default mappings

PrefixChangelog section
featAdded
fixFixed
refactorChanged
docsDocumentation
perfPerformance
breakingBreaking Changes

Generate a changelog

# Preview without writing
$ mix releaser.changelog cfdi_xml --dry-run

Changelog for cfdi_xml:

## [4.0.19] - 2026-04-20

### Added

- add CartaPorte 3.1 complement support
- add Certificate.toBase64() method

### Fixed

- correct UTF-8 encoding in XML attributes

### Changed

- migrate certificar() to use Certificate class

--dry-run: no files written
# Write to CHANGELOG.md
$ mix releaser.changelog cfdi_xml
Updated apps/cfdi/xml/CHANGELOG.md

Scope by git ref

# Changes since a specific tag
$ mix releaser.changelog cfdi_xml --from cfdi_xml-v4.0.18

# Changes in a range
$ mix releaser.changelog --from v1.0.0 --to v2.0.0

Custom anchors

Override the default prefix → section mapping:

releaser: [
  changelog: [
    anchors: %{
      "feat" => "New Features",
      "fix" => "Bug Fixes",
      "security" => "Security",
      "deprecate" => "Deprecated",
      "remove" => "Removed"
    }
  ]
]

Hooks

Hooks let you run custom code before and after version bumps.

Built-in hooks

Releaser includes two ready-to-use hooks:

Releaser.Hooks.GitTag

After a bump, stages the changed mix.exs files, creates a commit, and tags it:

Commit: "bump: version update"
Tag:    "cfdi_xml-v4.0.19"

Releaser.Hooks.ChangelogHook

After a bump, generates/updates the CHANGELOG.md in the app's directory.

Enable hooks

Add them to your config:

releaser: [
  hooks: [
    post: [
      Releaser.Hooks.GitTag,
      Releaser.Hooks.ChangelogHook
    ]
  ]
]

Now when you bump:

$ mix releaser.bump cfdi_xml patch

Version changes:
  cfdi_xml                  4.0.18 → 4.0.19   (direct)

1 app(s) updated
  changelog apps/cfdi/xml/CHANGELOG.md
  tagged cfdi_xml-v4.0.19

Skip hooks for a single run

$ mix releaser.bump cfdi_xml patch --no-hooks

Writing custom hooks

Pre-hook example: ensure clean working tree

defmodule MyProject.Hooks.EnsureClean do
  @behaviour Releaser.Hooks.PreHook

  @impl true
  def run(_context) do
    if Releaser.Git.dirty?() do
      {:error, "Working tree is dirty. Commit or stash changes first."}
    else
      :ok
    end
  end
end

Post-hook example: notify Slack

defmodule MyProject.Hooks.NotifySlack do
  @behaviour Releaser.Hooks.PostHook

  @impl true
  def run(%{app: app, new_version: version, changes: changes}) do
    count = length(changes)
    message = "Released #{app} v#{version} (#{count} package(s) updated)"

    # Your Slack API call here...
    Slack.post_message("#releases", message)

    :ok
  end
end

Post-hook example: run tests before tagging

defmodule MyProject.Hooks.RunTests do
  @behaviour Releaser.Hooks.PreHook

  @impl true
  def run(%{app: app, path: path}) do
    case System.cmd("mix", ["test"], cd: path, stderr_to_stdout: true) do
      {_output, 0} -> :ok
      {output, _} -> {:error, "Tests failed for #{app}:\n#{output}"}
    end
  end
end

Hook context

Both pre and post hooks receive a context map:

%{
  app: "cfdi_xml",                    # app being bumped
  path: "apps/cfdi/xml",             # path to the app
  old_version: "4.0.18",             # version before bump
  new_version: "4.0.19",             # version after bump
  bump_type: :patch,                 # :patch | :minor | :major | :release | :explicit
  changes: [                         # all planned changes (including cascade)
    %{app: "cfdi_xml", path: "...", old: "4.0.18", new: "4.0.19", reason: :direct},
    %{app: "cfdi_designs", path: "...", old: "1.0.0", new: "1.0.1", reason: :cascade}
  ],
  apps: [%Releaser.App{}, ...]       # all discovered apps
}

Hook execution order

  1. All pre-hooks run (in config order)
  2. If any pre-hook returns {:error, reason}, the bump is aborted
  3. Version files are updated
  4. All post-hooks run (in config order)
  5. If a post-hook fails, a warning is printed but the bump is not rolled back

Full hook configuration example

releaser: [
  hooks: [
    pre: [
      MyProject.Hooks.EnsureClean,
      MyProject.Hooks.RunTests
    ],
    post: [
      Releaser.Hooks.ChangelogHook,
      Releaser.Hooks.GitTag,
      MyProject.Hooks.NotifySlack
    ]
  ]
]