From 7e71bfdaea5d4c04b192ca2ec01377697117c740 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 23 Oct 2025 17:08:56 +0200 Subject: [PATCH] Final round of github actions --- .../actions/generate-release-notes/action.yml | 140 +----------------- .../generate-release-notes/generate_notes.py | 116 ++++++++++++++- .github/release-notes-config.yml | 1 + .github/workflows/release.yml | 34 ++++- 4 files changed, 147 insertions(+), 144 deletions(-) diff --git a/.github/actions/generate-release-notes/action.yml b/.github/actions/generate-release-notes/action.yml index 6a40f860..b7191c20 100644 --- a/.github/actions/generate-release-notes/action.yml +++ b/.github/actions/generate-release-notes/action.yml @@ -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<> $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" diff --git a/.github/actions/generate-release-notes/generate_notes.py b/.github/actions/generate-release-notes/generate_notes.py index e83da717..d3b87521 100755 --- a/.github/actions/generate-release-notes/generate_notes.py +++ b/.github/actions/generate-release-notes/generate_notes.py @@ -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("
") + lines.append(f"{len(prs)} changes") + 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("
") + + 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 diff --git a/.github/release-notes-config.yml b/.github/release-notes-config.yml index bd680bb1..512757b4 100644 --- a/.github/release-notes-config.yml +++ b/.github/release-notes-config.yml @@ -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' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b34d6306..0a849b3c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 }} -- 2.34.1