chore(release): 0.19.0 #33
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - '[0-9]+.[0-9]+.[0-9]+*' # Matches semver tags like 0.1.0, 1.0.0, etc. | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Tag to release (semver format, e.g., 0.1.0)' | |
| required: true | |
| type: string | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| jobs: | |
| create-release: | |
| name: Create Release | |
| runs-on: ubuntu-latest | |
| outputs: | |
| upload_url: ${{ steps.create_release.outputs.upload_url }} | |
| release_id: ${{ steps.create_release.outputs.id }} | |
| version: ${{ steps.get_version.outputs.VERSION }} | |
| changelog: ${{ steps.changelog.outputs.CHANGELOG }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Fetch full history for changelog generation | |
| - name: Get version from tag | |
| id: get_version | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| VERSION="${{ github.event.inputs.tag }}" | |
| else | |
| VERSION="${GITHUB_REF#refs/tags/}" | |
| fi | |
| echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT | |
| echo "VERSION_NO_V=${VERSION#v}" >> $GITHUB_OUTPUT | |
| - name: Validate semver format | |
| run: | | |
| VERSION="${{ steps.get_version.outputs.VERSION }}" | |
| # POSIX-compatible regex check for semver | |
| case "$VERSION" in | |
| [0-9]*.[0-9]*.[0-9]*) | |
| # Basic semver format matched | |
| ;; | |
| *) | |
| echo "Error: Tag '$VERSION' is not a valid semver format" | |
| echo "Expected format: X.Y.Z, X.Y.Z-prerelease, or X.Y.Z+build" | |
| exit 1 | |
| ;; | |
| esac | |
| - 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 | |
| CHANGELOG="Initial release" | |
| else | |
| # Generate changelog from commits since last tag | |
| CHANGELOG=$(git log ${PREV_TAG}..HEAD --pretty=format:"- %s" --no-merges | head -20) | |
| if [ -z "$CHANGELOG" ]; then | |
| CHANGELOG="- No changes since last release" | |
| fi | |
| fi | |
| # Escape for GitHub output (POSIX compatible) | |
| CHANGELOG=$(printf '%s\n' "$CHANGELOG" | sed 's/%/%25/g; s/\r/%0D/g' | awk '{printf "%s%0A", $0}' | sed 's/%0A$//') | |
| echo "CHANGELOG=$CHANGELOG" >> $GITHUB_OUTPUT | |
| - name: Check if release exists | |
| id: check_release | |
| run: | | |
| VERSION="${{ steps.get_version.outputs.VERSION }}" | |
| if gh release view "$VERSION" >/dev/null 2>&1; then | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "exists=false" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create Release | |
| id: create_release | |
| if: steps.check_release.outputs.exists == 'false' | |
| run: | | |
| VERSION="${{ steps.get_version.outputs.version }}" | |
| IS_PRERELEASE="false" | |
| # Check if this is a prerelease (contains - or is 0.x.x) | |
| case "$VERSION" in | |
| *-*|0.*) | |
| IS_PRERELEASE="true" | |
| ;; | |
| esac | |
| # Create the release as draft | |
| if [ "$IS_PRERELEASE" = "true" ]; then | |
| gh release create "$VERSION" \ | |
| --title "Release $VERSION" \ | |
| --notes "${{ steps.changelog.outputs.CHANGELOG }}" \ | |
| --draft \ | |
| --prerelease | |
| else | |
| gh release create "$VERSION" \ | |
| --title "Release $VERSION" \ | |
| --notes "${{ steps.changelog.outputs.CHANGELOG }}" \ | |
| --draft | |
| fi | |
| # Get release info | |
| RELEASE_INFO=$(gh release view "$VERSION" --json id,uploadUrl) | |
| RELEASE_ID=$(echo "$RELEASE_INFO" | jq -r '.id') | |
| UPLOAD_URL=$(echo "$RELEASE_INFO" | jq -r '.uploadUrl') | |
| echo "id=${RELEASE_ID}" >> $GITHUB_OUTPUT | |
| echo "upload_url=${UPLOAD_URL}" >> $GITHUB_OUTPUT | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| build: | |
| name: Build ${{ matrix.target }} | |
| needs: create-release | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-unknown-linux-musl | |
| os: ubuntu-latest | |
| archive: tar.gz | |
| - target: aarch64-unknown-linux-musl | |
| os: ubuntu-22.04-arm | |
| archive: tar.gz | |
| - target: x86_64-pc-windows-msvc | |
| os: windows-latest | |
| archive: zip | |
| - target: aarch64-pc-windows-msvc | |
| os: windows-11-arm | |
| archive: zip | |
| - target: x86_64-apple-darwin | |
| os: macos-14 | |
| archive: tar.gz | |
| - target: aarch64-apple-darwin | |
| os: macos-15 | |
| archive: tar.gz | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@1.91.1 | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Setup protoc | |
| uses: arduino/setup-protoc@v3 | |
| with: | |
| repo-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Cache dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ endsWith(matrix.target, '-unknown-linux-musl') && 'docker-alpine' || runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ endsWith(matrix.target, '-unknown-linux-musl') && 'docker-alpine' || runner.os }}-${{ matrix.target }}-cargo- | |
| ${{ endsWith(matrix.target, '-unknown-linux-musl') && 'docker-alpine' || runner.os }}-cargo- | |
| - name: Build binary | |
| shell: sh | |
| run: | | |
| if [ "${{ runner.os }}" = "Linux" ]; then | |
| # Use official Rust Docker image for Linux builds | |
| # Disable default features to avoid ONNX Runtime issues (same as Windows) | |
| docker run --rm \ | |
| -v "$(pwd):/workspace" \ | |
| -w /workspace \ | |
| rust:1.91.1-alpine3.21 \ | |
| sh -c " | |
| apk add --no-cache git musl-dev openssl-dev openssl-libs-static pkgconfig gcc g++ protobuf-dev && \ | |
| rustup target add ${{ matrix.target }} && \ | |
| cargo build --release --target ${{ matrix.target }} --no-default-features | |
| " | |
| elif [ "${{ runner.os }}" = "Windows" ]; then | |
| # Windows: disable default features to avoid ONNX Runtime issues | |
| cargo build --release --target ${{ matrix.target }} --no-default-features | |
| else | |
| # Native build for other platforms (macOS) | |
| cargo build --release --target ${{ matrix.target }} | |
| fi | |
| - name: Create archive directory | |
| shell: sh | |
| run: mkdir -p dist | |
| - name: Create archive (Unix) | |
| if: matrix.archive == 'tar.gz' | |
| shell: sh | |
| run: | | |
| cd target/${{ matrix.target }}/release | |
| case "${{ matrix.target }}" in | |
| *windows*) | |
| tar czf ../../../dist/octomind-${{ needs.create-release.outputs.version }}-${{ matrix.target }}.tar.gz octomind.exe | |
| ;; | |
| *) | |
| tar czf ../../../dist/octomind-${{ needs.create-release.outputs.version }}-${{ matrix.target }}.tar.gz octomind | |
| ;; | |
| esac | |
| - name: Create archive (Windows) | |
| if: matrix.archive == 'zip' | |
| shell: sh | |
| run: | | |
| cd target/${{ matrix.target }}/release | |
| 7z a ../../../dist/octomind-${{ needs.create-release.outputs.version }}-${{ matrix.target }}.zip octomind.exe | |
| - name: Upload release asset | |
| shell: sh | |
| run: | | |
| gh release upload "${{ needs.create-release.outputs.version }}" \ | |
| "./dist/octomind-${{ needs.create-release.outputs.version }}-${{ matrix.target }}.${{ matrix.archive }}" \ | |
| --clobber | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| publish-crate: | |
| name: Publish to Crates.io | |
| needs: [create-release, build] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@1.91.1 | |
| - name: Setup protoc | |
| uses: arduino/setup-protoc@v3 | |
| with: | |
| repo-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Cache dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-publish-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-publish- | |
| ${{ runner.os }}-cargo- | |
| - name: Validate version matches tag | |
| run: | | |
| VERSION="${{ needs.create-release.outputs.version }}" | |
| CARGO_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/') | |
| if [ "$VERSION" != "$CARGO_VERSION" ]; then | |
| echo "❌ Version mismatch!" | |
| echo "Git tag: $VERSION" | |
| echo "Cargo.toml: $CARGO_VERSION" | |
| echo "Please update Cargo.toml version to match the git tag" | |
| exit 1 | |
| fi | |
| echo "✅ Version validation passed: $VERSION" | |
| - name: Check if version already published | |
| id: check_published | |
| run: | | |
| VERSION="${{ needs.create-release.outputs.version }}" | |
| # Check if this version already exists on crates.io | |
| if cargo search octomind --limit 1 | grep -q "octomind = \"$VERSION\""; then | |
| echo "already_published=true" >> $GITHUB_OUTPUT | |
| echo "⚠️ Version $VERSION already published to crates.io" | |
| else | |
| echo "already_published=false" >> $GITHUB_OUTPUT | |
| echo "✅ Version $VERSION not yet published" | |
| fi | |
| - name: Dry run publish | |
| if: steps.check_published.outputs.already_published == 'false' | |
| run: | | |
| echo "🔍 Running dry-run publish to validate package..." | |
| cargo publish --dry-run --no-default-features | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} | |
| - name: Publish to crates.io | |
| if: steps.check_published.outputs.already_published == 'false' | |
| run: | | |
| echo "🚀 Publishing to crates.io..." | |
| cargo publish --no-default-features | |
| echo "✅ Successfully published to crates.io!" | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} | |
| - name: Skip publishing | |
| if: steps.check_published.outputs.already_published == 'true' | |
| run: | | |
| echo "⏭️ Skipping crates.io publish - version already exists" | |
| finalize-release: | |
| name: Finalize Release | |
| needs: [create-release, build, publish-crate] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Publish release | |
| run: | | |
| VERSION="${{ needs.create-release.outputs.version }}" | |
| # Update release notes with download links | |
| cat > release_notes.md << EOF | |
| ## 🚀 What's Changed | |
| ${{ needs.create-release.outputs.changelog }} | |
| ## 📦 Installation | |
| ### Quick Install Script (Universal) | |
| \`\`\`bash | |
| curl -fsSL https://raw.githubusercontent.com/${{ github.repository }}/main/install.sh | sh | |
| \`\`\` | |
| **Works on:** Linux, macOS, Windows (Git Bash/WSL/MSYS2), and any Unix-like system | |
| ### Manual Download | |
| | Platform | Architecture | Download | | |
| |----------|--------------|----------| | |
| | Linux | x86_64 (static) | [octomind-${VERSION}-x86_64-unknown-linux-musl.tar.gz](https://github.com/${{ github.repository }}/releases/download/${VERSION}/octomind-${VERSION}-x86_64-unknown-linux-musl.tar.gz) | | |
| | Linux | ARM64 (static) | [octomind-${VERSION}-aarch64-unknown-linux-musl.tar.gz](https://github.com/${{ github.repository }}/releases/download/${VERSION}/octomind-${VERSION}-aarch64-unknown-linux-musl.tar.gz) | | |
| | Windows | x86_64 | [octomind-${VERSION}-x86_64-pc-windows-msvc.zip](https://github.com/${{ github.repository }}/releases/download/${VERSION}/octomind-${VERSION}-x86_64-pc-windows-msvc.zip) | | |
| | Windows | ARM64 | [octomind-${VERSION}-aarch64-pc-windows-msvc.zip](https://github.com/${{ github.repository }}/releases/download/${VERSION}/octomind-${VERSION}-aarch64-pc-windows-msvc.zip) | | |
| | macOS | x86_64 | [octomind-${VERSION}-x86_64-apple-darwin.tar.gz](https://github.com/${{ github.repository }}/releases/download/${VERSION}/octomind-${VERSION}-x86_64-apple-darwin.tar.gz) | | |
| | macOS | ARM64 | [octomind-${VERSION}-aarch64-apple-darwin.tar.gz](https://github.com/${{ github.repository }}/releases/download/${VERSION}/octomind-${VERSION}-aarch64-apple-darwin.tar.gz) | | |
| ### Using Cargo (from crates.io) | |
| \`\`\`bash | |
| cargo install octomind | |
| \`\`\` | |
| ### Using Cargo (from Git) | |
| \`\`\`bash | |
| cargo install --git https://github.com/${{ github.repository }} | |
| \`\`\` | |
| ### Verify Installation | |
| \`\`\`bash | |
| octomind --version | |
| \`\`\` | |
| EOF | |
| # Publish the release (remove draft status) | |
| gh release edit "$VERSION" --draft=false --notes-file release_notes.md | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| docker: | |
| name: Build and Push Docker Images | |
| needs: [create-release, build, publish-crate] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set lowercase repository name | |
| id: repo | |
| run: echo "repository=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: . | |
| platforms: linux/amd64,linux/arm64 | |
| push: true | |
| tags: | | |
| ghcr.io/${{ steps.repo.outputs.repository }}:latest | |
| ghcr.io/${{ steps.repo.outputs.repository }}:${{ needs.create-release.outputs.version }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max |