diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index d34af2d49..6ba88a153 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -91,94 +91,21 @@ jobs: run: | echo "release_filename=edgetx-firmware-nightly-${GITHUB_SHA::8}.zip" >> $GITHUB_ENV echo "build_date=$(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_ENV + echo "old_nightly_sha=$(git rev-parse nightly)" >> $GITHUB_ENV - name: Zip release file uses: montudor/action-zip@v1 with: args: zip -qq -j -r ${{ env.release_filename }} ./edgetx-firmware-nightly - - name: Build Changelog - id: build_changelog - uses: mikepenz/release-changelog-builder-action@v6 + - name: Generate changelog + id: changelog + uses: orhun/git-cliff-action@v4 with: - mode: "HYBRID" - configurationJson: | - { - "categories": [ - { - "title": "## ๐Ÿ–ฅ๏ธ Black & White Radio Changes", - "labels": ["scope:bw"] - }, - { - "title": "## ๐ŸŽจ Color Radio Changes", - "labels": ["scope:color"] - }, - { - "title": "## ๐ŸŽฎ Firmware (All Radios Generally)", - "labels": ["scope:firmware", "scope:fw"] - }, - { - "title": "## ๐Ÿ’ป Companion Software Changes", - "labels": ["scope:cpn", "companion"] - }, - { - "title": "## ๐Ÿš€ Features", - "labels": ["feat", "enhancement โœจ"] - }, - { - "title": "## ๐Ÿ› Fixes", - "labels": ["fix", "bugfix", "bug ๐Ÿชฒ", "bug/regression โ†ฉ๏ธ"] - }, - { - "title": "## ๐Ÿงน Chores", - "labels": ["chore", "house keeping ๐Ÿงน"] - }, - { - "title": "## ๐Ÿ”ง CI/CD", - "labels": ["ci", "ci/cd ๐Ÿ”ง"] - }, - { - "title": "## ๐Ÿงช Tests", - "labels": ["scope:test", "test", "unit tests ๐Ÿงช"] - }, - { - "title": "## ๐Ÿ“ Documentation", - "labels": ["scope:docs", "docs", "documentation ๐Ÿ“"] - }, - { - "title": "## ๐Ÿ”จ Refactoring", - "labels": ["refactor"] - }, - { - "title": "## ๐ŸŽจ Style", - "labels": ["style"] - }, - { - "title": "## ๐Ÿ“ฆ Other", - "labels": [] - } - ], - "template": "#{{CHANGELOG}}", - "pr_template": "- #{{TITLE}} (##{{NUMBER}})", - "commit_template": "- #{{TITLE}} (#{{MERGE_SHA}})", - "empty_template": "- No changes", - "label_extractor": [ - { - "pattern": "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([^)]+\\))?(!)?:", - "on_property": "title", - "target": "$1" - }, - { - "pattern": "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)\\(([^)]+)\\)", - "on_property": "title", - "target": "scope:$2" - } - ] - } - toTag: HEAD - fromTag: nightly + config: cliff.toml + args: --verbose --strip all nightly..HEAD env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OUTPUT: CHANGELOG.md - name: Update nightly tag run: | @@ -213,6 +140,9 @@ jobs: ## Changes since last nightly build - ${{ steps.build_changelog.outputs.changelog }} + ${{ steps.changelog.outputs.content }} + + --- + **Full Diff:** https://github.com/${{ github.repository }}/compare/${{ env.old_nightly_sha }}...${{ github.sha }} files: | ${{ env.release_filename }} diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 522d58aa4..5dfd5343b 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -1,20 +1,6 @@ name: Create Release Draft on: - workflow_run: - workflows: - - "Run tests and package firmware" - - "MacOSX Companion" - - "Windows Companion 64-bit" - - "Linux Companion" - types: - - completed - branches: - - 'main' - - '[0-9]+.[0-9]+' - tags: - - 'v*' - workflow_dispatch: inputs: tag: @@ -26,145 +12,34 @@ permissions: contents: write jobs: - check-tag: - name: Check if triggered by tag - runs-on: ubuntu-latest - outputs: - is_tag: ${{ steps.check.outputs.is_tag }} - tag_name: ${{ steps.check.outputs.tag_name }} - steps: - - name: Check out the repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check if this is a tag push - id: check - run: | - echo "=== Release Draft Trigger Check ===" - echo "Event: ${{ github.event_name }}" - - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "โœ“ Manually triggered with tag: ${{ inputs.tag }}" - echo "is_tag=true" >> $GITHUB_OUTPUT - echo "tag_name=${{ inputs.tag }}" >> $GITHUB_OUTPUT - else - # Get the ref that triggered the workflow_run - REF="${{ github.event.workflow_run.head_branch }}" - echo "Triggered by workflow_run for ref: $REF" - - if git show-ref --verify --quiet "refs/tags/$REF"; then - echo "โœ“ This is a tag push: $REF" - echo "is_tag=true" >> $GITHUB_OUTPUT - echo "tag_name=$REF" >> $GITHUB_OUTPUT - else - echo "โ„น This is NOT a tag push (branch: $REF)" - echo "is_tag=false" >> $GITHUB_OUTPUT - fi - fi - - - name: Summary - run: | - if [ "${{ steps.check.outputs.is_tag }}" = "true" ]; then - echo "### โœ… Release Draft Will Be Created" >> $GITHUB_STEP_SUMMARY - echo "Tag: \`${{ steps.check.outputs.tag_name }}\`" >> $GITHUB_STEP_SUMMARY - else - echo "### โ„น๏ธ Release Draft Skipped" >> $GITHUB_STEP_SUMMARY - echo "This workflow run was triggered by a branch push, not a version tag." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Release drafts are only created when:**" >> $GITHUB_STEP_SUMMARY - echo "- A version tag is pushed (e.g., \`v2.10.0\`, \`v3.0.0-rc1\`)" >> $GITHUB_STEP_SUMMARY - echo "- The workflow is manually triggered with a tag" >> $GITHUB_STEP_SUMMARY - fi - - verify-builds: - name: Verify all builds succeeded - needs: check-tag - runs-on: ubuntu-latest - if: needs.check-tag.outputs.is_tag == 'true' - # Set job timeout to 6 hours (GitHub max) to be safe - timeout-minutes: 360 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Wait for companion and firmware builds - run: | - SHA=${{ github.event.workflow_run.head_sha || github.sha }} - mapfile -t WORKFLOWS < <(echo '${{ toJson(github.event.workflow_run.workflow_name || '["Run tests and package firmware", "MacOSX Companion", "Windows Companion 64-bit", "Linux Companion"]') }}' | jq -r '.[]') - - echo "Waiting for all workflows to complete for SHA: $SHA" - - # 300 retries * 60 seconds = 5 hours - MAX_RETRIES=300 - RETRY_COUNT=0 - - while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do - ALL_SUCCESS=true - for wf in "${WORKFLOWS[@]}"; do - if [ "$wf" == "${{ github.workflow }}" ]; then continue; fi - - RESULT=$(gh run list --commit $SHA --workflow "$wf" --json conclusion,status --jq '.[0] | "\(.status):\(.conclusion)"' || echo "unknown:pending") - STATUS=$(echo $RESULT | cut -d: -f1) - CONCLUSION=$(echo $RESULT | cut -d: -f2) - - # Exit immediately if any sibling failed - if [[ "$CONCLUSION" == "failure" || "$CONCLUSION" == "cancelled" ]]; then - echo "::error::Workflow $wf resulted in $CONCLUSION. Aborting." - exit 1 - fi - - if [ "$CONCLUSION" != "success" ]; then - # Only print status every 5 minutes to keep logs clean - if (( RETRY_COUNT % 5 == 0 )); then - echo "Still waiting for $wf (Current status: $STATUS)..." - fi - ALL_SUCCESS=false - break - fi - done - - if [ "$ALL_SUCCESS" = true ]; then - echo "All required workflows finished successfully!" - exit 0 - fi - - RETRY_COUNT=$((RETRY_COUNT+1)) - sleep 60 - done - - echo "::error::Timed out after 5 hours waiting for builds." - exit 1 - release: name: Create draft release - needs: [check-tag, verify-builds] runs-on: ubuntu-latest - if: needs.check-tag.outputs.is_tag == 'true' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Check out the repo uses: actions/checkout@v4 with: - ref: ${{ needs.check-tag.outputs.tag_name }} + ref: ${{ inputs.tag }} fetch-depth: 0 - name: Clean up any previous drafts for this version run: | - # Find and delete any existing draft releases for this tag in case of re-runs - DRAFTS=$(gh release list --exclude-pre-releases=false | grep "Draft" | grep "${{ needs.check-tag.outputs.tag_name }}" | cut -f1 || true) - for draft in $DRAFTS; do - echo "Deleting old draft: $draft" - gh release delete "$draft" --yes - done + TAG="${{ inputs.tag }}" + if gh release view "$TAG" --json isDraft --jq '.isDraft' 2>/dev/null | grep -q true; then + echo "Deleting existing draft release for tag: $TAG" + gh release delete "$TAG" --yes + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Extract version info id: version continue-on-error: false run: | - VERSION="${{ needs.check-tag.outputs.tag_name }}" + VERSION="${{ inputs.tag }}" echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "release_name=EdgeTX $VERSION" >> $GITHUB_OUTPUT # Extract codename from CMakeLists.txt if [ -f "CMakeLists.txt" ]; then @@ -186,129 +61,90 @@ jobs: echo "CMakeLists.txt not found" fi - - name: Build Changelog - id: build_changelog - uses: mikepenz/release-changelog-builder-action@v6 - with: - mode: "HYBRID" - configurationJson: | - { - "categories": [ - { - "title": "## ๐Ÿ–ฅ๏ธ Black & White Radio Changes", - "labels": ["scope:bw"] - }, - { - "title": "## ๐ŸŽจ Color Radio Changes", - "labels": ["scope:color"] - }, - { - "title": "## ๐ŸŽฎ Firmware (All Radios Generally)", - "labels": ["scope:firmware", "scope:fw"] - }, - { - "title": "## ๐Ÿ’ป Companion Software Changes", - "labels": ["scope:cpn", "companion"] - }, - { - "title": "## ๐Ÿš€ Features", - "labels": ["feat", "enhancement โœจ"] - }, - { - "title": "## ๐Ÿ› Fixes", - "labels": ["fix", "bugfix", "bug ๐Ÿชฒ", "bug/regression โ†ฉ๏ธ"] - }, - { - "title": "## ๐Ÿงน Chores", - "labels": ["chore", "house keeping ๐Ÿงน"] - }, - { - "title": "## ๐Ÿ”ง CI/CD", - "labels": ["ci", "ci/cd ๐Ÿ”ง"] - }, - { - "title": "## ๐Ÿงช Tests", - "labels": ["scope:test", "test", "unit tests ๐Ÿงช"] - }, - { - "title": "## ๐Ÿ“ Documentation", - "labels": ["scope:docs", "docs", "documentation ๐Ÿ“"] - }, - { - "title": "## ๐Ÿ”จ Refactoring", - "labels": ["refactor"] - }, - { - "title": "## ๐ŸŽจ Style", - "labels": ["style"] - }, - { - "title": "## ๐Ÿ“ฆ Other", - "labels": [] - } - ], - "template": "#{{CHANGELOG}}\n\n## Thank you to the following contributors who made this release possible!\n#{{CONTRIBUTORS}}", - "pr_template": "- #{{TITLE}} (##{{NUMBER}})", - "commit_template": "- #{{TITLE}} (#{{MERGE_SHA}})", - "empty_template": "- No changes", - "label_extractor": [ - { - "pattern": "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([^)]+\\))?(!)?:", - "on_property": "title", - "target": "$1" - }, - { - "pattern": "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)\\(([^)]+)\\)", - "on_property": "title", - "target": "scope:$2" - } - ], - "max_pull_requests": 1000, - "max_back_track_time_days": 365 - } - includeOpen: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Generate Changelog and Contributors List + id: changelog + run: | + # Find the previous tag relative to the input tag + PREV_TAG=$(git describe --tags --abbrev=0 "${{ inputs.tag }}^" 2>/dev/null || echo "") + + if [ -z "$PREV_TAG" ]; then + echo "No previous tag found. Generating full history for ${{ inputs.tag }}" + RANGE="${{ inputs.tag }}" + CONTRIB_RANGE="${{ inputs.tag }}" + else + echo "Found previous tag: $PREV_TAG" + RANGE="$PREV_TAG..${{ inputs.tag }}" + CONTRIB_RANGE="$RANGE" + fi + + # Generate the changelog using the explicit range + git-cliff $RANGE --tag "${{ inputs.tag }}" --strip all --output CHANGELOG.md + + # Get unique contributors from the same commit range + CONTRIBUTORS=$(git log --format='%aN' $CONTRIB_RANGE 2>/dev/null | sort -u | sed 's/^/- /' || echo "") + + # Combine + CHANGELOG_CONTENT=$(cat CHANGELOG.md) + + { + echo "changelog<> $GITHUB_OUTPUT - name: Download all release artifacts run: | - TAG="${{ needs.check-tag.outputs.tag_name }}" + TAG="${{ inputs.tag }}" + COMMIT_SHA=$(git rev-parse "${TAG}^{commit}") + echo "Tag $TAG points to commit: $COMMIT_SHA" - COMMIT_SHA=$(git rev-parse $TAG) - echo "Looking for artifacts from commit: $COMMIT_SHA" + # Get the latest successful run ID for each unique workflow + WORKFLOW_RUNS=$(gh run list --commit "$COMMIT_SHA" --status success --json workflowName,databaseId,createdAt --jq 'group_by(.workflowName) | map(sort_by(.createdAt) | last) | .[].databaseId') - # Find all successful workflow runs for this commit - WORKFLOW_RUNS=$(gh run list --commit $COMMIT_SHA --json databaseId,status,conclusion --jq '.[] | select(.conclusion=="success") | .databaseId') - - if [ -z "$WORKFLOW_RUNS" ]; then - echo "No successful workflow runs found for commit $COMMIT_SHA" - exit 1 - fi - - # Download artifacts from each successful run mkdir -p release-assets + for run_id in $WORKFLOW_RUNS; do - echo "Downloading artifacts from run $run_id..." - gh run download $run_id --dir release-assets --pattern "edgetx-*-${TAG}" || echo "No matching artifacts in run $run_id" + echo "Processing Run ID: $run_id" + gh run download "$run_id" --dir release-assets 2>/dev/null || true + done + + cd release-assets + + # Clean up: Delete any folders ending in "-logs" BEFORE we start zipping + echo "Cleaning up log artifacts..." + rm -rf *-logs/ + + # Package remaining folders into zips + for dir in */; do + # Ensure we are actually looking at a directory + [ -d "$dir" ] || continue + dir_name="${dir%/}" + + echo "Processing folder: $dir_name" + + # Check for existing zip inside (e.g. if firmware was already zipped) + INNER_ZIP=$(find "$dir" -maxdepth 1 -name "*.zip" | head -n 1) + + if [ -n "$INNER_ZIP" ]; then + echo " Found existing zip, pulling it out..." + mv "$INNER_ZIP" "${dir_name}.zip" + else + echo " No zip found, creating ${dir_name}.zip..." + (cd "$dir" && zip -r "../${dir_name}.zip" .) + fi + + # Remove the folder after zipping/moving + rm -rf "$dir" done - # Check if we got any artifacts - if [ ! "$(ls -A release-assets 2>/dev/null)" ]; then - echo "::error::No artifacts found matching pattern edgetx-*-${TAG}" - exit 1 - fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Package release assets - run: | - cd release-assets - for dir in */; do - dir_name="${dir%/}" - echo "Packaging $dir_name..." - (cd "$dir_name" && zip -r "../${dir_name}.zip" .) - done - - name: List release assets run: | echo "Release assets to upload:" @@ -317,9 +153,9 @@ jobs: - name: Create draft release uses: softprops/action-gh-release@v2 with: - tag_name: ${{ needs.check-tag.outputs.tag_name }} + tag_name: ${{ inputs.tag }} draft: true - prerelease: ${{ contains(needs.check-tag.outputs.tag_name, '-rc') || contains(needs.check-tag.outputs.tag_name, '-beta') || contains(needs.check-tag.outputs.tag_name, '-alpha') }} + prerelease: ${{ contains(inputs.tag, '-rc') || contains(inputs.tag, '-beta') || contains(inputs.tag, '-alpha') }} name: EdgeTX "${{ steps.version.outputs.codename }}" ${{ steps.version.outputs.version }} body: | We are pleased to offer EdgeTX "${{ steps.version.outputs.codename }}" ${{ steps.version.outputs.version }}. @@ -364,6 +200,5 @@ jobs: ## Changes ${{ steps.build_changelog.outputs.changelog }} - files: | - release-assets/*.zip + files: release-assets/*.zip fail_on_unmatched_files: true diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 000000000..d176df485 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,57 @@ +# Configuration for git-cliff changelog generation +# https://git-cliff.org/docs/configuration + +[changelog] +header = "" +body = """ +{% for group, commits in commits | group_by(attribute="group") %} +### {{ group }} +{% for commit in commits -%} +- {{ commit.message | split(pat="\\n") | first | trim }} +{% endfor %} +{% endfor -%} +""" +trim = true + +[git] +conventional_commits = true +filter_unconventional = false + +# Only look for nightly, release and rc tags +tag_pattern = "^(nightly|v[0-9]+\\.[0-9]+\\.[0-9]+(-rc[0-9]+)?)$" + +# Ensures it looks at the branch structure +topo_order = true + +# Repect the nearest tag on current branch +use_branch_tags = true + +# Ensure it sorts from the tag forward +sort_commits = "oldest" + +# Commit parsers - order matters! More specific patterns first +commit_parsers = [ + # Scope-based categorization (highest priority) + { message = "^feat\\(bw\\)", group = "๐Ÿ–ฅ๏ธ Black & White Radio Changes" }, + { message = "^fix\\(bw\\)", group = "๐Ÿ–ฅ๏ธ Black & White Radio Changes" }, + { message = "^feat\\(color\\)", group = "๐ŸŽจ Color Radio Changes" }, + { message = "^fix\\(color\\)", group = "๐ŸŽจ Color Radio Changes" }, + { message = "^feat\\((firmware|fw)\\)", group = "๐ŸŽฎ Firmware (All Radios Generally)" }, + { message = "^fix\\((firmware|fw)\\)", group = "๐ŸŽฎ Firmware (All Radios Generally)" }, + { message = "^feat\\(cpn\\)", group = "๐Ÿ’ป Companion Software Changes" }, + { message = "^fix\\(cpn\\)", group = "๐Ÿ’ป Companion Software Changes" }, + + # Type-based categorization (fallback) + { message = "^feat", group = "๐Ÿš€ Features" }, + { message = "^fix", group = "๐Ÿ› Fixes" }, + { message = "^docs?", group = "๐Ÿ“ Documentation" }, + { message = "^perf", group = "โšก Performance" }, + { message = "^refactor", group = "๐Ÿ”จ Refactoring" }, + { message = "^style", group = "๐ŸŽจ Style" }, + { message = "^test", group = "๐Ÿงช Tests" }, + { message = "^chore", group = "๐Ÿงน Chores" }, + { message = "^ci", group = "๐Ÿ”ง CI/CD" }, + + # Everything else + { body = ".*", group = "๐Ÿ“ฆ Other" }, +]