Sampo - automate changelogs, versioning, and publishing for Elixir (and multi-ecosystem monorepos)

Hey everyone :waving_hand:

I’m excited to share Sampo, a tool suite to automate changelogs, versioning, and publishing—even for monorepos across multiple package registries. It currently Elixir (Hex) packages, alongside Rust (Crates), JavaScript/TypeScript (npm), Python (PyPI), PHP (Packagist)… And more coming soon!

In a nutshell

Sampo is a CLI, a GitHub App, and a GitHub Action, that automatically detects packages in your repository, and uses changesets (markdown files describing changes explicitly) to bump versions (in SemVer format), generate changelogs (human-readable files listing changes), and publish packages (to their respective registries). It’s designed to be easy to opt-in and opt-out, with minimal configuration required, sensible defaults, and no assumptions/constraints on your workflow (except using SemVer).

If you’ve ever struggled with keeping user-facing changelogs updated, coordinating version bumps across dependent packages, or automating your publishing process… Sampo might be the tool you were looking for!

In more details

Sampo is a CLI tool (for now only available on Cargo, but soon on your favorite OS package manager), a Github Action, and a GitHub App that work together to streamline your release process.

Sampo automatically discovers Elixir (and Rust/Javascript/Typescript) packages in your workspace (including umbrella projects), respects your mix.exs configuration, and calls mix hex.publish under the hood (you still need to have your HEX_API_KEY environment variable or Mix’s auth configured).

Like Hex.pm, Sampo enforces Semantic Versioning (SemVer) to indicate the nature of changes in each release. Versions follow the MAJOR.MINOR.PATCH format with three bump levels: patch — Bug fixes and backwards-compatible changes; minor — New features that are backwards-compatible: major — Breaking changes that are not backwards-compatible.

For example, a user can safely update from version 1.2.3 to 1.2.4 (patch) or 1.3.0 (minor), but should review changes before updating to 2.0.0 (major). Sampo also supports pre-release versions (e.g., 1.2.0-rc.1) as SemVer §9 conventions.

For each change made to your packages, you can use sampo add to create a new changeset file, the CLI will prompt you to select which packages were affected, the type of version bump (patch/minor/major), and a description of the change. You can use Sampo GitHub bot to get reminders on each PR without a changeset.

---
hex/back-end: minor
npm/web-app: patch
---

A helpful description of the change, to be read by your users.

Those changesets are markdown file, inspired by Changesets and Lerna, stored in the .sampo/changesets/ directory. Sampo consumes those changesets to determine version bumps, and update changelogs—a human-readable file listing all changes for each released version, at the root of every package.


# Example

## 0.2.0 — 2024-06-20

### Minor changes

- [abcdefg](link/to/commit) A helpful description of the changes. — Thanks @user!

## 0.1.1 — 2024-05-12

### Patch changes

- [hijklmn](link/to/commit) A brief description of the fix. — Thanks @first-time-contributor for their first contribution!

... previous entries ...

Run sampo release to process all pending changesets, bump package versions, and update changelogs. This can be automated in CI/CD pipelines using Sampo GitHub Action, with a release PR created whenever changesets are detected. As long as the release is not finalized, you can continue to add changesets and re-run the sampo release command. Sampo will update package versions and pending changelogs accordingly.

Finally, run sampo publish to publish updated packages to their respective registries and tag the current versions. This step can also be automated in CI/CD pipelines using Sampo GitHub Action, by merging the release PR, and can also create GitHub Releases and Discussions for each new tag.

Philosophy

Sampo is designed to be an helpful, reliable, and flexible tool that users can trust.

We want to make it easy to get started, with minimal configuration, sensible defaults, and automated workflows. At the same time, we want to provide rich configuration options, and flexible workflows to cover more advanced use cases. Finally, Sampo should be easy to opt in and opt out, with little to none assumptions, conventions to follow, or lock-ins.

It’s fully open-source, and we welcome contributions and feedback from the community! If you give it a try, please let us know what you think, and if we can do anything to improve the Elixir support :slightly_smiling_face:

And leave a star on GitHub if you find it useful!

7 Likes

Hi everyone :waving_hand:

Since I introduced Sampo here, I’ve written a longer article that goes into the motivations behind the project, the design philosophy, and some ideas for what’s next. I hope you find this interesting!

3 Likes

Very timely, I’m recently doing one final pass out evaluating various such tools. Thanks for releasing yours!

1 Like

Thanks for checking it out!

If you’re comparing options, there’s an « Alternatives » section in my article that outlines how Sampo compare to other tools: https://goulven-clech.dev/2025/introducing-sampo#alternatives.

I’m in active development, so if you find anything missing or have specific needs, please let me know and I’ll see what I can do :slightly_smiling_face:

Hey, I finally got around to this. I wanted to tell you that after reviewing sampo I found it a bit heavy for my needs. I am mostly looking for manually bumping whichever part of the version I want and I want to be in charge of deciding that. Add also git tagging and GitHub release notes. That’s mostly what I need.

While I am open to have a tool auto-generate more things for me in the future, I still am not sure I would want it to decide for me whether a major, minor, or patch version must be bumped. I am also not 100% sold on conventional commits which many other tools use to make that call f.ex. Ash’s creator @zachdaniel’s GitHub - zachdaniel/git_ops: A tool for version and changelog management in Elixir via conventional commits. tool, though his allows us to only opt in to version management inside mix.exs when we want to.

Thanks for your contribution, the tools looks amazing and I will always kind of shill for Rust tools because a multitude of them have been a net positive to my workflows. It’s just that the library that I am about to release is a much more manual and hands-on in nature when it comes to releases. I’ll reevaluate sampo in the near future because I’ll start another library soon after the first one gets released. And I might need it for some customer projects, too.

Hi @dimitarvp ,

Sorry for the slow reply, my weeks have been pretty packed lately and I’m only now catching up properly.

Thanks again for taking the time to check out Sampo. Just to clarify a couple of things :

Sampo never decides the bump level on its own: you explicitly declare the bump (patch, minor, or major), the packages affected, and the user-facing description of the change, either via the CLI or by editing the changeset file by hand. Sampo then just does the deterministic SemVer math from there. There’s no hidden logic trying to guess what the next version “should” be.

Sampo also doesn’t depend on Conventional Commits at all. One of the design goals was precisely to avoid mixing the technical history in git (written for and by contributors) with the API changelog (written for users, and sometimes reviewed/edited by product/docs owners). Changesets are the single source of truth, and the changelog and versions are generated from those.

It’s also meant to be modular. You can use it only to bump versions, generate changelogs, create git tags and GitHub releases, without letting it publish to registries. Similarly, you can stick to the CLI locally, and ignore both the GitHub App and the GitHub Action. The intent is that Sampo should be easy to opt in and out of, with as few assumptions, conventions, or lock-ins as possible. If there are still rough edges or sources of friction that made it feel “too heavy” in practice, I’m genuinely interested in hearing about them so I can keep smoothing things out :slightly_smiling_face:

2 Likes

Thanks for the follow-up, much appreciated.

Sadly can’t remember now but I think it was mostly the volume of the blog post + the changesets thing.

But seeing as I had to spend a few hours on a bash script together with an LLM to just be able to release my own package with GIT tagging and bumping+syncing a version between mix.exs and Cargo.toml and nothing much else – and the shell script still ballooned to nearly 200 lines due to having to catch error and double-check errors – then re-reviewing sampo starts making more and more sense.

Not a remark towards you or the tool – but I find myself very puzzled at the current state of affairs. To be more concrete, these are the steps I need to release my package:

  • Bump the version to what is desired (determined by one of the three: patch / minor / major)
  • Set it inside mix.exs
  • Set it inside Cargo.toml (my library has a Rust NIF)
  • Make a GIT commit
  • Make a GIT tag
  • Push to the GIT remote
  • Make a GitHub release
  • Do mix hex.publish

My script is at nearly 200 lines and I still do the last 3 manually.

My main point of criticism, to your tool or any other, is that this is a mechanical logistical work that should have minimal involvement from the programmer.

This is my bucket list above. I use cargo-edit to directly set the version in the Cargo.toml file and mix_version to change the version in mix.exs – I don’t want to rely on UNIX text CLI tools for it. Hopefully that list is useful for you.

For the record, I dig the idea of the changesets a lot but I’ve dragged my feet on releasing my library for years – bad health, general being super busy, family emergencies and previous burnout were involved – so I just wanted to get it over with. I am very likely to revisit sampo soon-ish.

1 Like

Oh, just for the record for you or anyone else, here’s the bash abomination that me and Gemini worked on for about an hour in order to cover most failure modes and use proper native tools and not just use brittle CLI text manipulation: xqlite/scripts/release.sh at 5fd91f15c748f18bf5a0a91ebe39df252ef9f5ed · dimitarvp/xqlite · GitHub

Monstrosity. Just to bump a version in two files and do a little GIT magic.

It’s almost 2026. All this stuff should have been figured out and written in stone 10 years ago. Shameful. But maybe when I retire I’ll have the time.

2 Likes

Thanks for sharing all this context, I really get those periods where everything stacks up, and you’re just trying to ship something…

Tbh the blog post is really there as a deep dive into Sampo, but as I mention in the first paragraphs, it’s absolutely not required reading to use the tool. The actual documentation is in the repo’s README. Sadly writing great docs isn’t exactly my superpower, but I try to keep it concise, practical, and complete enough :sweat_smile:

In practice, getting started with Sampo is very lightweight: you run sampo init once, then sampo add each time you want to add a changeset. The CLI guides you through selecting the bump level, the affected package(s), and the user-facing description, then you just commit the generated markdown file in the PR or commit where the change happens. The defaults should be fine for most projects, so you don’t need to touch the config file at all; but if you do, the config options are described here.

For the release and publishing side, the easiest path is usually the GitHub Action. If you want to try Sampo quickly, you can basically hand your AI agent the main README (or even just the repo link) and/or the Action README, and let it wire up. Once it’s in place, Sampo will automatically open a release PR whenever there are unconsumed changesets, and merging that PR will take care of the changelog, GitHub releases, git tags, and publishing to Hex (with your Hex token set as an environment variable).

If you do end up revisiting Sampo and run into any friction, I’m genuinely interested in hearing about it. Your “bucket list” of steps is exactly the kind of workflow I want to make boring and reliable!

1 Like

I did not notice this. Thanks, this actually makes me want to try it again sooner.

1 Like

You can actually see the GitHub Action in action (lol) on Bruits’ public repos, both Maudit and Sampo itself have currently an automatic release PR open, and there are quite a few closed ones in the history as well.

2 Likes

Hi everyone :waving_hand:

The latest Sampo releases improved Hex/Elixir support (we now recognise @version module attributes in mix.exs), clearer CLI exit codes to distinguish success with no pending changes, and a bunch of bug fixes.

Also, thanks to Rafael Audibert from PostHog (who are in the process of adopting Sampo to publish their various SDKs), we’ve added support for PHP (Packagist) and Python (PyPI).

If you give it a try, I’d really appreciate any feedback or edge cases you run into :slightly_smiling_face:

1 Like

and one more manual step you forgot is to update your README.md with the newly released version

1 Like

Hi @a-maze-d ,

Just to clarify that all stages of @dimitarvp 's message are covered by Sampo. But not « update your README.md with the newly released version » , because I don’t think many projects have the version hard-coded into their README? Most project generally use shields?

But if you have a use case that I haven’t thought of, don’t hesitate to clarify, and I can possibly add the feature to Sampo.

1 Like

To clarify, I mostly meant update the Elixir code block that advises which version to put inside your mix.exs. :smiley:

1 Like