From 50cdd17ef30a0c8fb4534caade1f3fbf72d77edc Mon Sep 17 00:00:00 2001 From: jon Date: Sat, 31 Jan 2026 10:48:04 +0000 Subject: [PATCH] chore: add CI/CD and release workflows - Improve CI workflow: add concurrency, skip-ci support, remove unnecessary build step - Add release workflow: manual version bumping (patch/minor/major), auto-tag, multi-platform builds (macOS ARM, Linux x64), auto-cleanup on failure - Changelog generation from PR labels - Build validation (fmt, clippy, test) before release tagging --- .github/workflows/ci.yml | 20 ++- .github/workflows/release.yml | 256 ++++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b137dcf..e3bbe65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,10 @@ on: pull_request: branches: [main] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: CARGO_TERM_COLOR: always RUSTFLAGS: -Dwarnings @@ -14,6 +18,7 @@ jobs: check: name: Check runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[skip ci]')" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -23,6 +28,7 @@ jobs: fmt: name: Format runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[skip ci]')" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -33,6 +39,7 @@ jobs: clippy: name: Clippy runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[skip ci]')" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable @@ -44,20 +51,9 @@ jobs: test: name: Test runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[skip ci]')" steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - run: cargo test - - build: - name: Build Release - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest] - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - run: cargo build --release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..68e37d0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,256 @@ +name: Release + +on: + workflow_dispatch: + inputs: + version_bump: + description: 'Version bump type' + required: true + default: 'patch' + type: choice + options: + - patch + - minor + - major + +concurrency: + group: release + cancel-in-progress: false + +permissions: + contents: write + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: -Dwarnings + BINARY_NAME: olx-tracker + +jobs: + checks: + name: Checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + - name: Format + run: cargo fmt --all -- --check + - name: Clippy + run: cargo clippy --all-targets + - name: Test + run: cargo test + + create-tag: + name: Create Tag + needs: checks + runs-on: ubuntu-latest + outputs: + version: ${{ steps.bump.outputs.version }} + tag: ${{ steps.bump.outputs.tag }} + commit_sha: ${{ steps.commit.outputs.sha }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify on main branch + run: | + if [ "${{ github.ref }}" != "refs/heads/main" ]; then + echo "::error::Release must be run from main branch, not ${{ github.ref }}" + exit 1 + fi + + - name: Get current version + id: current + run: | + # Get version from Cargo.toml + VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/') + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Current version: $VERSION" + + - name: Bump version + id: bump + run: | + CURRENT="${{ steps.current.outputs.version }}" + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT" + + case "${{ inputs.version_bump }}" in + major) + MAJOR=$((MAJOR + 1)) + MINOR=0 + PATCH=0 + ;; + minor) + MINOR=$((MINOR + 1)) + PATCH=0 + ;; + patch) + PATCH=$((PATCH + 1)) + ;; + esac + + NEW_VERSION="$MAJOR.$MINOR.$PATCH" + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "tag=v$NEW_VERSION" >> $GITHUB_OUTPUT + echo "New version: $NEW_VERSION" + + - name: Update Cargo.toml + run: | + sed -i 's/^version = ".*"/version = "${{ steps.bump.outputs.version }}"/' Cargo.toml + cat Cargo.toml | head -10 + + - name: Commit and tag + id: commit + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Cargo.toml + git commit -m "chore: bump version to ${{ steps.bump.outputs.version }}" + echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + git tag -a "${{ steps.bump.outputs.tag }}" -m "Release ${{ steps.bump.outputs.tag }}" + git push origin main --follow-tags + + build: + name: Build (${{ matrix.target }}) + needs: create-tag + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: macos-latest + target: aarch64-apple-darwin + artifact: olx-tracker-macos-arm64 + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + artifact: olx-tracker-linux-x64 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ needs.create-tag.outputs.tag }} + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Cache + uses: Swatinem/rust-cache@v2 + with: + key: release-${{ matrix.target }} + + - name: Build + run: cargo build --release --target ${{ matrix.target }} + + - name: Package (Unix) + run: | + cd target/${{ matrix.target }}/release + tar -czvf ../../../${{ matrix.artifact }}.tar.gz ${{ env.BINARY_NAME }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: ${{ matrix.artifact }}.tar.gz + + release: + name: Create Release + needs: [create-tag, build] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ needs.create-tag.outputs.tag }} + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Get previous tag + id: prev_tag + run: | + PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + echo "tag=$PREV_TAG" >> $GITHUB_OUTPUT + echo "Previous tag: $PREV_TAG" + + - name: Build Changelog + id: changelog + uses: mikepenz/release-changelog-builder-action@v5 + with: + configurationJson: | + { + "categories": [ + { "title": "## Features", "labels": ["feature", "enhancement"] }, + { "title": "## Bug Fixes", "labels": ["bug", "fix"] }, + { "title": "## Documentation", "labels": ["documentation", "docs"] }, + { "title": "## Other Changes", "labels": [] } + ], + "template": "#{{CHANGELOG}}\n\n**Full Changelog**: #{{RELEASE_DIFF}}", + "pr_template": "- #{{TITLE}} (#{{NUMBER}})", + "empty_template": "No pull requests since last release." + } + fromTag: ${{ steps.prev_tag.outputs.tag }} + toTag: ${{ needs.create-tag.outputs.tag }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.create-tag.outputs.tag }} + name: ${{ env.BINARY_NAME }} ${{ needs.create-tag.outputs.tag }} + body: | + ## Installation + + Download the appropriate binary for your platform: + + | Platform | File | + |----------|------| + | macOS (Apple Silicon) | `olx-tracker-macos-arm64.tar.gz` | + | Linux (x64) | `olx-tracker-linux-x64.tar.gz` | + + ### Quick Install + + ```bash + # Linux/macOS + tar -xzf olx-tracker-*.tar.gz + chmod +x olx-tracker + ./olx-tracker --help + ``` + + ${{ steps.changelog.outputs.changelog }} + files: artifacts/**/*.tar.gz + draft: false + prerelease: false + + cleanup: + name: Cleanup on failure + needs: [create-tag, build] + if: failure() && needs.create-tag.result == 'success' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Delete tag and revert commit + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + echo "Deleting tag ${{ needs.create-tag.outputs.tag }}..." + git push --delete origin ${{ needs.create-tag.outputs.tag }} || true + + echo "Reverting version bump commit ${{ needs.create-tag.outputs.commit_sha }}..." + git pull origin main + git revert ${{ needs.create-tag.outputs.commit_sha }} --no-edit + git push origin main