name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
packages: write
pull-requests: write
jobs:
validate-version:
name: Validate Version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
version_without_v: ${{ steps.version.outputs.version_without_v }}
steps:
- name: Determine version
id: version
run: |
VERSION="${{ github.ref_name }}"
# Validate version format
if ! [[ "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "Invalid version format: $VERSION"
echo "Expected format: v1.0.0, v1.0.0-beta.1, etc."
exit 1
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "version_without_v=${VERSION#v}" >> $GITHUB_OUTPUT
build-artifacts:
name: Build Release Artifacts
needs: [validate-version]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
asset_name: meilibridge-linux-amd64
- os: macos-latest
target: x86_64-apple-darwin
asset_name: meilibridge-darwin-amd64
- os: macos-latest
target: aarch64-apple-darwin
asset_name: meilibridge-darwin-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
asset_name: meilibridge-windows-amd64.exe
steps:
- uses: actions/checkout@v4
- name: Update version in Cargo.toml
shell: bash
run: |
VERSION="${{ needs.validate-version.outputs.version_without_v }}"
if [[ "${{ matrix.os }}" == "macos-latest" ]]; then
# macOS requires backup extension for sed -i
sed -i.bak "s/^version = .*/version = \"$VERSION\"/" Cargo.toml
rm -f Cargo.toml.bak
else
# Linux and Windows (Git Bash) don't require backup extension
sed -i "s/^version = .*/version = \"$VERSION\"/" Cargo.toml
fi
echo "Updated Cargo.toml version to: $VERSION"
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Build release binary
run: |
cargo build --release --target ${{ matrix.target }}
- name: Package binary
shell: bash
run: |
cd target/${{ matrix.target }}/release
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
7z a -tzip "../../../${{ matrix.asset_name }}.zip" meilibridge.exe
else
tar czf "../../../${{ matrix.asset_name }}.tar.gz" meilibridge
fi
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.asset_name }}
path: |
*.tar.gz
*.zip
create-release:
name: Create GitHub Release
needs: [validate-version, build-artifacts]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: ./artifacts
- name: Generate changelog
id: changelog
run: |
# Get the previous tag
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [[ -z "$PREV_TAG" ]]; then
echo "No previous tag found, including all commits"
RANGE=""
else
echo "Previous tag: $PREV_TAG"
RANGE="$PREV_TAG.."
fi
# Generate changelog
{
echo "## What's Changed"
echo ""
# Features
echo "### 🚀 Features"
git log $RANGE --pretty=format:"* %s (%h) - %an" --grep="^feat" | head -20 || echo "* No new features"
echo ""
# Bug fixes
echo "### 🐛 Bug Fixes"
git log $RANGE --pretty=format:"* %s (%h) - %an" --grep="^fix" | head -20 || echo "* No bug fixes"
echo ""
# Other changes
echo "### 📚 Documentation & Other Changes"
git log $RANGE --pretty=format:"* %s (%h) - %an" --grep="^docs\|^chore\|^refactor\|^style\|^test" | head -20 || echo "* No other changes"
echo ""
# Contributors
echo "### 👥 Contributors"
git log $RANGE --pretty=format:"%an" | sort -u | sed 's/^/* /' | head -20
echo ""
echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG:-${{ needs.validate-version.outputs.version }}}...${{ needs.validate-version.outputs.version }}"
} > CHANGELOG_RELEASE.md
# Read changelog for output
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "body<<$EOF" >> $GITHUB_OUTPUT
cat CHANGELOG_RELEASE.md >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT
- name: Create release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.validate-version.outputs.version }}
name: Release ${{ needs.validate-version.outputs.version }}
body: ${{ steps.changelog.outputs.body }}
draft: false
prerelease: ${{ contains(needs.validate-version.outputs.version, '-') }}
files: |
artifacts/**/*.tar.gz
artifacts/**/*.zip
generate_release_notes: true
publish-crates:
name: Publish to crates.io
needs: [validate-version]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Update version in Cargo.toml
run: |
VERSION="${{ needs.validate-version.outputs.version_without_v }}"
sed -i "s/^version = .*/version = \"$VERSION\"/" Cargo.toml
- name: Verify package
run: cargo package --allow-dirty
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
cargo publish --allow-dirty --token $CARGO_REGISTRY_TOKEN
docker-release:
name: Docker Release
needs: [validate-version]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update version in Cargo.toml
run: |
VERSION="${{ needs.validate-version.outputs.version_without_v }}"
sed -i "s/^version = .*/version = \"$VERSION\"/" Cargo.toml
echo "Updated Cargo.toml version to: $VERSION"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./docker/Dockerfile
platforms: linux/amd64
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/meilibridge:${{ needs.validate-version.outputs.version_without_v }}
${{ secrets.DOCKERHUB_USERNAME }}/meilibridge:latest
build-args: |
VERSION=${{ needs.validate-version.outputs.version_without_v }}
cache-from: type=gha
cache-to: type=gha,mode=max
update-changelog:
name: Update Changelog
needs: [validate-version, create-release]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
persist-credentials: true
ref: main
- name: Update CHANGELOG.md
run: |
VERSION="${{ needs.validate-version.outputs.version_without_v }}"
DATE=$(date +%Y-%m-%d)
# Update CHANGELOG.md only
sed -i "s/## \[Unreleased\]/## [Unreleased]\n\n## [$VERSION] - $DATE/" CHANGELOG.md
- name: Configure Git
run: |
git config --global user.name "Github Actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Commit and push changes
run: |
git add CHANGELOG.md
if git diff --staged --quiet; then
echo "No changes to commit"
else
git commit -m "chore: update CHANGELOG.md for ${{ needs.validate-version.outputs.version }} release
Updates CHANGELOG.md with release date for version ${{ needs.validate-version.outputs.version }}.
[skip ci]"
git push
fi