From d50d65e94c3dcdd6dbb8b111f149a463308227ef Mon Sep 17 00:00:00 2001 From: Fran Leustek Date: Fri, 9 Jan 2026 12:21:57 +0100 Subject: [PATCH 1/5] add automated changelog generation --- .github/CONTRIBUTING.md | 28 ++++++++++++++ .github/release.yml | 31 +++++++++++++++ .github/workflows/create-tag-and-exit.yml | 46 +++++++++++++++++++---- CHANGELOG.md | 9 +++++ 4 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 .github/release.yml create mode 100644 CHANGELOG.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f6e9e1ab..b2faa6e0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -13,6 +13,7 @@ By participating in this project, you agree to abide by our [Code of Conduct](.g - [Reporting Issues](#reporting-issues) - [Pull Request Guidelines](#pull-request-guidelines) - [Commit Message Guidelines](#commit-message-guidelines) +- [Changelog and Release Labels](#changelog-and-release-labels) - [Testing](#testing) - [Linting](#linting) - [Documentation](#documentation) @@ -63,6 +64,33 @@ When you're ready to contribute code: --- +## Changelog and Release Labels + +This project uses [GitHub's automatic release notes](https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes) to generate changelogs. + +### PR Labels for Changelog Categories + +When submitting a PR, add one of these labels to categorize your change: + +| Label | Changelog Section | +|-------|-------------------| +| `changelog:added` | Added | +| `changelog:changed` | Changed | +| `changelog:fixed` | Fixed | +| `changelog:removed` | Removed | +| `changelog:security` | Security | + +PRs without labels appear under "Other Changes". + +### Creating a Release + +Releases are created via the `create-tag-and-exit` workflow, which: +1. Creates a GitHub Release with auto-generated notes +2. Updates CHANGELOG.md from all releases +3. Triggers the image build workflow + +--- + ## Testing Before submitting a PR, ensure that your changes pass all tests. diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000..67ef8f00 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,31 @@ +# Configuration for GitHub's automatically generated release notes +# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes + +changelog: + exclude: + labels: + - duplicate + - invalid + - wontfix + authors: + - dependabot + - github-actions + categories: + - title: "Added" + labels: + - "changelog:added" + - title: "Changed" + labels: + - "changelog:changed" + - title: "Fixed" + labels: + - "changelog:fixed" + - title: "Removed" + labels: + - "changelog:removed" + - title: "Security" + labels: + - "changelog:security" + - title: "Other Changes" + labels: + - "*" diff --git a/.github/workflows/create-tag-and-exit.yml b/.github/workflows/create-tag-and-exit.yml index 5c3311c5..08cf784e 100644 --- a/.github/workflows/create-tag-and-exit.yml +++ b/.github/workflows/create-tag-and-exit.yml @@ -19,11 +19,9 @@ jobs: with: fetch-depth: 0 - - name: Create tag (user input or bump patch) + - name: Determine tag version + id: version run: | - git config user.name "github-actions" - git config user.email "github-actions@github.com" - if [[ -n "${{ github.event.inputs.tag }}" ]]; then NEW_TAG="${{ github.event.inputs.tag }}" else @@ -33,8 +31,42 @@ jobs: PATCH=$(echo $LAST_TAG | cut -d. -f3) NEW_TAG="v${MAJOR}.${MINOR}.$((PATCH + 1))" fi - + echo "tag=$NEW_TAG" >> $GITHUB_OUTPUT echo "Creating new tag: $NEW_TAG" - git tag $NEW_TAG - git push origin $NEW_TAG + - name: Create GitHub Release with auto-generated notes + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create ${{ steps.version.outputs.tag }} \ + --generate-notes \ + --title "${{ steps.version.outputs.tag }}" + + - name: Update CHANGELOG.md from releases + env: + GH_TOKEN: ${{ github.token }} + run: | + # Generate changelog header + cat > CHANGELOG.md << 'HEADER' + # Changelog + + All notable changes to this project will be documented in this file. + The format is based on [Keep a Changelog](https://keepachangelog.com/). + + HEADER + + # Append all releases (sorted by date, newest first) + gh release list --limit 100 | while read -r tag title date status; do + echo "## [$tag] - $(date -d "$date" +%Y-%m-%d 2>/dev/null || echo "$date")" + echo "" + gh release view "$tag" --json body -q '.body' | sed 's/^## /### /' + echo "" + done >> CHANGELOG.md + + - name: Commit and push CHANGELOG.md + run: | + git config user.name "github-actions" + git config user.email "github-actions@github.com" + git add CHANGELOG.md + git diff --staged --quiet || git commit -m "chore: update changelog for ${{ steps.version.outputs.tag }}" + git push origin main diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..375f6f03 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +All notable changes to this project will be documented in this file. +The format is based on [Keep a Changelog](https://keepachangelog.com/). + +For releases prior to automated changelog generation, please see the +[GitHub Releases](https://github.com/devzero-inc/zxporter/releases) page. + + From a2916350323ad450d8bc0c6960816c37ed45f26d Mon Sep 17 00:00:00 2001 From: Fran Leustek Date: Fri, 9 Jan 2026 12:50:35 +0100 Subject: [PATCH 2/5] update create-tag-and-exit --- .github/workflows/create-tag-and-exit.yml | 43 ++++++++++++++++------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/.github/workflows/create-tag-and-exit.yml b/.github/workflows/create-tag-and-exit.yml index 08cf784e..1a8329b9 100644 --- a/.github/workflows/create-tag-and-exit.yml +++ b/.github/workflows/create-tag-and-exit.yml @@ -46,18 +46,21 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - # Generate changelog header - cat > CHANGELOG.md << 'HEADER' - # Changelog + # Generate changelog header (no indentation to avoid leading spaces) + printf '%s\n' \ + "# Changelog" \ + "" \ + "All notable changes to this project will be documented in this file." \ + "The format is based on [Keep a Changelog](https://keepachangelog.com/)." \ + "" \ + "For releases prior to automated changelog generation, please see the" \ + "[GitHub Releases](https://github.com/devzero-inc/zxporter/releases) page." \ + "" > CHANGELOG.md - All notable changes to this project will be documented in this file. - The format is based on [Keep a Changelog](https://keepachangelog.com/). - - HEADER - - # Append all releases (sorted by date, newest first) - gh release list --limit 100 | while read -r tag title date status; do - echo "## [$tag] - $(date -d "$date" +%Y-%m-%d 2>/dev/null || echo "$date")" + # Append all releases using JSON for reliable parsing + gh release list --limit 100 --json tagName,publishedAt | jq -r '.[] | "\(.tagName) \(.publishedAt)"' | while read -r tag published_at; do + formatted_date=$(date -d "$published_at" +%Y-%m-%d 2>/dev/null || echo "$published_at") + echo "## [$tag] - $formatted_date" echo "" gh release view "$tag" --json body -q '.body' | sed 's/^## /### /' echo "" @@ -68,5 +71,19 @@ jobs: git config user.name "github-actions" git config user.email "github-actions@github.com" git add CHANGELOG.md - git diff --staged --quiet || git commit -m "chore: update changelog for ${{ steps.version.outputs.tag }}" - git push origin main + if git diff --staged --quiet; then + echo "No changelog changes to commit" + exit 0 + fi + git commit -m "chore: update changelog for ${{ steps.version.outputs.tag }}" + + # Pull with rebase and push, with retry on failure + max_retries=3 + for i in $(seq 1 $max_retries); do + git fetch origin main + git rebase origin/main && git push origin main && exit 0 + echo "Push attempt $i failed, retrying..." + sleep 2 + done + echo "Failed to push after $max_retries attempts" + exit 1 From 4b169a70a42aa246c71978116421af6ef6417253 Mon Sep 17 00:00:00 2001 From: Fran Leustek Date: Fri, 9 Jan 2026 18:04:47 +0100 Subject: [PATCH 3/5] update create-tag-and-exit --- .github/workflows/create-tag-and-exit.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/create-tag-and-exit.yml b/.github/workflows/create-tag-and-exit.yml index 1a8329b9..093bdcb4 100644 --- a/.github/workflows/create-tag-and-exit.yml +++ b/.github/workflows/create-tag-and-exit.yml @@ -57,12 +57,14 @@ jobs: "[GitHub Releases](https://github.com/devzero-inc/zxporter/releases) page." \ "" > CHANGELOG.md - # Append all releases using JSON for reliable parsing - gh release list --limit 100 --json tagName,publishedAt | jq -r '.[] | "\(.tagName) \(.publishedAt)"' | while read -r tag published_at; do + # Append all releases using JSON for reliable parsing (no --limit to paginate all) + gh release list --json tagName,publishedAt | jq -r '.[] | "\(.tagName) \(.publishedAt)"' | while read -r tag published_at; do formatted_date=$(date -d "$published_at" +%Y-%m-%d 2>/dev/null || echo "$published_at") echo "## [$tag] - $formatted_date" echo "" - gh release view "$tag" --json body -q '.body' | sed 's/^## /### /' + # Transform top-level headings (## ) to subheadings (### ) for changelog nesting. + # Uses negative lookahead pattern to avoid double-transforming already-nested headings. + gh release view "$tag" --json body -q '.body' | sed 's/^##\([^#]\)/###\1/' echo "" done >> CHANGELOG.md From fd4a083ae6d3d82b25fc4697d1a3e8ed6a605d52 Mon Sep 17 00:00:00 2001 From: Fran Leustek Date: Fri, 9 Jan 2026 18:14:21 +0100 Subject: [PATCH 4/5] update create-tag-and-exit --- .github/workflows/create-tag-and-exit.yml | 27 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/workflows/create-tag-and-exit.yml b/.github/workflows/create-tag-and-exit.yml index 093bdcb4..43fc4e21 100644 --- a/.github/workflows/create-tag-and-exit.yml +++ b/.github/workflows/create-tag-and-exit.yml @@ -38,9 +38,13 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - gh release create ${{ steps.version.outputs.tag }} \ - --generate-notes \ - --title "${{ steps.version.outputs.tag }}" + if gh release view "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then + echo "Release ${{ steps.version.outputs.tag }} already exists; skipping creation." + else + gh release create ${{ steps.version.outputs.tag }} \ + --generate-notes \ + --title "${{ steps.version.outputs.tag }}" + fi - name: Update CHANGELOG.md from releases env: @@ -57,8 +61,9 @@ jobs: "[GitHub Releases](https://github.com/devzero-inc/zxporter/releases) page." \ "" > CHANGELOG.md - # Append all releases using JSON for reliable parsing (no --limit to paginate all) - gh release list --json tagName,publishedAt | jq -r '.[] | "\(.tagName) \(.publishedAt)"' | while read -r tag published_at; do + # Append all releases using JSON for reliable parsing (paginate to include all) + gh api --paginate repos/${{ github.repository }}/releases?per_page=100 \ + --jq 'map({tagName: .tag_name, publishedAt: .published_at})' | jq -r '.[] | "\(.tagName) \(.publishedAt)"' | while read -r tag published_at; do formatted_date=$(date -d "$published_at" +%Y-%m-%d 2>/dev/null || echo "$published_at") echo "## [$tag] - $formatted_date" echo "" @@ -83,7 +88,17 @@ jobs: max_retries=3 for i in $(seq 1 $max_retries); do git fetch origin main - git rebase origin/main && git push origin main && exit 0 + if ! git rebase origin/main; then + if git diff --name-only --diff-filter=U | grep -q .; then + echo "Rebase failed due to conflicts; aborting." + git rebase --abort + exit 1 + fi + echo "Rebase failed; aborting." + git rebase --abort || true + exit 1 + fi + git push origin main && exit 0 echo "Push attempt $i failed, retrying..." sleep 2 done From 9eb8ef5f797205e62bc733a07f8de08dfb0da954 Mon Sep 17 00:00:00 2001 From: Fran Leustek Date: Fri, 9 Jan 2026 18:22:03 +0100 Subject: [PATCH 5/5] update create-tag-and-exit --- .github/workflows/create-tag-and-exit.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create-tag-and-exit.yml b/.github/workflows/create-tag-and-exit.yml index 43fc4e21..a00fde1c 100644 --- a/.github/workflows/create-tag-and-exit.yml +++ b/.github/workflows/create-tag-and-exit.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: tag: - description: 'Optional: Tag to push (e.g., v1.2.3). If omitted, patch is bumped.' + description: "Optional: Tag to push (e.g., v1.2.3). If omitted, patch is bumped." required: false permissions: @@ -58,7 +58,7 @@ jobs: "The format is based on [Keep a Changelog](https://keepachangelog.com/)." \ "" \ "For releases prior to automated changelog generation, please see the" \ - "[GitHub Releases](https://github.com/devzero-inc/zxporter/releases) page." \ + "[GitHub Releases](https://github.com/${{ github.repository }}/releases) page." \ "" > CHANGELOG.md # Append all releases using JSON for reliable parsing (paginate to include all)