GoulvenClech

GoulvenClech

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!

Most Liked

GoulvenClech

GoulvenClech

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!

GoulvenClech

GoulvenClech

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:

dimitarvp

dimitarvp

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.

Last Post!

a-maze-d

a-maze-d

@GoulvenClech, sorry for the delay to answer.

Use cases I see missing are those where you have markdown/text files, like the README.md where you give instructions how to install your library. The default mix new my_app will create such instruction and therefore it’s common to see it in Elixir libraries (and I do like it because you can easily copy&paste it)

There are other use cases like livebook examples, or installation instructions in docs.

As the last example illustrates, it can even be within a link

For docs you can maybe get away with using a handcrafted mix job that you can run before the docs are created, but it would be neater if Sampo would support it

I hope that makes sense

Where Next?

Popular in Announcing Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36654 110
New
jakub-zawislak
Hi everyone, I’m coming from the Symfony (PHP) framework. I like Phoenix, but it has a one thing that was build much better in the Symfo...
New
Crowdhailer
Raxx is an alternative to Plug and is inspired by projects such as Rack(Ruby) and Ring(Clojure). 1.0-rc.1 is now available. To use it re...
New
anshuman23
Hello all, I have been working on my proposed project called Tensorflex as part of Google Summer of Code 2018.. Tensorflex can be used f...
New
mindok
What is ContEx? A pure Elixir server-side data plotting/charting library outputting SVG. It has nice barcharts in particular and works g...
New
mplatts
With HEEX released we decided to start a components library using Tailwind CSS - check it out here: Petal Components. We also have a boi...
New
wojtekmach
Hey everyone! Req is an HTTP client for Elixir that I’ve been working on for quite some time. There is already a lot of HTTP clients out...
New

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New

We're in Beta

About us Mission Statement