Final round of github actions
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 23 Oct 2025 15:08:56 +0000 (17:08 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 23 Oct 2025 15:08:56 +0000 (17:08 +0200)
.github/actions/generate-release-notes/action.yml
.github/actions/generate-release-notes/generate_notes.py
.github/release-notes-config.yml
.github/workflows/release.yml

index 6a40f860c474202b8fbe025dd6c16b76d1808320..b7191c2062c5b210bea6df8957abd49ba447257d 100644 (file)
@@ -20,7 +20,7 @@ inputs:
 outputs:
   release-notes:
     description: 'The complete generated release notes including frontend changes'
-    value: ${{ steps.finalize.outputs.release-notes }}
+    value: ${{ steps.generate.outputs.release-notes }}
 runs:
   using: 'composite'
   steps:
@@ -34,7 +34,7 @@ runs:
       run: |
         pip install PyGithub PyYAML
 
-    - name: Generate base release notes
+    - name: Generate complete release notes
       id: generate
       shell: bash
       env:
@@ -48,139 +48,3 @@ runs:
       run: |
         chmod +x ${{ github.action_path }}/generate_notes.py
         python3 ${{ github.action_path }}/generate_notes.py
-
-    - name: Extract and merge frontend changes
-      id: frontend
-      shell: bash
-      env:
-        GITHUB_TOKEN: ${{ inputs.github-token }}
-        VERSION: ${{ inputs.version }}
-        PREVIOUS_TAG: ${{ inputs.previous-tag }}
-        BRANCH: ${{ inputs.branch }}
-        BASE_NOTES: ${{ steps.generate.outputs.release-notes }}
-        SERVER_CONTRIBUTORS: ${{ steps.generate.outputs.contributors }}
-      run: |
-        # Save base notes to file from environment variable
-        echo "$BASE_NOTES" > /tmp/base_notes.md
-
-        # Create temp files for frontend changes
-        FRONTEND_FILE=$(mktemp)
-        CONTRIBUTORS_FILE=$(mktemp)
-
-        # Start with server contributors
-        echo "$SERVER_CONTRIBUTORS" | tr ',' '\n' > "$CONTRIBUTORS_FILE"
-
-        # Find frontend update PRs
-        echo "📦 Looking for frontend update PRs..."
-
-        if [ -z "$PREVIOUS_TAG" ]; then
-          echo "No previous tag, searching recent merged PRs"
-          gh pr list --state merged --limit 100 --json number,title,body --jq '.[] | select(.title | test("^⬆️ Update music-assistant-frontend to [0-9]")) | {number: .number, title: .title, body: .body}' > /tmp/frontend_prs.json
-        else
-          echo "Searching between $PREVIOUS_TAG and $BRANCH"
-          git log "$PREVIOUS_TAG..$BRANCH" --oneline --merges | grep -oP '#\K[0-9]+' > /tmp/pr_numbers.txt || echo ""
-
-          > /tmp/frontend_prs.json
-          while read -r PR_NUM; do
-            if [ -n "$PR_NUM" ]; then
-              PR_DATA=$(gh pr view "$PR_NUM" --json number,title,body 2>/dev/null || echo "")
-              if [ -n "$PR_DATA" ]; then
-                if echo "$PR_DATA" | jq -e '.title | test("^⬆️ Update music-assistant-frontend to [0-9]")' > /dev/null 2>&1; then
-                  echo "$PR_DATA" >> /tmp/frontend_prs.json
-                fi
-              fi
-            fi
-          done < /tmp/pr_numbers.txt
-        fi
-
-        # Process frontend PRs
-        if [ -s /tmp/frontend_prs.json ]; then
-          echo "## 🎨 Frontend Changes" > "$FRONTEND_FILE"
-          echo "" >> "$FRONTEND_FILE"
-
-          while IFS= read -r pr_json; do
-            if [ -n "$pr_json" ]; then
-              BODY=$(echo "$pr_json" | jq -r '.body')
-
-              # Extract bullet points, excluding headers and dependabot lines
-              echo "$BODY" | grep -E '^[[:space:]]*[•-]' | \
-                grep -v '🙇' | \
-                grep -viE '^[[:space:]]*[•-][[:space:]]*Chore\(deps' | \
-                head -20 >> "$FRONTEND_FILE" || true
-
-              # Extract contributors
-              echo "$BODY" | grep -oP '@[a-zA-Z0-9_-]+' >> "$CONTRIBUTORS_FILE" || true
-            fi
-          done < /tmp/frontend_prs.json
-
-          # Check if we found changes
-          if [ $(wc -l < "$FRONTEND_FILE") -gt 3 ]; then
-            echo "✅ Found frontend changes"
-
-            # Merge contributors (deduplicate and format)
-            # Contributors already have @ from grep, so don't add it again
-            MERGED_CONTRIBUTORS=$(sort -u "$CONTRIBUTORS_FILE" | paste -sd ", " -)
-
-            # Find contributors section in base notes
-            CONTRIB_LINE=$(grep -n "## :bow: Thanks to our contributors" /tmp/base_notes.md | head -1 | cut -d: -f1 || echo "")
-
-            if [ -n "$CONTRIB_LINE" ]; then
-              # Split notes at contributors section
-              head -n $((CONTRIB_LINE - 1)) /tmp/base_notes.md > /tmp/before_contrib.md
-
-              # Build final notes
-              {
-                cat /tmp/before_contrib.md
-                echo ""
-                cat "$FRONTEND_FILE"
-                echo ""
-                echo "## :bow: Thanks to our contributors"
-                echo ""
-                echo "Special thanks to the following contributors who helped with this release:"
-                echo ""
-                echo "$MERGED_CONTRIBUTORS"
-              } > /tmp/final_notes.md
-            else
-              # No contributors section, just append
-              {
-                cat /tmp/base_notes.md
-                echo ""
-                cat "$FRONTEND_FILE"
-              } > /tmp/final_notes.md
-            fi
-
-            echo "has_frontend_changes=true" >> $GITHUB_OUTPUT
-          else
-            echo "ℹ️ No frontend changes found"
-            cp /tmp/base_notes.md /tmp/final_notes.md
-            echo "has_frontend_changes=false" >> $GITHUB_OUTPUT
-          fi
-        else
-          echo "ℹ️ No frontend PRs found"
-          cp /tmp/base_notes.md /tmp/final_notes.md
-          echo "has_frontend_changes=false" >> $GITHUB_OUTPUT
-        fi
-
-        # Cleanup
-        rm -f "$FRONTEND_FILE" "$CONTRIBUTORS_FILE" /tmp/frontend_prs.json /tmp/pr_numbers.txt /tmp/before_contrib.md
-
-    - name: Output final notes
-      id: finalize
-      shell: bash
-      run: |
-        # Read the final notes and output them
-        FINAL_NOTES=$(cat /tmp/final_notes.md)
-
-        # Use multiline output format
-        cat >> $GITHUB_OUTPUT << 'EOF'
-        release-notes<<NOTES_EOF
-        EOF
-        cat /tmp/final_notes.md >> $GITHUB_OUTPUT
-        cat >> $GITHUB_OUTPUT << 'EOF'
-        NOTES_EOF
-        EOF
-
-        # Cleanup
-        rm -f /tmp/base_notes.md /tmp/final_notes.md
-
-        echo "✅ Release notes generation complete"
index e83da71785a04236f9ee8229927520e48460e523..d3b875214791f78075e181a208e05c2fc049023e 100755 (executable)
@@ -145,7 +145,54 @@ def format_change_line(pr, config):
     return result.replace("$URL", pr.html_url)
 
 
-def generate_release_notes(config, categories, uncategorized, contributors, previous_tag):
+def extract_frontend_changes(prs):
+    """Extract frontend changes from frontend update PRs.
+
+    Returns tuple of (frontend_changes_list, frontend_contributors_set)
+    """
+    frontend_changes = []
+    frontend_contributors = set()
+
+    # Pattern to match frontend update PRs
+    frontend_pr_pattern = re.compile(r"^⬆️ Update music-assistant-frontend to \d")
+
+    for pr in prs:
+        if not frontend_pr_pattern.match(pr.title):
+            continue
+
+        print(f"Processing frontend PR #{pr.number}: {pr.title}")  # noqa: T201
+
+        if not pr.body:
+            continue
+
+        # Extract bullet points from PR body, excluding headers and dependabot lines
+        for body_line in pr.body.split("\n"):
+            stripped_line = body_line.strip()
+            # Check if it's a bullet point
+            if stripped_line.startswith(("- ", "* ", "• ")):
+                # Skip thank you lines and dependency updates
+                if "🙇" in stripped_line:
+                    continue
+                if re.match(r"^[•\-\*]\s*Chore\(deps", stripped_line, re.IGNORECASE):
+                    continue
+
+                # Add the change
+                frontend_changes.append(stripped_line)
+
+                # Extract contributors mentioned in this line
+                contributors_in_line = re.findall(r"@([a-zA-Z0-9_-]+)", stripped_line)
+                frontend_contributors.update(contributors_in_line)
+
+                # Limit to 20 changes per PR
+                if len(frontend_changes) >= 20:
+                    break
+
+    return frontend_changes, frontend_contributors
+
+
+def generate_release_notes(  # noqa: PLR0915
+    config, categories, uncategorized, contributors, previous_tag, frontend_changes=None
+):
     """Generate the formatted release notes."""
     lines = []
 
@@ -163,9 +210,16 @@ def generate_release_notes(config, categories, uncategorized, contributors, prev
         lines.append(f"_Changes since [{previous_tag}]({repo_url}/releases/tag/{previous_tag})_")
         lines.append("")
 
-    # Add categorized PRs
+    # Add categorized PRs - first pass: categories without "after-other" flag
     category_configs = config.get("categories", [])
+    deferred_categories = []
+
     for cat_config in category_configs:
+        # Defer categories marked with after-other
+        if cat_config.get("after-other", False):
+            deferred_categories.append(cat_config)
+            continue
+
         cat_title = cat_config.get("title", "Other")
         if cat_title not in categories or not categories[cat_title]:
             continue
@@ -190,6 +244,14 @@ def generate_release_notes(config, categories, uncategorized, contributors, prev
 
         lines.append("")
 
+    # Add frontend changes if any (before "Other Changes")
+    if frontend_changes and len(frontend_changes) > 0:
+        lines.append("### 🎨 Frontend Changes")
+        lines.append("")
+        for change in frontend_changes:
+            lines.append(change)
+        lines.append("")
+
     # Add uncategorized PRs if any
     if uncategorized:
         lines.append("### Other Changes")
@@ -198,6 +260,32 @@ def generate_release_notes(config, categories, uncategorized, contributors, prev
             lines.append(format_change_line(pr, config))
         lines.append("")
 
+    # Add deferred categories (after "Other Changes")
+    for cat_config in deferred_categories:
+        cat_title = cat_config.get("title", "Other")
+        if cat_title not in categories or not categories[cat_title]:
+            continue
+
+        prs = categories[cat_title]
+        lines.append(f"### {cat_title}")
+        lines.append("")
+
+        # Check if category should be collapsed
+        collapse_after = cat_config.get("collapse-after")
+        if collapse_after and len(prs) > collapse_after:
+            lines.append("<details>")
+            lines.append(f"<summary>{len(prs)} changes</summary>")
+            lines.append("")
+
+        for pr in prs:
+            lines.append(format_change_line(pr, config))
+
+        if collapse_after and len(prs) > collapse_after:
+            lines.append("")
+            lines.append("</details>")
+
+        lines.append("")
+
     # Add contributors section using template
     if contributors:
         template = config.get("template", "")
@@ -254,13 +342,31 @@ def main():
         categories, uncategorized = categorize_prs(prs, config)
         print(f"Categorized into {len(categories)} categories, {len(uncategorized)} uncategorized")  # noqa: T201
 
-        # Get contributors
+        # Extract frontend changes and contributors
+        frontend_changes_list, frontend_contributors_set = extract_frontend_changes(prs)
+        print(  # noqa: T201
+            f"Found {len(frontend_changes_list)} frontend changes "
+            f"from {len(frontend_contributors_set)} contributors"
+        )
+
+        # Get server contributors
         contributors_list = get_contributors(prs, config)
-        print(f"Found {len(contributors_list)} contributors")  # noqa: T201
+
+        # Merge frontend contributors with server contributors
+        all_contributors = set(contributors_list) | frontend_contributors_set
+        contributors_list = sorted(all_contributors)
+        print(  # noqa: T201
+            f"Total {len(contributors_list)} unique contributors (server + frontend)"
+        )
 
         # Generate formatted notes
         notes = generate_release_notes(
-            config, categories, uncategorized, contributors_list, previous_tag
+            config,
+            categories,
+            uncategorized,
+            contributors_list,
+            previous_tag,
+            frontend_changes_list,
         )
 
     # Output to GitHub Actions
index bd680bb1a247b445b7e1caf40b58e1ff194d1dfa..512757b481d99cb6bbad1f44920cd882f026a585 100644 (file)
@@ -38,6 +38,7 @@ categories:
       - 'bugfix'
 
   - title: '🧰 Maintenance and dependency bumps'
+    after-other: true  # Show this section after "Other Changes"
     collapse-after: 3
     labels:
       - 'ci'
index b34d6306f53e293b7b94834aaea77e51d1c0d69a..0a849b3c1596b5321d5f2cffc1b016354b3814c2 100644 (file)
@@ -205,12 +205,44 @@ jobs:
           channel: ${{ inputs.channel }}
           github-token: ${{ secrets.GITHUB_TOKEN }}
 
+      - name: Format release title
+        id: format_title
+        run: |
+          VERSION="${{ inputs.version }}"
+          CHANNEL="${{ inputs.channel }}"
+
+          if [[ "$CHANNEL" == "nightly" ]]; then
+            # Extract base version and date from X.Y.Z.devYYYYMMDD
+            if [[ "$VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.dev([0-9]+)$ ]]; then
+              BASE="${BASH_REMATCH[1]}"
+              DATE="${BASH_REMATCH[2]}"
+              TITLE="$BASE Nightly $DATE"
+            else
+              TITLE="$VERSION Nightly"
+            fi
+          elif [[ "$CHANNEL" == "beta" ]]; then
+            # Extract base version and beta number from X.Y.Z.bN
+            if [[ "$VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.b([0-9]+)$ ]]; then
+              BASE="${BASH_REMATCH[1]}"
+              BETA_NUM="${BASH_REMATCH[2]}"
+              TITLE="$BASE Beta $BETA_NUM"
+            else
+              TITLE="$VERSION Beta"
+            fi
+          else
+            # Stable release - just use the version
+            TITLE="$VERSION"
+          fi
+
+          echo "title=$TITLE" >> $GITHUB_OUTPUT
+          echo "Release title: $TITLE"
+
       - name: Create GitHub Release
         id: create_release
         uses: softprops/action-gh-release@v2
         with:
           tag_name: ${{ inputs.version }}
-          name: ${{ inputs.version }}
+          name: ${{ steps.format_title.outputs.title }}
           body: ${{ steps.generate_notes.outputs.release-notes }}
           prerelease: ${{ needs.validate-and-build.outputs.is_prerelease }}
           target_commitish: ${{ needs.validate-and-build.outputs.branch }}