From 3958928a9aac8de61d577392aa206fe157ffbf24 Mon Sep 17 00:00:00 2001 From: Rishi Tank Date: Mon, 5 Jan 2026 22:02:52 +0000 Subject: [PATCH 1/4] fix: auto-bump patch version when tag exists in workflow_run trigger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, when the Release workflow was triggered by workflow_run (after CI completes on main), it would skip the release if the current version tag already existed. This required manual version bumps before each release. Now, when triggered by workflow_run: - If current version tag doesn't exist → release with current version - If current version tag exists → auto-bump patch version, update Cargo.toml, then release This makes releases fully automatic after merging PRs to main. --- .github/workflows/release.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2c87cf..9f74c6c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -108,8 +108,14 @@ jobs: # Check if this version tag already exists if git tag -l "v$CURRENT_VERSION" | grep -q .; then - echo "Tag v$CURRENT_VERSION already exists, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT + echo "Tag v$CURRENT_VERSION already exists, auto-bumping patch version" + # Auto-bump patch version + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" + NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" + echo "Auto-bumped to: $NEW_VERSION" + echo "should_release=true" >> $GITHUB_OUTPUT + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "needs_bump=true" >> $GITHUB_OUTPUT else echo "New version v$CURRENT_VERSION detected, will release" echo "should_release=true" >> $GITHUB_OUTPUT From 8f78d7bf890f3cf91bb97ae7496255085f5d11ca Mon Sep 17 00:00:00 2001 From: Rishi Tank Date: Mon, 5 Jan 2026 22:16:10 +0000 Subject: [PATCH 2/4] fix: use RELEASE_TOKEN for auto-bump in release workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Uses RELEASE_TOKEN (a PAT with Contents write permission) to bypass branch protection when auto-bumping the version. Setup required: 1. Create a Fine-Grained PAT at GitHub Settings → Developer Settings → Personal Access Tokens 2. Grant it 'Contents: Read and write' permission for this repo 3. Add it as a repository secret named RELEASE_TOKEN The workflow falls back to GITHUB_TOKEN if RELEASE_TOKEN is not set, which will fail on protected branches but work on unprotected ones. --- .github/workflows/release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9f74c6c..725dab4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -129,6 +129,7 @@ jobs: echo "should_release=false" >> $GITHUB_OUTPUT # Bump version in Cargo.toml if needed + # Uses RELEASE_TOKEN (PAT) to bypass branch protection, falls back to GITHUB_TOKEN bump-version: needs: check if: needs.check.outputs.should_release == 'true' && needs.check.outputs.needs_bump == 'true' @@ -136,7 +137,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} - name: Update Cargo.toml version run: | @@ -154,7 +155,7 @@ jobs: git commit -m "chore: bump version to $VERSION [skip ci]" git push env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} build: needs: [check, bump-version] From b4453e632e5d30afeda0cc5cc6c853979ab13a80 Mon Sep 17 00:00:00 2001 From: Rishi Tank Date: Mon, 5 Jan 2026 23:28:48 +0000 Subject: [PATCH 3/4] fix: replace OpenAI with git-cliff for changelog generation - Remove OpenAI API dependency for changelog generation - Use git-cliff (Rust-based, free, no API keys needed) - Parses conventional commits automatically - Categorizes commits with emoji prefixes - Falls back to simple format if git-cliff fails --- .github/workflows/release.yml | 238 ++++++++++------------------------ 1 file changed, 71 insertions(+), 167 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 725dab4..bc99c73 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -206,7 +206,7 @@ jobs: name: ${{ matrix.artifact }} path: ${{ matrix.artifact }} - # Generate AI-powered changelog + # Generate changelog using git-cliff (fast, free, no API keys needed) changelog: needs: [check, bump-version, build] if: | @@ -224,177 +224,80 @@ jobs: - name: Pull latest changes run: git pull origin ${{ github.ref_name }} || true - - name: Get previous tag - id: prev_tag + - name: Install git-cliff run: | - # Get the most recent tag before this release - PREV_TAG=$(git tag -l 'v*' --sort=-v:refname | head -2 | tail -1) - if [ -z "$PREV_TAG" ]; then - # No previous tag, use first commit - PREV_TAG=$(git rev-list --max-parents=0 HEAD) - fi - echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT - echo "Previous tag: $PREV_TAG" + # Install git-cliff (Rust-based changelog generator) + curl -sSfL https://github.com/orhun/git-cliff/releases/download/v2.7.0/git-cliff-2.7.0-x86_64-unknown-linux-gnu.tar.gz | tar xz + sudo mv git-cliff-2.7.0/git-cliff /usr/local/bin/ + git-cliff --version - - name: Collect commits - id: commits - run: | - PREV_TAG="${{ steps.prev_tag.outputs.prev_tag }}" - - # Get commits with conventional commit format parsing - echo "## Commits since $PREV_TAG" > commits.md - echo "" >> commits.md - - # Group commits by type - git log "$PREV_TAG"..HEAD --pretty=format:"%s|%h|%an" | while IFS='|' read -r msg hash author; do - echo "$msg|$hash|$author" - done > raw_commits.txt - - # Parse and categorize commits - echo "### ✨ Features" > features.md - echo "" >> features.md - echo "### 🐛 Bug Fixes" > fixes.md - echo "" >> fixes.md - echo "### 🔒 Security" > security.md - echo "" >> security.md - echo "### 📚 Documentation" > docs.md - echo "" >> docs.md - echo "### 🔧 Maintenance" > chores.md - echo "" >> chores.md - echo "### 🎨 Refactoring" > refactor.md - echo "" >> refactor.md - echo "### ⚡ Performance" > perf.md - echo "" >> perf.md - echo "### 🧪 Tests" > tests.md - echo "" >> tests.md - echo "### 📦 Other Changes" > other.md - echo "" >> other.md - - while IFS='|' read -r msg hash author; do - # Extract type from conventional commit - if [[ "$msg" =~ ^feat(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> features.md - elif [[ "$msg" =~ ^fix(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> fixes.md - elif [[ "$msg" =~ ^security(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> security.md - elif [[ "$msg" =~ ^docs?(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> docs.md - elif [[ "$msg" =~ ^chore(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> chores.md - elif [[ "$msg" =~ ^refactor(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> refactor.md - elif [[ "$msg" =~ ^perf(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> perf.md - elif [[ "$msg" =~ ^test(\(.+\))?:\ (.+) ]]; then - scope="${BASH_REMATCH[1]}" - desc="${BASH_REMATCH[2]}" - echo "- ${desc} (\`${hash}\`) - @${author}" >> tests.md - else - # Other commits - echo "- ${msg} (\`${hash}\`) - @${author}" >> other.md - fi - done < raw_commits.txt - - - name: Generate changelog with AI + - name: Generate changelog id: generate - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | VERSION="${{ needs.check.outputs.version }}" - # Combine categorized commits - { - echo "# Release v${VERSION}" - echo "" - echo "Released on $(date '+%Y-%m-%d')" - echo "" - - # Add non-empty sections - for file in features.md fixes.md security.md docs.md refactor.md perf.md tests.md chores.md other.md; do - if [ -f "$file" ] && [ $(wc -l < "$file") -gt 2 ]; then - cat "$file" - echo "" - fi - done - } > changelog_draft.md - - # If OpenAI API key is available, enhance with AI - if [ -n "$OPENAI_API_KEY" ]; then - echo "Enhancing changelog with AI..." - - # Prepare prompt for AI - COMMITS=$(cat raw_commits.txt | head -50) - - # Call OpenAI API to generate summary - RESPONSE=$(curl -s https://api.openai.com/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d @- < changelog.md - else - echo "AI summary generation failed, using draft changelog" - cp changelog_draft.md changelog.md - fi - else - echo "No OpenAI API key, using standard changelog" - cp changelog_draft.md changelog.md + # Get previous tag for range + PREV_TAG=$(git tag -l 'v*' --sort=-v:refname | head -2 | tail -1) + if [ -z "$PREV_TAG" ]; then + PREV_TAG=$(git rev-list --max-parents=0 HEAD) + fi + echo "Generating changelog from $PREV_TAG to HEAD" + + # Create git-cliff config inline + cat > cliff.toml << 'CLIFF_CONFIG' + [changelog] + header = "" + body = """ + # Release v{{ version }} + + Released on {{ timestamp | date(format="%Y-%m-%d") }} + + {% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits %} + - {{ commit.message | split(pat="\n") | first | trim }} (`{{ commit.id | truncate(length=7, end="") }}`) - @{{ commit.author.name }} + {%- endfor %} + {% endfor %} + """ + footer = "" + trim = true + + [git] + conventional_commits = true + filter_unconventional = false + split_commits = false + commit_parsers = [ + { message = "^feat", group = "✨ Features" }, + { message = "^fix", group = "🐛 Bug Fixes" }, + { message = "^security", group = "🔒 Security" }, + { message = "^doc", group = "📚 Documentation" }, + { message = "^perf", group = "⚡ Performance" }, + { message = "^refactor", group = "🎨 Refactoring" }, + { message = "^test", group = "🧪 Tests" }, + { message = "^chore", group = "🔧 Maintenance" }, + { message = "^ci", group = "🔧 Maintenance" }, + { message = "^build", group = "🔧 Maintenance" }, + { message = ".*", group = "📦 Other" }, + ] + filter_commits = false + tag_pattern = "v[0-9].*" + CLIFF_CONFIG + + # Generate changelog for this release + git-cliff --config cliff.toml --tag "v${VERSION}" "${PREV_TAG}..HEAD" > changelog.md + + # If git-cliff fails or produces empty output, fall back to simple format + if [ ! -s changelog.md ]; then + echo "git-cliff produced empty output, using fallback" + { + echo "# Release v${VERSION}" + echo "" + echo "Released on $(date '+%Y-%m-%d')" + echo "" + echo "### 📦 Changes" + echo "" + git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (\`%h\`) - @%an" + } > changelog.md fi # Output changelog for use in release @@ -402,7 +305,8 @@ jobs: cat changelog.md >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - # Also save as artifact + # Display changelog + echo "=== Generated Changelog ===" cat changelog.md - name: Upload changelog artifact From 9489ecee5190f0d9c7f4cf93d965235bd2dd2ae8 Mon Sep 17 00:00:00 2001 From: Rishi Tank Date: Wed, 7 Jan 2026 17:24:23 +0000 Subject: [PATCH 4/4] fix: address CodeRabbit review comments - Upgrade git-cliff from v2.7.0 to v2.11.0 to address CVE-2024-32650 - Add SHA512 checksum verification before installing git-cliff binary - Improve error handling to capture git-cliff exit code - Distinguish between git-cliff failure vs empty output scenarios --- .github/workflows/release.yml | 50 +++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc99c73..15f26e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -226,9 +226,27 @@ jobs: - name: Install git-cliff run: | - # Install git-cliff (Rust-based changelog generator) - curl -sSfL https://github.com/orhun/git-cliff/releases/download/v2.7.0/git-cliff-2.7.0-x86_64-unknown-linux-gnu.tar.gz | tar xz - sudo mv git-cliff-2.7.0/git-cliff /usr/local/bin/ + # Install git-cliff v2.11.0 (Rust-based changelog generator) + # Using latest version to address CVE-2024-32650 in rustls dependency + VERSION="2.11.0" + TARBALL="git-cliff-${VERSION}-x86_64-unknown-linux-gnu.tar.gz" + DOWNLOAD_URL="https://github.com/orhun/git-cliff/releases/download/v${VERSION}" + + # Download the tarball and checksum + curl -sSfL "${DOWNLOAD_URL}/${TARBALL}" -o "${TARBALL}" + curl -sSfL "${DOWNLOAD_URL}/${TARBALL}.sha512" -o "${TARBALL}.sha512" + + # Verify checksum before installation + echo "Verifying checksum..." + sha512sum -c "${TARBALL}.sha512" + + # Extract and install + tar xzf "${TARBALL}" + sudo mv "git-cliff-${VERSION}/git-cliff" /usr/local/bin/ + + # Cleanup + rm -rf "${TARBALL}" "${TARBALL}.sha512" "git-cliff-${VERSION}" + git-cliff --version - name: Generate changelog @@ -283,12 +301,14 @@ jobs: tag_pattern = "v[0-9].*" CLIFF_CONFIG - # Generate changelog for this release - git-cliff --config cliff.toml --tag "v${VERSION}" "${PREV_TAG}..HEAD" > changelog.md + # Generate changelog for this release with proper error handling + # Capture exit code to distinguish between failure and empty output + CLIFF_EXIT_CODE=0 + git-cliff --config cliff.toml --tag "v${VERSION}" "${PREV_TAG}..HEAD" > changelog.md 2>&1 || CLIFF_EXIT_CODE=$? - # If git-cliff fails or produces empty output, fall back to simple format - if [ ! -s changelog.md ]; then - echo "git-cliff produced empty output, using fallback" + # Check if git-cliff failed (non-zero exit code) + if [ $CLIFF_EXIT_CODE -ne 0 ]; then + echo "git-cliff failed with exit code $CLIFF_EXIT_CODE, using fallback" { echo "# Release v${VERSION}" echo "" @@ -298,6 +318,20 @@ jobs: echo "" git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (\`%h\`) - @%an" } > changelog.md + # Check if git-cliff succeeded but produced empty output + elif [ ! -s changelog.md ]; then + echo "git-cliff produced empty output (no commits in range), using fallback" + { + echo "# Release v${VERSION}" + echo "" + echo "Released on $(date '+%Y-%m-%d')" + echo "" + echo "### 📦 Changes" + echo "" + git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (\`%h\`) - @%an" + } > changelog.md + else + echo "git-cliff succeeded with valid output" fi # Output changelog for use in release