diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index d07e2c9..778a25d 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -76,6 +76,15 @@ jobs: echo "=== Build output sizes ===" ls -lh out/*.exe out/*.AppImage out/*.deb 2>/dev/null || true + - name: Generate release notes + if: steps.version-check.outputs.SKIP == 'false' + run: | + git fetch --unshallow 2>/dev/null || true + chmod +x scripts/generate-release-notes.sh + scripts/generate-release-notes.sh "${{ steps.version-check.outputs.TAG }}" > /tmp/release-notes.md + echo "--- Generated release notes ---" + cat /tmp/release-notes.md + - name: Create GitHub release and upload assets if: steps.version-check.outputs.SKIP == 'false' env: @@ -100,5 +109,5 @@ jobs: gh release create "${{ steps.version-check.outputs.TAG }}" \ --repo "$GITHUB_REPOSITORY" \ --title "${{ steps.version-check.outputs.TAG }}" \ - --notes "Automated build for ${{ steps.version-check.outputs.TAG }}" \ + --notes-file /tmp/release-notes.md \ "${ASSETS[@]}" diff --git a/scripts/generate-release-notes.sh b/scripts/generate-release-notes.sh new file mode 100644 index 0000000..e577750 --- /dev/null +++ b/scripts/generate-release-notes.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# +# Generate Markdown release notes from conventional commits. +# Usage: ./scripts/generate-release-notes.sh [prev-ref] +# e.g. ./scripts/generate-release-notes.sh v0.7.0 +# e.g. ./scripts/generate-release-notes.sh v0.7.0 abc123f +# +# If prev-ref is not provided, tries git describe to find previous tag. +# If no previous ref is found, includes all commits up to HEAD. +# Uses HEAD as the endpoint (tag may not exist in git yet). + +set -eo pipefail + +TAG="${1:?Usage: generate-release-notes.sh [prev-ref]}" +PREV_REF="${2:-}" + +# If no prev-ref provided, try to find one from git tags +if [ -z "$PREV_REF" ]; then + PREV_REF=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || true) +fi + +if [ -n "$PREV_REF" ]; then + RANGE="${PREV_REF}..HEAD" + COMPARE_TEXT="**Full changelog**: \`${PREV_REF}...${TAG}\`" +else + RANGE="HEAD" + COMPARE_TEXT="**Initial release**" +fi + +# Collect commits into temp files by category +TMPDIR_NOTES=$(mktemp -d) +trap 'rm -rf "$TMPDIR_NOTES"' EXIT + +for prefix in feat fix refactor perf docs test chore other; do + : > "${TMPDIR_NOTES}/${prefix}" +done + +while IFS= read -r line; do + [ -z "$line" ] && continue + MATCHED=false + for prefix in feat fix refactor perf docs test chore; do + if [[ "$line" =~ ^${prefix}(\(.*\))?:\ (.+)$ ]]; then + SCOPE="${BASH_REMATCH[1]}" + MSG="${BASH_REMATCH[2]}" + if [ -n "$SCOPE" ]; then + echo "- **${SCOPE}**: ${MSG}" >> "${TMPDIR_NOTES}/${prefix}" + else + echo "- ${MSG}" >> "${TMPDIR_NOTES}/${prefix}" + fi + MATCHED=true + break + fi + done + if [ "$MATCHED" = false ]; then + echo "- ${line}" >> "${TMPDIR_NOTES}/other" + fi +done < <(git log --format="%s" "$RANGE" 2>/dev/null) + +# Section display names +section_title() { + case "$1" in + feat) echo "Features" ;; + fix) echo "Bug Fixes" ;; + refactor) echo "Refactoring" ;; + perf) echo "Performance" ;; + docs) echo "Documentation" ;; + test) echo "Tests" ;; + chore) echo "Chores" ;; + esac +} + +# Build output +echo "# KCC ${TAG}" +echo "" + +for prefix in feat fix refactor perf docs test chore; do + if [ -s "${TMPDIR_NOTES}/${prefix}" ]; then + echo "## $(section_title "$prefix")" + echo "" + cat "${TMPDIR_NOTES}/${prefix}" + echo "" + fi +done + +if [ -s "${TMPDIR_NOTES}/other" ]; then + echo "## Other Changes" + echo "" + cat "${TMPDIR_NOTES}/other" + echo "" +fi + +echo "---" +echo "" +echo "${COMPARE_TEXT}"