diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 725dab4..15f26e7 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,114 @@ 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 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}" - - 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 + # 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}" - - name: Generate changelog with AI + git-cliff --version + + - 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 + # 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 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=$? + + # 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 "" + echo "Released on $(date '+%Y-%m-%d')" + echo "" + echo "### ๐Ÿ“ฆ Changes" + 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 "No OpenAI API key, using standard changelog" - cp changelog_draft.md changelog.md + echo "git-cliff succeeded with valid output" fi # Output changelog for use in release @@ -402,7 +339,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