From 4e97799f3e080985dd0839253f65d78e7942104d Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Thu, 23 Oct 2025 03:13:20 +0200 Subject: [PATCH] Add new release workflow --- .github/release-drafter.yml | 6 +- .github/workflows/RELEASE_WORKFLOW_GUIDE.md | 207 ++++++++ .github/workflows/release.yml | 534 ++++++++++++++++---- 3 files changed, 659 insertions(+), 88 deletions(-) create mode 100644 .github/workflows/RELEASE_WORKFLOW_GUIDE.md diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 05b70c02..9fd1426e 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,7 +1,7 @@ change-template: '- $TITLE (by @$AUTHOR in #$NUMBER)' -prerelease: true -prerelease-identifier: 'b' -include-pre-releases: true + +# Note: prerelease flag is set dynamically by the workflow based on channel +# This config is used for all release channels (stable, beta, nightly) # Exclude bots from contributors list exclude-contributors: diff --git a/.github/workflows/RELEASE_WORKFLOW_GUIDE.md b/.github/workflows/RELEASE_WORKFLOW_GUIDE.md new file mode 100644 index 00000000..1c764d2d --- /dev/null +++ b/.github/workflows/RELEASE_WORKFLOW_GUIDE.md @@ -0,0 +1,207 @@ +# Release Workflow Guide + +This document explains how to use the new release workflow for Music Assistant. + +## Overview + +The release workflow has been completely reworked to **create releases** rather than being triggered by them. This gives you more control over the release process and ensures consistency across different release channels. + +## How to Create a Release + +1. Go to the **Actions** tab in GitHub +2. Select the **"Create Release"** workflow +3. Click **"Run workflow"** +4. Fill in the required inputs: + - **Version number**: Enter the version in the appropriate format (see below) + - **Release channel**: Select from `stable`, `beta`, or `nightly` +5. Click **"Run workflow"** to start the release process + +## Version Format Requirements + +The workflow validates version numbers based on the selected channel: + +### Stable Channel +- **Format**: `X.Y.Z` (e.g., `2.1.0`, `2.1.1`) +- **Examples**: + - ✅ `2.1.0` + - ✅ `1.5.3` + - ❌ `2.1.0.b1` (beta suffix not allowed for stable) + +### Beta Channel +- **Format**: `X.Y.Z.bN` (e.g., `2.1.0.b1`, `2.1.0.b2`) +- **Examples**: + - ✅ `2.1.0.b1` + - ✅ `2.2.0.b3` + - ❌ `2.1.0` (must have beta suffix) + +### Nightly Channel +- **Format**: `X.Y.Z.devN` (e.g., `2.1.0.dev1`, `2.1.0.dev20251023`) +- **Examples**: + - ✅ `2.1.0.dev1` + - ✅ `2.1.0.dev20251023` + - ❌ `2.1.0` (must have dev suffix) + +## What the Workflow Does + +### 1. Validation & Build (`validate-and-build` job) +- ✅ Validates the version number format matches the selected channel +- ✅ Updates the version in `pyproject.toml` +- ✅ Builds the Python package (wheel and source distribution) +- ✅ Uploads artifacts for use in subsequent jobs + +### 2. Create Release (`create-release` job) +- ✅ Checks out the correct branch (stable for stable releases, dev for beta/nightly) +- ✅ Determines the previous release tag for the same channel +- ✅ Uses Release Drafter to generate release notes from PRs/commits on that branch +- ✅ **Extracts and appends frontend changes** from music-assistant-frontend update PRs +- ✅ **Merges and deduplicates contributors** from both server and frontend PRs +- ✅ Creates a GitHub release (marked as prerelease for beta/nightly) +- ✅ Attaches the built Python packages to the release + +#### Branch Strategy +- **Stable releases** (`X.Y.Z`): Built from the `stable` branch +- **Beta releases** (`X.Y.Z.bN`): Built from the `dev` branch +- **Nightly releases** (`X.Y.Z.devN`): Built from the `dev` branch + +This ensures stable releases only include changes that have been merged to stable, while beta and nightly releases include the latest development changes. + +#### Frontend Changes Integration +The workflow automatically finds all "⬆️ Update music-assistant-frontend to X.X.X" PRs that were merged since the last release and extracts their release notes. These are appended to the server release notes under a "🎨 Frontend Changes" section as a unified list of all frontend changes across all versions. This ensures users can see both server and frontend changes in one place. + +#### Contributor Merging +The workflow also extracts all `@username` mentions from both the server release notes (generated by Release Drafter) and the frontend PR descriptions, then merges and deduplicates them. The final "Thanks to our contributors" section includes everyone who contributed to both the server and frontend in this release cycle. + +**Example of final release notes structure:** +```markdown +## ⚠ Breaking Changes +- Server breaking change (by @serverdev in #123) + +## 🚀 Features and enhancements +- Server feature 1 (by @contributor1 in #124) +- Server feature 2 (by @contributor2 in #125) + +## 🐛 Bugfixes +- Server bugfix (by @contributor1 in #126) + +## 🎨 Frontend Changes +• Add the provider type on items on search (by @frontenddev in #1174) +• Fix player menu position (by @contributor3 in #1175) +• Update translations (by @translator in #1170) +• Fix dark mode styling issue (by @frontenddev in #1171) + +## :bow: Thanks to our contributors + +Special thanks to the following contributors who helped with this release: + +@contributor1, @contributor2, @contributor3, @frontenddev, @serverdev, @translator +``` + +### 3. PyPI Publish (`pypi-publish` job) +- ✅ **Only runs for stable releases** +- ✅ Publishes the package to PyPI using the configured token + +### 4. Docker Image Build (`build-and-push-container-image` job) +- ✅ Builds multi-platform Docker images (amd64 + arm64) +- ✅ Uses the correct base image version for the channel +- ✅ Tags images appropriately based on the channel: + +#### Stable Release Tags +- `ghcr.io/music-assistant/server:X.Y.Z` +- `ghcr.io/music-assistant/server:X.Y` +- `ghcr.io/music-assistant/server:X` +- `ghcr.io/music-assistant/server:stable` +- `ghcr.io/music-assistant/server:latest` + +#### Beta Release Tags +- `ghcr.io/music-assistant/server:X.Y.Z.bN` +- `ghcr.io/music-assistant/server:beta` + +#### Nightly Release Tags +- `ghcr.io/music-assistant/server:X.Y.Z.devN` +- `ghcr.io/music-assistant/server:nightly` + +### 5. Update Add-on Repository (`update-addon-repository` job) +- ✅ Determines the correct add-on folder based on channel: + - **Stable**: `music_assistant` + - **Beta**: `music_assistant_beta` + - **Nightly**: `music_assistant_nightly` +- ✅ Updates `config.yaml` with the new version number +- ✅ Updates `CHANGELOG.md` with the full release notes +- ✅ Keeps only the last 2 versions in the changelog (removes older entries) +- ✅ Commits and pushes changes directly to the `music-assistant/home-assistant-addon` repository + +This ensures the Home Assistant add-on is automatically updated with each release, making it immediately available to users! + +## Base Image Versions + +The workflow uses different base image versions per channel (configured in workflow env vars): + +- **Stable**: `1.3.1` +- **Beta**: `1.3.2` +- **Nightly**: `1.3.2` + +These can be updated in the workflow file's `env` section. + +## Release Notes + +Release notes are automatically generated by Release Drafter based on: +- Merged pull requests since the last release of the same channel +- PR labels (breaking-change, feature, bugfix, etc.) +- Contributors list + +The release drafter configuration is in `.github/release-drafter.yml`. + +## Examples + +### Creating a Stable Release +``` +Version: 2.1.0 +Channel: stable +``` +This will: +- Create release `2.1.0` (not a prerelease) +- Publish to PyPI +- Tag Docker images with `2.1.0`, `2.1`, `2`, `stable`, and `latest` + +### Creating a Beta Release +``` +Version: 2.2.0.b1 +Channel: beta +``` +This will: +- Create release `2.2.0.b1` (marked as prerelease) +- Skip PyPI publish +- Tag Docker images with `2.2.0.b1` and `beta` + +### Creating a Nightly Release +``` +Version: 2.2.0.dev20251023 +Channel: nightly +``` +This will: +- Create release `2.2.0.dev20251023` (marked as prerelease) +- Skip PyPI publish +- Tag Docker images with `2.2.0.dev20251023` and `nightly` + +## Troubleshooting + +### Workflow Fails During Validation +- Check that your version number matches the format for the selected channel +- Ensure the version doesn't already exist as a tag + +### PyPI Publish Fails +- Verify the `PYPI_TOKEN` secret is configured correctly +- Check that the version doesn't already exist on PyPI + +### Docker Build Fails +- Check that the base image version exists +- Verify the Dockerfile is compatible with the version being built + +## Migration Notes + +This workflow replaces the previous `release.yml` which was triggered by creating a release in GitHub. Key differences: + +- **Before**: Create a release manually → workflow publishes it +- **Now**: Trigger workflow → it creates and publishes the release + +The new approach provides better automation, validation, and consistency across channels. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a4128a47..4cdff36c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,47 +1,110 @@ -name: Publish releases +name: Create Release on: - release: - types: [published] + workflow_dispatch: + inputs: + version: + description: "Version number (e.g., 1.2.3, 1.2.3.b1, or 1.2.3.dev1)" + required: true + type: string + channel: + description: "Release channel" + required: true + type: choice + options: + - stable + - beta + - nightly env: PYTHON_VERSION: "3.12" BASE_IMAGE_VERSION_STABLE: "1.3.1" BASE_IMAGE_VERSION_BETA: "1.3.2" + BASE_IMAGE_VERSION_NIGHTLY: "1.3.2" jobs: - build-artifact: - name: Builds python artifact uploads to Github Artifact store + validate-and-build: + name: Validate version and build Python artifact runs-on: ubuntu-latest outputs: - version: ${{ steps.vars.outputs.tag }} + version: ${{ inputs.version }} + is_prerelease: ${{ steps.validate.outputs.is_prerelease }} + base_image_version: ${{ steps.validate.outputs.base_image_version }} + branch: ${{ steps.branch.outputs.branch }} steps: - - uses: actions/checkout@v5 - - name: Get tag - id: vars - run: >- - echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT - - name: Validate version number - run: >- - if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then - if ! [[ "${{ steps.vars.outputs.tag }}" =~ "b" || "${{ steps.vars.outputs.tag }}" =~ "rc" ]]; then - echo "Pre-release: Tag is missing beta suffix (${{ steps.vars.outputs.tag }})" - exit 1 - fi + - name: Determine branch to use + id: branch + run: | + CHANNEL="${{ inputs.channel }}" + + if [ "$CHANNEL" = "stable" ]; then + echo "branch=stable" >> $GITHUB_OUTPUT + echo "Using stable branch for stable release" else - if [[ "${{ steps.vars.outputs.tag }}" =~ "b" || "${{ steps.vars.outputs.tag }}" =~ "rc" ]]; then - echo "Release: Tag must not have a beta (or rc) suffix (${{ steps.vars.outputs.tag }})" - exit 1 - fi + echo "branch=dev" >> $GITHUB_OUTPUT + echo "Using dev branch for $CHANNEL release" fi + + - uses: actions/checkout@v5 + with: + ref: ${{ steps.branch.outputs.branch }} + fetch-depth: 0 + + - name: Validate version number format + id: validate + run: | + VERSION="${{ inputs.version }}" + CHANNEL="${{ inputs.channel }}" + + # Regex patterns for each channel + STABLE_PATTERN='^[0-9]+\.[0-9]+\.[0-9]+$' + BETA_PATTERN='^[0-9]+\.[0-9]+\.[0-9]+\.b[0-9]+$' + NIGHTLY_PATTERN='^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$' + + # Validate version format matches channel + case "$CHANNEL" in + stable) + if ! [[ "$VERSION" =~ $STABLE_PATTERN ]]; then + echo "Error: Stable channel requires version format: X.Y.Z (e.g., 1.2.3)" + exit 1 + fi + echo "is_prerelease=false" >> $GITHUB_OUTPUT + echo "base_image_version=${{ env.BASE_IMAGE_VERSION_STABLE }}" >> $GITHUB_OUTPUT + ;; + beta) + if ! [[ "$VERSION" =~ $BETA_PATTERN ]]; then + echo "Error: Beta channel requires version format: X.Y.Z.bN (e.g., 1.2.3.b1)" + exit 1 + fi + echo "is_prerelease=true" >> $GITHUB_OUTPUT + echo "base_image_version=${{ env.BASE_IMAGE_VERSION_BETA }}" >> $GITHUB_OUTPUT + ;; + nightly) + if ! [[ "$VERSION" =~ $NIGHTLY_PATTERN ]]; then + echo "Error: Nightly channel requires version format: X.Y.Z.devN (e.g., 1.2.3.dev1)" + exit 1 + fi + echo "is_prerelease=true" >> $GITHUB_OUTPUT + echo "base_image_version=${{ env.BASE_IMAGE_VERSION_NIGHTLY }}" >> $GITHUB_OUTPUT + ;; + *) + echo "Error: Invalid channel: $CHANNEL" + exit 1 + ;; + esac + + echo "✅ Version $VERSION is valid for $CHANNEL channel" + - name: Set up Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v6.0.0 with: python-version: ${{ env.PYTHON_VERSION }} - - name: Install build + + - name: Install build dependencies run: >- pip install build tomli tomli-w - - name: Set Python project version from tag + + - name: Update version in pyproject.toml shell: python run: |- import tomli @@ -50,124 +113,425 @@ jobs: with open("pyproject.toml", "rb") as f: pyproject = tomli.load(f) - pyproject["project"]["version"] = "${{ steps.vars.outputs.tag }}" + pyproject["project"]["version"] = "${{ inputs.version }}" with open("pyproject.toml", "wb") as f: tomli_w.dump(pyproject, f) + + print(f"✅ Updated pyproject.toml version to ${{ inputs.version }}") + - name: Build python package run: >- python3 -m build + - name: Upload distributions uses: actions/upload-artifact@v4 with: name: release-dists path: dist/ + create-release: + name: Create GitHub Release with Release Drafter + runs-on: ubuntu-latest + needs: validate-and-build + permissions: + contents: write + pull-requests: read + outputs: + release_id: ${{ steps.create_release.outputs.id }} + upload_url: ${{ steps.create_release.outputs.upload_url }} + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ needs.validate-and-build.outputs.branch }} + fetch-depth: 0 + + - name: Download distributions + uses: actions/download-artifact@v5 + with: + name: release-dists + path: dist/ + + - name: Determine previous version tag + id: prev_version + run: | + CHANNEL="${{ inputs.channel }}" + + case "$CHANNEL" in + stable) + # For stable, find latest stable tag (no beta or dev suffix) + PREV_TAG=$(git tag --sort=-version:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1) + ;; + beta) + # For beta, find latest beta tag + PREV_TAG=$(git tag --sort=-version:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.b[0-9]+$' | head -n 1) + ;; + nightly) + # For nightly, find latest nightly tag + PREV_TAG=$(git tag --sort=-version:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$' | head -n 1) + ;; + esac + + if [ -z "$PREV_TAG" ]; then + echo "No previous $CHANNEL release found, using all commits" + echo "prev_tag=" >> $GITHUB_OUTPUT + else + echo "Previous $CHANNEL release: $PREV_TAG" + echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT + fi + + - name: Create Release with Release Drafter + id: create_release + uses: release-drafter/release-drafter@v6 + with: + version: ${{ inputs.version }} + tag: ${{ inputs.version }} + name: ${{ inputs.version }} + prerelease: ${{ needs.validate-and-build.outputs.is_prerelease }} + commitish: ${{ needs.validate-and-build.outputs.branch }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract and append frontend changes to release notes + id: update_notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "📦 Extracting frontend changes from PRs..." + + # Get the current release body + RELEASE_BODY=$(gh release view "${{ inputs.version }}" --json body --jq .body) + + # Find all frontend update PRs since the previous tag + PREV_TAG="${{ steps.prev_version.outputs.prev_tag }}" + + # Create temp files + FRONTEND_FILE=$(mktemp) + CONTRIBUTORS_FILE=$(mktemp) + + if [ -z "$PREV_TAG" ]; then + echo "No previous tag found, 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 + # Get PR numbers from merge commits since previous tag + echo "Searching for frontend PRs between $PREV_TAG and HEAD" + git log $PREV_TAG..HEAD --oneline --merges | grep -oP '#\K[0-9]+' > /tmp/pr_numbers.txt || echo "" + + # Fetch details for frontend update PRs + > /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 + + # Extract contributors from server release notes (existing) + echo "$RELEASE_BODY" | grep -oP '@[a-zA-Z0-9_-]+' | sort -u > "$CONTRIBUTORS_FILE" || true + + # Process each frontend PR and extract changes + contributors + 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 from the body, excluding section headers and contributors + echo "$BODY" | grep -E '^[[:space:]]*[•-]' | grep -v '🙇' | head -20 >> "$FRONTEND_FILE" || true + + # Extract contributors from frontend PR body + echo "$BODY" | grep -oP '@[a-zA-Z0-9_-]+' >> "$CONTRIBUTORS_FILE" || true + fi + done < /tmp/frontend_prs.json + + # Check if we actually found any changes + if [ $(wc -l < "$FRONTEND_FILE") -gt 3 ]; then + echo "✅ Found frontend changes" + + # Deduplicate and sort contributors + MERGED_CONTRIBUTORS=$(sort -u "$CONTRIBUTORS_FILE" | paste -sd ", " -) + + # Split release body into parts (before and after contributors section) + echo "$RELEASE_BODY" > /tmp/original_notes.md + + # Find where the contributors section starts + CONTRIB_LINE=$(grep -n "## :bow: Thanks to our contributors" /tmp/original_notes.md | head -1 | cut -d: -f1 || echo "") + + if [ -n "$CONTRIB_LINE" ]; then + # Extract everything before contributors section + head -n $((CONTRIB_LINE - 1)) /tmp/original_notes.md > /tmp/notes_before_contrib.md + + # Build new release notes with frontend changes and merged contributors + { + cat /tmp/notes_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/updated_notes.md + else + # No contributors section found, just append frontend changes + { + cat /tmp/original_notes.md + echo "" + cat "$FRONTEND_FILE" + } > /tmp/updated_notes.md + fi + + # Update the release + gh release edit "${{ inputs.version }}" --notes-file /tmp/updated_notes.md + + echo "✅ Release notes updated with frontend changes and merged contributors" + else + echo "ℹ️ No frontend bullet points found" + fi + else + echo "ℹ️ No frontend update PRs found in this release" + fi + + # Cleanup + rm -f "$FRONTEND_FILE" "$CONTRIBUTORS_FILE" /tmp/frontend_prs.json /tmp/pr_numbers.txt /tmp/updated_notes.md /tmp/original_notes.md /tmp/notes_before_contrib.md + + - name: Upload artifacts to release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ inputs.version }} + files: dist/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + pypi-publish: name: Publish release to PyPI (stable releases only) runs-on: ubuntu-latest - needs: build-artifact + needs: + - validate-and-build + - create-release + if: ${{ inputs.channel == 'stable' }} steps: - name: Retrieve release distributions - if: ${{ github.event.release.prerelease == false }} uses: actions/download-artifact@v5 with: name: release-dists path: dist/ + - name: Publish release to PyPI - if: ${{ github.event.release.prerelease == false }} uses: pypa/gh-action-pypi-publish@v1.13.0 with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} build-and-push-container-image: - name: Builds and pushes the Music Assistant Server container to ghcr.io + name: Build and push Music Assistant Server container to ghcr.io runs-on: ubuntu-latest permissions: packages: write needs: - - build-artifact - - pypi-publish + - validate-and-build + - create-release steps: - uses: actions/checkout@v5 - - name: Retrieve release distributions + with: + ref: ${{ needs.validate-and-build.outputs.branch }} + + - name: Download distributions uses: actions/download-artifact@v5 with: name: release-dists path: dist/ + - name: Log in to the GitHub container registry uses: docker/login-action@v3.6.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.11.1 - - name: Version number for tags + + - name: Generate Docker tags id: tags - shell: bash - run: |- - patch=${GITHUB_REF#refs/*/} - echo "patch=${patch}" >> $GITHUB_OUTPUT - echo "minor=${patch%.*}" >> $GITHUB_OUTPUT - echo "major=${patch%.*.*}" >> $GITHUB_OUTPUT - - name: Build and Push release - uses: docker/build-push-action@v6.18.0 - if: ${{ github.event.release.prerelease == false }} - with: - context: . - platforms: linux/amd64,linux/arm64 - file: Dockerfile - tags: |- - ghcr.io/${{ github.repository_owner }}/server:${{ steps.tags.outputs.patch }}, - ghcr.io/${{ github.repository_owner }}/server:${{ steps.tags.outputs.minor }}, - ghcr.io/${{ github.repository_owner }}/server:${{ steps.tags.outputs.major }}, - ghcr.io/${{ github.repository_owner }}/server:stable, - ghcr.io/${{ github.repository_owner }}/server:latest - push: true - build-args: | - MASS_VERSION=${{ needs.build-artifact.outputs.version }} - BASE_IMAGE_VERSION=${{ env.BASE_IMAGE_VERSION_STABLE }} - - name: Build and Push pre-release + run: | + VERSION="${{ inputs.version }}" + CHANNEL="${{ inputs.channel }}" + + # Extract version components + if [[ "$VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then + MAJOR="${BASH_REMATCH[1]}" + MINOR="${BASH_REMATCH[2]}" + PATCH="${BASH_REMATCH[3]}" + fi + + TAGS="ghcr.io/${{ github.repository_owner }}/server:$VERSION" + + case "$CHANNEL" in + stable) + # For stable: add major, minor, major.minor, stable, and latest tags + TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:$MAJOR.$MINOR.$PATCH" + TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:$MAJOR.$MINOR" + TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:$MAJOR" + TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:stable" + TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:latest" + ;; + beta) + # For beta: add beta tag + TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:beta" + ;; + nightly) + # For nightly: add nightly tag + TAGS="$TAGS,ghcr.io/${{ github.repository_owner }}/server:nightly" + ;; + esac + + echo "tags=$TAGS" >> $GITHUB_OUTPUT + echo "Docker tags: $TAGS" + + - name: Build and push Docker image uses: docker/build-push-action@v6.18.0 - if: ${{ github.event.release.prerelease == true }} with: context: . platforms: linux/amd64,linux/arm64 file: Dockerfile - tags: |- - ghcr.io/${{ github.repository_owner }}/server:${{ steps.tags.outputs.patch }}, - ghcr.io/${{ github.repository_owner }}/server:beta + tags: ${{ steps.tags.outputs.tags }} push: true build-args: | - MASS_VERSION=${{ needs.build-artifact.outputs.version }} - BASE_IMAGE_VERSION=${{ env.BASE_IMAGE_VERSION_BETA }} + MASS_VERSION=${{ inputs.version }} + BASE_IMAGE_VERSION=${{ needs.validate-and-build.outputs.base_image_version }} - release-notes-update: - name: Updates the release notes and changelog - needs: [build-artifact, pypi-publish, build-and-push-container-image] + update-addon-repository: + name: Update Home Assistant Add-on Repository runs-on: ubuntu-latest - steps: - - name: Update changelog and release notes including frontend notes - uses: music-assistant/release-notes-merge-action@main - with: - github_token: ${{ secrets.PRIVILEGED_GITHUB_TOKEN }} - release_tag: ${{ needs.build-artifact.outputs.version }} - pre_release: ${{ github.event.release.prerelease }} - - addon-version-update: - name: Updates the Addon repository with the new version needs: - - build-artifact - - pypi-publish + - validate-and-build + - create-release - build-and-push-container-image - - release-notes-update - runs-on: ubuntu-latest steps: - - name: Push new version number to addon config - uses: music-assistant/addon-update-action@main + - name: Determine addon folder + id: addon_folder + run: | + CHANNEL="${{ inputs.channel }}" + + case "$CHANNEL" in + stable) + echo "folder=music_assistant" >> $GITHUB_OUTPUT + echo "Updating stable add-on" + ;; + beta) + echo "folder=music_assistant_beta" >> $GITHUB_OUTPUT + echo "Updating beta add-on" + ;; + nightly) + echo "folder=music_assistant_nightly" >> $GITHUB_OUTPUT + echo "Updating nightly add-on" + ;; + esac + + - name: Checkout add-on repository + uses: actions/checkout@v5 with: - github_token: ${{ secrets.PRIVILEGED_GITHUB_TOKEN }} - new_server_version: ${{ needs.build-artifact.outputs.version }} - pre_release: ${{ github.event.release.prerelease }} + repository: music-assistant/home-assistant-addon + token: ${{ secrets.GITHUB_TOKEN }} + path: addon-repo + + - name: Get release notes + id: get_notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd addon-repo + + # Get the release body from the server repository + RELEASE_NOTES=$(gh release view "${{ inputs.version }}" --repo music-assistant/server --json body --jq .body) + + # Save to file for processing + echo "$RELEASE_NOTES" > /tmp/release_notes.md + + - name: Update config.yaml version + run: | + ADDON_FOLDER="${{ steps.addon_folder.outputs.folder }}" + VERSION="${{ inputs.version }}" + + cd addon-repo/$ADDON_FOLDER + + # Update version in config.yaml using sed + sed -i "s/^version: .*/version: $VERSION/" config.yaml + + echo "✅ Updated config.yaml to version $VERSION" + + - name: Update CHANGELOG.md + run: | + ADDON_FOLDER="${{ steps.addon_folder.outputs.folder }}" + VERSION="${{ inputs.version }}" + + cd addon-repo/$ADDON_FOLDER + + # Get current date + RELEASE_DATE=$(date +"%d.%m.%Y") + + # Read the new release notes + NEW_NOTES=$(cat /tmp/release_notes.md) + + # Create new changelog entry + { + echo "# [$VERSION] - $RELEASE_DATE" + echo "" + echo "$NEW_NOTES" + echo "" + echo "" + } > /tmp/new_changelog.md + + # If CHANGELOG.md exists, keep only the last 2 versions + if [ -f CHANGELOG.md ]; then + # Extract headers to count versions + VERSION_COUNT=$(grep -c "^# \[" CHANGELOG.md || echo "0") + + if [ "$VERSION_COUNT" -ge 2 ]; then + # Keep only first 2 versions (extract everything before the 3rd version header) + awk '/^# \[/{i++}i==3{exit}1' CHANGELOG.md > /tmp/old_changelog.md + + # Combine new entry with trimmed old changelog + cat /tmp/new_changelog.md /tmp/old_changelog.md > CHANGELOG.md + else + # Less than 2 versions, just prepend + cat /tmp/new_changelog.md CHANGELOG.md > /tmp/combined_changelog.md + mv /tmp/combined_changelog.md CHANGELOG.md + fi + else + # No existing changelog, create new + mv /tmp/new_changelog.md CHANGELOG.md + fi + + echo "✅ Updated CHANGELOG.md with new release" + + - name: Commit and push changes + run: | + ADDON_FOLDER="${{ steps.addon_folder.outputs.folder }}" + VERSION="${{ inputs.version }}" + CHANNEL="${{ inputs.channel }}" + + cd addon-repo + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git add "$ADDON_FOLDER/config.yaml" "$ADDON_FOLDER/CHANGELOG.md" + + git commit -m "🤖 Bump $CHANNEL add-on to version $VERSION" || { + echo "No changes to commit" + exit 0 + } + + git push + + echo "✅ Successfully updated add-on repository" -- 2.34.1