name: Release Build on: push: tags: - "v*.*.*" workflow_dispatch: inputs: version: description: 'Version to release (e.g., 1.0.0)' required: true runTests: description: 'Run test suite' type: boolean required: false default: true buildDocker: description: 'Build Docker image' type: boolean required: false default: true pushDocker: description: 'Push Docker image to registry' type: boolean required: false default: false buildBinaries: description: 'Build executables and installers' type: boolean required: false default: true createRelease: description: 'Create GitHub release' type: boolean required: false default: false jobs: # Validate release validate: runs-on: ubuntu-latest outputs: app_version: ${{ steps.version.outputs.app_version }} release_version: ${{ steps.version.outputs.release_version }} is_tag: ${{ steps.version.outputs.is_tag }} steps: - name: Checkout uses: actions/checkout@v4 - name: Get version info id: version run: | if [[ "${{ github.ref }}" =~ ^refs/tags/ ]]; then # Tag event release_version=${GITHUB_REF##refs/tags/} app_version=${release_version#v} is_tag=true else # Manual workflow with version app_version="${{ github.event.inputs.version }}" # Validate version format (x.x.x) if ! [[ "$app_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Error: Version must be in format x.x.x (e.g., 1.0.0)" echo "Provided version: $app_version" exit 1 fi release_version="v$app_version" is_tag=false fi echo "app_version=$app_version" >> $GITHUB_OUTPUT echo "release_version=$release_version" >> $GITHUB_OUTPUT echo "is_tag=$is_tag" >> $GITHUB_OUTPUT echo "Release Version: $release_version" echo "App Version: $app_version" echo "Is Tag: $is_tag" - name: Check if release already exists run: | if gh release view "${{ steps.version.outputs.release_version }}" &>/dev/null; then echo "❌ Release ${{ steps.version.outputs.release_version }} already exists. Stopping workflow." exit 1 fi echo "✅ Release ${{ steps.version.outputs.release_version }} does not exist. Proceeding." env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Run tests test: needs: validate if: ${{ needs.validate.outputs.is_tag == 'true' || github.event.inputs.runTests == 'true' }} uses: ./.github/workflows/test.yml secrets: inherit # Run E2E tests e2e: needs: validate if: ${{ needs.validate.outputs.is_tag == 'true' || github.event.inputs.runTests == 'true' }} uses: ./.github/workflows/e2e.yml secrets: inherit # Build frontend once for all build jobs and cache it build-frontend: needs: [validate, test, e2e] if: | always() && needs.validate.result == 'success' && (needs.test.result == 'success' || needs.test.result == 'skipped') && (needs.e2e.result == 'success' || needs.e2e.result == 'skipped') && (needs.validate.outputs.is_tag == 'true' || github.event.inputs.buildBinaries == 'true') uses: ./.github/workflows/build-frontend.yml secrets: inherit # Build portable executables build-executables: needs: [validate, test, e2e, build-frontend] if: | always() && needs.validate.result == 'success' && (needs.test.result == 'success' || needs.test.result == 'skipped') && (needs.e2e.result == 'success' || needs.e2e.result == 'skipped') && needs.build-frontend.result == 'success' && (needs.validate.outputs.is_tag == 'true' || github.event.inputs.buildBinaries == 'true') uses: ./.github/workflows/build-executable.yml with: app_version: ${{ needs.validate.outputs.app_version }} secrets: inherit # Build Windows installer build-windows-installer: needs: [validate, test, e2e, build-frontend] if: | always() && needs.validate.result == 'success' && (needs.test.result == 'success' || needs.test.result == 'skipped') && (needs.e2e.result == 'success' || needs.e2e.result == 'skipped') && needs.build-frontend.result == 'success' && (needs.validate.outputs.is_tag == 'true' || github.event.inputs.buildBinaries == 'true') uses: ./.github/workflows/build-windows-installer.yml with: app_version: ${{ needs.validate.outputs.app_version }} secrets: inherit # Build macOS installers (Intel and ARM) build-macos: needs: [validate, test, e2e, build-frontend] if: | always() && needs.validate.result == 'success' && (needs.test.result == 'success' || needs.test.result == 'skipped') && (needs.e2e.result == 'success' || needs.e2e.result == 'skipped') && needs.build-frontend.result == 'success' && (needs.validate.outputs.is_tag == 'true' || github.event.inputs.buildBinaries == 'true') uses: ./.github/workflows/build-macos-installer.yml with: app_version: ${{ needs.validate.outputs.app_version }} secrets: inherit # Build and push Docker image(s) build-docker: needs: [validate, test, e2e] if: | always() && needs.validate.result == 'success' && (needs.test.result == 'success' || needs.test.result == 'skipped') && (needs.e2e.result == 'success' || needs.e2e.result == 'skipped') && (needs.validate.outputs.is_tag == 'true' || github.event.inputs.buildDocker == 'true') uses: ./.github/workflows/build-docker.yml with: push_docker: ${{ needs.validate.outputs.is_tag == 'true' || github.event.inputs.pushDocker == 'true' }} app_version: ${{ needs.validate.outputs.app_version }} secrets: inherit # Create GitHub release create-release: needs: [validate, build-executables, build-windows-installer, build-macos] runs-on: ubuntu-latest if: | always() && needs.validate.result == 'success' && needs.build-executables.result == 'success' && needs.build-windows-installer.result == 'success' && needs.build-macos.result == 'success' && ( needs.validate.outputs.is_tag == 'true' || (github.event.inputs.createRelease == 'true' && github.event.inputs.buildBinaries == 'true') ) steps: - name: Get vault secrets uses: hashicorp/vault-action@v2 with: url: ${{ secrets.VAULT_HOST }} method: approle roleId: ${{ secrets.VAULT_ROLE_ID }} secretId: ${{ secrets.VAULT_SECRET_ID }} secrets: secrets/data/github repo_readonly_pat | REPO_READONLY_PAT - name: Download executable artifacts uses: actions/download-artifact@v4 with: pattern: executable-* path: ./artifacts merge-multiple: true - name: Download Windows installer uses: actions/download-artifact@v4 with: name: Cleanuparr-windows-installer path: ./artifacts - name: Download macOS installers uses: actions/download-artifact@v4 with: pattern: Cleanuparr-macos-*-installer path: ./artifacts merge-multiple: true - name: List downloaded artifacts run: | echo "Downloaded artifacts:" find ./artifacts -type f \( -name "*.zip" -o -name "*.pkg" -o -name "*.exe" \) | sort echo "" echo "Total files: $(find ./artifacts -type f \( -name "*.zip" -o -name "*.pkg" -o -name "*.exe" \) | wc -l)" - name: Create release uses: softprops/action-gh-release@v2 with: name: ${{ needs.validate.outputs.release_version }} tag_name: ${{ needs.validate.outputs.release_version }} token: ${{ env.REPO_READONLY_PAT }} make_latest: true target_commitish: main generate_release_notes: true files: | ./artifacts/*.zip ./artifacts/*.pkg ./artifacts/*.exe # Summary job summary: needs: [validate, test, e2e, build-frontend, build-executables, build-windows-installer, build-macos, build-docker] runs-on: ubuntu-latest if: always() steps: - name: Record workflow start time id: workflow-start run: | # Get workflow start time from GitHub API workflow_start=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }} --jq '.run_started_at') start_epoch=$(date -d "$workflow_start" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$workflow_start" +%s) echo "start=$start_epoch" >> $GITHUB_OUTPUT env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build Summary run: | # Calculate total workflow duration start_time=${{ steps.workflow-start.outputs.start }} end_time=$(date +%s) duration=$((end_time - start_time)) minutes=$((duration / 60)) seconds=$((duration % 60)) echo "## 🏗️ Cleanuparr Build Summary" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "**Version**: ${{ needs.validate.outputs.release_version }}" >> $GITHUB_STEP_SUMMARY echo "**App Version**: ${{ needs.validate.outputs.app_version }}" >> $GITHUB_STEP_SUMMARY echo "**Is Tag**: ${{ needs.validate.outputs.is_tag }}" >> $GITHUB_STEP_SUMMARY echo "**Total Duration**: ${minutes}m ${seconds}s" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Build Results" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY # Helper function to print job result print_result() { local name="$1" local result="$2" case "$result" in success) echo "✅ **$name**: Success" >> $GITHUB_STEP_SUMMARY ;; skipped) echo "⏭️ **$name**: Skipped" >> $GITHUB_STEP_SUMMARY ;; *) echo "❌ **$name**: $result" >> $GITHUB_STEP_SUMMARY ;; esac } print_result "Tests" "${{ needs.test.result }}" print_result "E2E Tests" "${{ needs.e2e.result }}" print_result "Frontend Build" "${{ needs.build-frontend.result }}" print_result "Portable Executables" "${{ needs.build-executables.result }}" print_result "Windows Installer" "${{ needs.build-windows-installer.result }}" print_result "macOS Installers (Intel & ARM)" "${{ needs.build-macos.result }}" print_result "Docker Image Build" "${{ needs.build-docker.result }}" echo "" >> $GITHUB_STEP_SUMMARY echo "🎉 **Build completed!**" >> $GITHUB_STEP_SUMMARY