ref:a3f0318b5ae6ee1965cb4b62b2f49b93435d58c3

Add automatic CI-driven release process (#4)

## Summary - Add CalVer (`YYYY.M.BUILD`) release automation driven by Anvil CI - Modeled after huorn-minecraft's `ci/release.sh` + `.anvil-ci.yml` pattern - On every `main` push, after all checks pass: compute version, build, generate changelog, create Anvil release, tag and push Closes #21 ## Changes - **ci/release.sh** — CalVer version computation from git tags (increment build within month, reset on new month) - **mix.exs** — Version from `$VERSION` env var, defaults to `"0.0.0-dev"` for local builds - **.anvil.yml** — New `release` step after compile/format/dialyzer/test, with branch guard and idempotency check ## Release step workflow 1. Guard: skip if not on `main` 2. Compute version via `ci/release.sh` 3. Idempotency: skip if tag already exists 4. Build with `VERSION` env var 5. Generate changelog from `git log` since last tag 6. Build hex package and docs 7. Create Anvil release with changelog 8. Tag commit and push tag ## Test plan - [x] `ci/release.sh` outputs `2026.4.1` with no existing tags - [x] `ci/release.sh` increments build within same month (`2026.4.1` -> `2026.4.2`) - [x] `ci/release.sh` resets build on month change (`2026.3.5` -> `2026.4.1`) - [x] `mix compile` works without `$VERSION` set (uses `0.0.0-dev`) - [x] `VERSION=2026.4.1 mix eval 'Mix.Project.config()[:version]'` returns correct version - [x] All 547 tests pass, dialyzer clean
SHA: a3f0318b5ae6ee1965cb4b62b2f49b93435d58c3
Author: Anvil <noreply@anvil.fangorn.io>
Date: 2026-04-09 16:15
Parents: f3281b6
3 files changed +82 -1
Type
.anvil.yml +54 −0
@@ -39,6 +39,8 @@
export MIX_HOME=/workspace/.mix
mix test --cover --export-coverage default
mix run --no-start -e '
tools_ebin = Path.wildcard("/usr/local/lib/erlang/lib/tools-*/ebin") |> List.first()
if tools_ebin, do: Code.append_path(tools_ebin)
:cover.start()
:cover.import(~c"cover/default.coverdata")
modules = :cover.imported_modules()
@@ -65,3 +67,55 @@
artifacts:
- name: lcov.info
path: cover/lcov.info
- name: release
run: |
set -e
apt-get update && apt-get install -y --no-install-recommends git jq
git config --global --add safe.directory /workspace
export MIX_HOME=/workspace/.mix
# Only release from main branch
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$BRANCH" != "main" ]; then
echo "Skipping release: not on main (on $BRANCH)"
exit 0
fi
# Compute CalVer version
VERSION=$(bash ci/release.sh)
echo "Releasing version: $VERSION"
# Idempotency: skip if this version tag already exists
if git tag -l "$VERSION" | grep -q .; then
echo "Tag $VERSION already exists, skipping release"
exit 0
fi
# Build with version
export VERSION
mix compile
# Generate changelog from commits since last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || git rev-list --max-parents=0 HEAD)
CHANGELOG=$(git log --oneline "$LAST_TAG"..HEAD)
# Generate hex package
mix hex.build
HEX_TAR=$(ls ex_git_objectstore-*.tar 2>/dev/null | head -1 || true)
# Generate docs
mix docs
tar czf ex_git_objectstore-${VERSION}-docs.tar.gz doc/
# Create Anvil release
anvil release create \
--tag "$VERSION" \
--title "ExGitObjectstore $VERSION" \
--body "$CHANGELOG"
# Tag and push
git tag "$VERSION"
git push origin "$VERSION"
echo "Released $VERSION"
depends_on: [compile, format, dialyzer, test]
mix.exs +2 −1
@@ -16,11 +16,12 @@
use Mix.Project
@source_url "https://github.com/notifd/ex_git_objectstore"
@version System.get_env("VERSION", "0.0.0-dev")
def project do
[
app: :ex_git_objectstore,
version: @version,
version: "0.1.0",
elixir: "~> 1.18",
start_permanent: Mix.env() == :prod,
deps: deps(),