Skip to content
Latchkey

Auto Release workflow (mjcumming/wiim)

The Auto Release workflow from mjcumming/wiim, explained and optimized by Latchkey.

C

CI health: C - fair

Point runs-on at Latchkey and get run de-duplication, job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: mjcumming/wiim.github/workflows/auto-release.ymlLicense MITView source

What it does

This is the Auto Release workflow from the mjcumming/wiim repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
# Enable automatic release workflow
name: Auto Release

on:
  push:
    branches:
      - main
    paths:
      - "custom_components/wiim/manifest.json"
  workflow_dispatch:
    inputs:
      reason:
        description: "Manual trigger reason"
        required: false
        type: string

jobs:
  check-version:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
      version-changed: ${{ steps.check.outputs.changed }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 2

      - name: Get current version
        id: version
        run: |
          VERSION=$(grep '"version"' custom_components/wiim/manifest.json | sed 's/.*"version": "\(.*\)".*/\1/')
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "Current version: $VERSION"

      - name: Get previous version
        id: prev-version
        run: |
          git checkout HEAD~1
          PREV_VERSION=$(grep '"version"' custom_components/wiim/manifest.json | sed 's/.*"version": "\(.*\)".*/\1/' || echo "none")
          echo "prev-version=$PREV_VERSION" >> $GITHUB_OUTPUT
          echo "Previous version: $PREV_VERSION"
          git checkout -

      - name: Check if version changed
        id: check
        run: |
          if [ "${{ steps.version.outputs.version }}" != "${{ steps.prev-version.outputs.prev-version }}" ]; then
            echo "changed=true" >> $GITHUB_OUTPUT
            echo "Version changed from ${{ steps.prev-version.outputs.prev-version }} to ${{ steps.version.outputs.version }}"
          else
            echo "changed=false" >> $GITHUB_OUTPUT
            echo "Version unchanged"
          fi

  # HACS validation - only needed for releases
  # Note: Tests and linting are handled by the CI workflow, which runs on every push.
  # This workflow only runs when manifest.json changes, so CI will have already validated the code.
  hacs-validation:
    needs: check-version
    if: needs.check-version.outputs.version-changed == 'true' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      # --- HACS repository validation ---------------------------------------------------
      # Validate that the repository follows all rules required by HACS.
      # This uses the official HACS Action (https://github.com/hacs/action)
      # so that every push and every automated release is guaranteed to be
      # accepted by HACS without manual re-validation.
      - name: Validate repository with HACS Action
        uses: hacs/action@22.5.0
        with:
          category: "integration"
          ignore: "topics brands"

  release:
    needs: [check-version, hacs-validation]
    if: needs.check-version.outputs.version-changed == 'true' || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    permissions:
      contents: write
      actions: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Wait for CI tests to complete
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          echo "⏳ Waiting for CI tests to complete before creating release..."
          echo "This ensures the release is only created after all tests pass."

          # Get the commit SHA that triggered this workflow
          COMMIT_SHA="${{ github.sha }}"
          WORKFLOW_NAME="CI"
          REPO="${{ github.repository }}"

          # Wait up to 15 minutes for CI to complete
          MAX_WAIT=900
          ELAPSED=0
          INTERVAL=10

          while [ $ELAPSED -lt $MAX_WAIT ]; do
            # Get the latest CI workflow run for this commit
            RUN_ID=$(gh run list \
              --workflow="$WORKFLOW_NAME" \
              --commit="$COMMIT_SHA" \
              --repo="$REPO" \
              --json databaseId,conclusion,status \
              --jq '.[0].databaseId' 2>/dev/null || echo "")

            if [ -z "$RUN_ID" ] || [ "$RUN_ID" == "null" ]; then
              echo "⏳ CI workflow not found yet, waiting..."
              sleep $INTERVAL
              ELAPSED=$((ELAPSED + INTERVAL))
              continue
            fi

            # Get workflow status
            STATUS=$(gh run view "$RUN_ID" \
              --repo="$REPO" \
              --json status,conclusion \
              --jq '.status' 2>/dev/null || echo "unknown")

            CONCLUSION=$(gh run view "$RUN_ID" \
              --repo="$REPO" \
              --json conclusion \
              --jq '.conclusion' 2>/dev/null || echo "unknown")

            echo "πŸ“Š CI Status: $STATUS, Conclusion: $CONCLUSION"

            if [ "$STATUS" == "completed" ]; then
              if [ "$CONCLUSION" == "success" ]; then
                echo "βœ… CI tests passed! Proceeding with release creation."
                break
              else
                echo "❌ CI tests failed or were cancelled. Aborting release."
                echo "Please fix the failing tests before releasing."
                exit 1
              fi
            fi

            echo "⏳ CI still running, waiting ${INTERVAL}s... (${ELAPSED}s elapsed)"
            sleep $INTERVAL
            ELAPSED=$((ELAPSED + INTERVAL))
          done

          if [ $ELAPSED -ge $MAX_WAIT ]; then
            echo "⚠️  Timeout waiting for CI to complete. Proceeding anyway, but this is unusual."
            echo "Please verify CI status manually: https://github.com/$REPO/actions"
          fi

      - name: Check if release already exists
        id: release-check
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          if gh release view "v${{ needs.check-version.outputs.version }}" >/dev/null 2>&1; then
            echo "exists=true" >> $GITHUB_OUTPUT
            echo "Release v${{ needs.check-version.outputs.version }} already exists"
          else
            echo "exists=false" >> $GITHUB_OUTPUT
            echo "Release v${{ needs.check-version.outputs.version }} does not exist"
          fi

      - name: Create ZIP file
        if: steps.release-check.outputs.exists == 'false'
        run: |
          rm -f wiim.zip
          # HACS with zip_release: true expects integration files at ZIP root
          # HACS creates custom_components/wiim/ and extracts ZIP contents there
          cd custom_components/wiim
          zip -r ../../wiim.zip . --exclude "*/__pycache__/*" --exclude "*.pyc"
          cd ../../
          # Add metadata files at ZIP root (optional)
          zip wiim.zip LICENSE README.md

      - name: Extract changelog for this version
        if: steps.release-check.outputs.exists == 'false'
        id: changelog
        run: |
          if [ -f "CHANGELOG.md" ]; then
            awk '/^## \['"${{ needs.check-version.outputs.version }}"'\]/{flag=1; next} /^## \[/{flag=0} flag' CHANGELOG.md > release_notes.md
            if [ -s release_notes.md ]; then
              printf 'changelog<<EOF\n' >> $GITHUB_OUTPUT
              cat release_notes.md >> $GITHUB_OUTPUT
              printf 'EOF\n' >> $GITHUB_OUTPUT
            else
              echo "changelog=Release v${{ needs.check-version.outputs.version }}" >> $GITHUB_OUTPUT
            fi
          else
            echo "changelog=Release v${{ needs.check-version.outputs.version }}" >> $GITHUB_OUTPUT
          fi

      - name: Create Release with GitHub CLI
        if: steps.release-check.outputs.exists == 'false'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          RELEASE_NOTES: ${{ steps.changelog.outputs.changelog }}
        run: |
          # Double-check that release doesn't exist before creating
          if gh release view "v${{ needs.check-version.outputs.version }}" >/dev/null 2>&1; then
            echo "Release v${{ needs.check-version.outputs.version }} already exists, skipping creation"
            exit 0
          fi
          gh release create "v${{ needs.check-version.outputs.version }}" \
            --title "WiiM Audio v${{ needs.check-version.outputs.version }}" \
            --notes "$RELEASE_NOTES" \
            --latest \
            wiim.zip || {
              if gh release view "v${{ needs.check-version.outputs.version }}" >/dev/null 2>&1; then
                echo "Release already exists (created by another process), continuing..."
                exit 0
              else
                echo "Failed to create release"
                exit 1
              fi
            }

      - name: Success notification
        if: steps.release-check.outputs.exists == 'false'
        run: |
          echo "πŸŽ‰ Successfully created release v${{ needs.check-version.outputs.version }}"
          echo "πŸ“¦ ZIP file uploaded as wiim.zip"
          echo "🏠 HACS will detect this release within 24 hours"
          echo "πŸ”— Release URL: https://github.com/${{ github.repository }}/releases/tag/v${{ needs.check-version.outputs.version }}"

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

# Enable automatic release workflow
name: Auto Release
 
on:
  push:
    branches:
      - main
    paths:
      - "custom_components/wiim/manifest.json"
  workflow_dispatch:
    inputs:
      reason:
        description: "Manual trigger reason"
        required: false
        type: string
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  check-version:
    timeout-minutes: 30
    runs-on: latchkey-small
    outputs:
      version: ${{ steps.version.outputs.version }}
      version-changed: ${{ steps.check.outputs.changed }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 2
 
      - name: Get current version
        id: version
        run: |
          VERSION=$(grep '"version"' custom_components/wiim/manifest.json | sed 's/.*"version": "\(.*\)".*/\1/')
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "Current version: $VERSION"
 
      - name: Get previous version
        id: prev-version
        run: |
          git checkout HEAD~1
          PREV_VERSION=$(grep '"version"' custom_components/wiim/manifest.json | sed 's/.*"version": "\(.*\)".*/\1/' || echo "none")
          echo "prev-version=$PREV_VERSION" >> $GITHUB_OUTPUT
          echo "Previous version: $PREV_VERSION"
          git checkout -
 
      - name: Check if version changed
        id: check
        run: |
          if [ "${{ steps.version.outputs.version }}" != "${{ steps.prev-version.outputs.prev-version }}" ]; then
            echo "changed=true" >> $GITHUB_OUTPUT
            echo "Version changed from ${{ steps.prev-version.outputs.prev-version }} to ${{ steps.version.outputs.version }}"
          else
            echo "changed=false" >> $GITHUB_OUTPUT
            echo "Version unchanged"
          fi
 
  # HACS validation - only needed for releases
  # Note: Tests and linting are handled by the CI workflow, which runs on every push.
  # This workflow only runs when manifest.json changes, so CI will have already validated the code.
  hacs-validation:
    timeout-minutes: 30
    needs: check-version
    if: needs.check-version.outputs.version-changed == 'true' || github.event_name == 'workflow_dispatch'
    runs-on: latchkey-small
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
 
      # --- HACS repository validation ---------------------------------------------------
      # Validate that the repository follows all rules required by HACS.
      # This uses the official HACS Action (https://github.com/hacs/action)
      # so that every push and every automated release is guaranteed to be
      # accepted by HACS without manual re-validation.
      - name: Validate repository with HACS Action
        uses: hacs/action@22.5.0
        with:
          category: "integration"
          ignore: "topics brands"
 
  release:
    timeout-minutes: 30
    needs: [check-version, hacs-validation]
    if: needs.check-version.outputs.version-changed == 'true' || github.event_name == 'workflow_dispatch'
    runs-on: latchkey-small
    permissions:
      contents: write
      actions: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
 
      - name: Wait for CI tests to complete
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          echo "⏳ Waiting for CI tests to complete before creating release..."
          echo "This ensures the release is only created after all tests pass."
 
          # Get the commit SHA that triggered this workflow
          COMMIT_SHA="${{ github.sha }}"
          WORKFLOW_NAME="CI"
          REPO="${{ github.repository }}"
 
          # Wait up to 15 minutes for CI to complete
          MAX_WAIT=900
          ELAPSED=0
          INTERVAL=10
 
          while [ $ELAPSED -lt $MAX_WAIT ]; do
            # Get the latest CI workflow run for this commit
            RUN_ID=$(gh run list \
              --workflow="$WORKFLOW_NAME" \
              --commit="$COMMIT_SHA" \
              --repo="$REPO" \
              --json databaseId,conclusion,status \
              --jq '.[0].databaseId' 2>/dev/null || echo "")
 
            if [ -z "$RUN_ID" ] || [ "$RUN_ID" == "null" ]; then
              echo "⏳ CI workflow not found yet, waiting..."
              sleep $INTERVAL
              ELAPSED=$((ELAPSED + INTERVAL))
              continue
            fi
 
            # Get workflow status
            STATUS=$(gh run view "$RUN_ID" \
              --repo="$REPO" \
              --json status,conclusion \
              --jq '.status' 2>/dev/null || echo "unknown")
 
            CONCLUSION=$(gh run view "$RUN_ID" \
              --repo="$REPO" \
              --json conclusion \
              --jq '.conclusion' 2>/dev/null || echo "unknown")
 
            echo "πŸ“Š CI Status: $STATUS, Conclusion: $CONCLUSION"
 
            if [ "$STATUS" == "completed" ]; then
              if [ "$CONCLUSION" == "success" ]; then
                echo "βœ… CI tests passed! Proceeding with release creation."
                break
              else
                echo "❌ CI tests failed or were cancelled. Aborting release."
                echo "Please fix the failing tests before releasing."
                exit 1
              fi
            fi
 
            echo "⏳ CI still running, waiting ${INTERVAL}s... (${ELAPSED}s elapsed)"
            sleep $INTERVAL
            ELAPSED=$((ELAPSED + INTERVAL))
          done
 
          if [ $ELAPSED -ge $MAX_WAIT ]; then
            echo "⚠️  Timeout waiting for CI to complete. Proceeding anyway, but this is unusual."
            echo "Please verify CI status manually: https://github.com/$REPO/actions"
          fi
 
      - name: Check if release already exists
        id: release-check
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          if gh release view "v${{ needs.check-version.outputs.version }}" >/dev/null 2>&1; then
            echo "exists=true" >> $GITHUB_OUTPUT
            echo "Release v${{ needs.check-version.outputs.version }} already exists"
          else
            echo "exists=false" >> $GITHUB_OUTPUT
            echo "Release v${{ needs.check-version.outputs.version }} does not exist"
          fi
 
      - name: Create ZIP file
        if: steps.release-check.outputs.exists == 'false'
        run: |
          rm -f wiim.zip
          # HACS with zip_release: true expects integration files at ZIP root
          # HACS creates custom_components/wiim/ and extracts ZIP contents there
          cd custom_components/wiim
          zip -r ../../wiim.zip . --exclude "*/__pycache__/*" --exclude "*.pyc"
          cd ../../
          # Add metadata files at ZIP root (optional)
          zip wiim.zip LICENSE README.md
 
      - name: Extract changelog for this version
        if: steps.release-check.outputs.exists == 'false'
        id: changelog
        run: |
          if [ -f "CHANGELOG.md" ]; then
            awk '/^## \['"${{ needs.check-version.outputs.version }}"'\]/{flag=1; next} /^## \[/{flag=0} flag' CHANGELOG.md > release_notes.md
            if [ -s release_notes.md ]; then
              printf 'changelog<<EOF\n' >> $GITHUB_OUTPUT
              cat release_notes.md >> $GITHUB_OUTPUT
              printf 'EOF\n' >> $GITHUB_OUTPUT
            else
              echo "changelog=Release v${{ needs.check-version.outputs.version }}" >> $GITHUB_OUTPUT
            fi
          else
            echo "changelog=Release v${{ needs.check-version.outputs.version }}" >> $GITHUB_OUTPUT
          fi
 
      - name: Create Release with GitHub CLI
        if: steps.release-check.outputs.exists == 'false'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          RELEASE_NOTES: ${{ steps.changelog.outputs.changelog }}
        run: |
          # Double-check that release doesn't exist before creating
          if gh release view "v${{ needs.check-version.outputs.version }}" >/dev/null 2>&1; then
            echo "Release v${{ needs.check-version.outputs.version }} already exists, skipping creation"
            exit 0
          fi
          gh release create "v${{ needs.check-version.outputs.version }}" \
            --title "WiiM Audio v${{ needs.check-version.outputs.version }}" \
            --notes "$RELEASE_NOTES" \
            --latest \
            wiim.zip || {
              if gh release view "v${{ needs.check-version.outputs.version }}" >/dev/null 2>&1; then
                echo "Release already exists (created by another process), continuing..."
                exit 0
              else
                echo "Failed to create release"
                exit 1
              fi
            }
 
      - name: Success notification
        if: steps.release-check.outputs.exists == 'false'
        run: |
          echo "πŸŽ‰ Successfully created release v${{ needs.check-version.outputs.version }}"
          echo "πŸ“¦ ZIP file uploaded as wiim.zip"
          echo "🏠 HACS will detect this release within 24 hours"
          echo "πŸ”— Release URL: https://github.com/${{ github.repository }}/releases/tag/v${{ needs.check-version.outputs.version }}"
 

What changed

1 third-party action is referenced by a movable tag. Pin it to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

This workflow runs 3 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow