From 2b761f15c82a0ae522bb3e3944f896aef4bf3846 Mon Sep 17 00:00:00 2001 From: Uruk Date: Tue, 30 Sep 2025 00:51:02 +0200 Subject: [PATCH] feat!: replace workflow_run with repository_dispatch for real-time build status updates Replaces the workflow_run trigger mechanism with repository_dispatch events to enable real-time build status communication between build workflows and the artifact comment system. Build workflows now actively notify the comment workflow when builds start, complete, or fail, providing immediate status updates rather than polling for completed workflows. Adds real-time payload processing to display current build status and target information in PR comments, improving visibility into ongoing build processes. BREAKING CHANGE: Changes the trigger mechanism from workflow_run to repository_dispatch, requiring build workflows to explicitly send status notifications. --- .github/workflows/artifact-comment.yml | 110 ++++++++++++++++++------- .github/workflows/build-android.yml | 61 ++++++++++++++ .github/workflows/build-ios.yml | 67 ++++++++++++++- 3 files changed, 207 insertions(+), 31 deletions(-) diff --git a/.github/workflows/artifact-comment.yml b/.github/workflows/artifact-comment.yml index 1aa981f1..0bfb99af 100644 --- a/.github/workflows/artifact-comment.yml +++ b/.github/workflows/artifact-comment.yml @@ -1,56 +1,61 @@ name: 📝 Artifact Comment on PR concurrency: - group: artifact-comment-${{ github.event.workflow_run.head_sha || github.sha }} + group: artifact-comment-${{ github.sha }} cancel-in-progress: true on: workflow_dispatch: # Allow manual testing pull_request: # Show in PR checks and provide status updates types: [opened, synchronize, reopened] - workflow_run: - workflows: - - "🤖 Android APK Build (Phone + TV)" - - "🤖 iOS IPA Build (Phone + TV)" + repository_dispatch: # Triggered by build workflows when they start/complete types: - - completed - - requested # Trigger when build starts + - build-started + - build-completed + - build-failed jobs: comment-artifacts: - if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || (github.event_name == 'workflow_run' && github.event.workflow_run.event == 'pull_request') + if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'repository_dispatch' name: 📦 Post Build Artifacts runs-on: ubuntu-latest permissions: contents: read pull-requests: write actions: read + repository-projects: read steps: - name: 🔍 Get PR and Artifacts uses: actions/github-script@v8 with: script: | - // Handle workflow_run, pull_request, and manual dispatch events + // Handle repository_dispatch, pull_request, and manual dispatch events let pr; + let targetCommitSha; - if (context.eventName === 'workflow_run') { - // Find PR associated with this commit - const { data: pullRequests } = await github.rest.repos.listPullRequestsAssociatedWithCommit({ - owner: context.repo.owner, - repo: context.repo.repo, - commit_sha: github.event.workflow_run.head_sha - }); + if (context.eventName === 'repository_dispatch') { + // Triggered by build workflows - get PR info from payload + const payload = context.payload.client_payload; + console.log('Repository dispatch payload:', JSON.stringify(payload, null, 2)); - if (pullRequests.length === 0) { - console.log('No pull request found for commit:', github.event.workflow_run.head_sha); + if (!payload || !payload.pr_number) { + console.log('No PR information in repository_dispatch payload'); return; } - pr = pullRequests[0]; + + const { data: prData } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: payload.pr_number + }); + pr = prData; + targetCommitSha = payload.commit_sha || pr.head.sha; } else if (context.eventName === 'pull_request') { // Direct PR event pr = context.payload.pull_request; + targetCommitSha = pr.head.sha; } else if (context.eventName === 'workflow_dispatch') { // Get current PR for manual testing @@ -61,19 +66,20 @@ jobs: pull_number: prNumber }); pr = prData; + targetCommitSha = pr.head.sha; } else { console.log('Unsupported event type:', context.eventName); return; } - console.log(`Processing PR #${pr.number} for commit ${pr.head.sha.substring(0, 7)}`); + console.log(`Processing PR #${pr.number} for commit ${targetCommitSha.substring(0, 7)}`); // Get all recent workflow runs for this PR to collect artifacts from multiple builds const { data: workflowRuns } = await github.rest.actions.listWorkflowRunsForRepo({ owner: context.repo.owner, repo: context.repo.repo, - head_sha: pr.head.sha, + head_sha: targetCommitSha, per_page: 30 }); @@ -151,11 +157,50 @@ jobs: } } + // Override with real-time data from repository_dispatch if available + if (context.eventName === 'repository_dispatch') { + const payload = context.payload.client_payload; + const workflowType = payload.workflow_name.includes('Android') ? 'Android' : 'iOS'; + + if (buildStatuses[workflowType]) { + // Update the existing status with real-time data + buildStatuses[workflowType].status = payload.status === 'in_progress' ? 'in_progress' : + payload.status === 'success' ? 'completed' : + payload.status === 'failure' ? 'completed' : + buildStatuses[workflowType].status; + buildStatuses[workflowType].conclusion = payload.status === 'success' ? 'success' : + payload.status === 'failure' ? 'failure' : + buildStatuses[workflowType].conclusion; + buildStatuses[workflowType].url = payload.run_url; + buildStatuses[workflowType].target = payload.target; + } else { + // Create new status entry for real-time updates + buildStatuses[workflowType] = { + name: payload.workflow_name, + status: payload.status === 'in_progress' ? 'in_progress' : + payload.status === 'success' ? 'completed' : + payload.status === 'failure' ? 'completed' : 'queued', + conclusion: payload.status === 'success' ? 'success' : + payload.status === 'failure' ? 'failure' : null, + url: payload.run_url, + runId: payload.run_id, + target: payload.target, + created_at: new Date().toISOString() + }; + } + } + console.log(`Collected ${allArtifacts.length} total artifacts from all builds`); // Build comment body with progressive status for individual builds let commentBody = `## 🔧 Build Status for PR #${pr.number}\n\n`; - commentBody += `🔗 **Commit**: [\`${pr.head.sha.substring(0, 7)}\`](https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${pr.head.sha})\n\n`; + commentBody += `🔗 **Commit**: [\`${targetCommitSha.substring(0, 7)}\`](https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${targetCommitSha})\n\n`; + + // Show event context for debugging (only for repository_dispatch) + if (context.eventName === 'repository_dispatch') { + const payload = context.payload.client_payload; + commentBody += `🔔 **Real-time Update**: ${payload.workflow_name} (${payload.target}) - ${payload.status}\n\n`; + } // Progressive build status and downloads table commentBody += `### 📦 Build Artifacts\n\n`; @@ -164,15 +209,24 @@ jobs: // Process each expected build target individually const buildTargets = [ - { name: 'Android Phone', platform: '🤖', device: '📱', pattern: /android.*phone/i }, - { name: 'Android TV', platform: '🤖', device: '📺', pattern: /android.*tv/i }, - { name: 'iOS Phone', platform: '🍎', device: '📱', pattern: /ios.*phone/i }, - { name: 'iOS TV', platform: '🍎', device: '📺', pattern: /ios.*tv/i } + { name: 'Android Phone', platform: '🤖', device: '📱', workflowType: 'Android', target: 'phone' }, + { name: 'Android TV', platform: '🤖', device: '📺', workflowType: 'Android', target: 'tv' }, + { name: 'iOS Phone', platform: '🍎', device: '📱', workflowType: 'iOS', target: 'phone' }, + { name: 'iOS TV', platform: '🍎', device: '📺', workflowType: 'iOS', target: 'tv' } ]; for (const target of buildTargets) { - // Find matching workflow status (using our simplified structure) - const matchingStatus = target.name.includes('Android') ? buildStatuses['Android'] : buildStatuses['iOS']; + // Find matching workflow status + let matchingStatus = buildStatuses[target.workflowType]; + + // For repository_dispatch events, check if this specific target matches + if (context.eventName === 'repository_dispatch' && matchingStatus) { + const payload = context.payload.client_payload; + if (payload.target !== target.target) { + // This update is for a different target, show default status + matchingStatus = null; + } + } // Find matching artifact const matchingArtifact = allArtifacts.find(artifact => diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index bc9b9ea1..d273f616 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -18,6 +18,7 @@ jobs: name: 🏗️ Build Android APK permissions: contents: read + repository-projects: write strategy: fail-fast: false @@ -25,6 +26,26 @@ jobs: target: [phone, tv] steps: + - name: 📢 Notify artifact comment workflow (started) + if: github.event_name == 'pull_request' + uses: actions/github-script@v8 + with: + script: | + await github.rest.repos.createDispatchEvent({ + owner: context.repo.owner, + repo: context.repo.repo, + event_type: 'build-started', + client_payload: { + workflow_name: 'Android APK Build', + target: '${{ matrix.target }}', + status: 'in_progress', + pr_number: ${{ github.event.pull_request.number }}, + commit_sha: '${{ github.event.pull_request.head.sha }}', + run_id: ${{ github.run_id }}, + run_url: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' + } + }); + - name: 📥 Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: @@ -91,3 +112,43 @@ jobs: path: | android/app/build/outputs/apk/release/*.apk retention-days: 7 + + - name: 🔔 Notify artifact comment workflow (success) + if: success() && github.event_name == 'pull_request' + uses: actions/github-script@v8 + with: + script: | + await github.rest.repos.createDispatchEvent({ + owner: context.repo.owner, + repo: context.repo.repo, + event_type: 'build-completed', + client_payload: { + workflow_name: 'Android APK Build', + target: '${{ matrix.target }}', + status: 'success', + pr_number: ${{ github.event.pull_request.number }}, + commit_sha: '${{ github.event.pull_request.head.sha }}', + run_id: ${{ github.run_id }}, + run_url: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' + } + }); + + - name: 🔔 Notify artifact comment workflow (failure) + if: failure() && github.event_name == 'pull_request' + uses: actions/github-script@v8 + with: + script: | + await github.rest.repos.createDispatchEvent({ + owner: context.repo.owner, + repo: context.repo.repo, + event_type: 'build-failed', + client_payload: { + workflow_name: 'Android APK Build', + target: '${{ matrix.target }}', + status: 'failure', + pr_number: ${{ github.event.pull_request.number }}, + commit_sha: '${{ github.event.pull_request.head.sha }}', + run_id: ${{ github.run_id }}, + run_url: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' + } + }); diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 98b587b3..27c5be6c 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -9,11 +9,11 @@ on: pull_request: branches: [develop, master] paths-ignore: - - '*.md' + - "*.md" push: branches: [develop, master] paths-ignore: - - '*.md' + - "*.md" jobs: build-ios: @@ -22,14 +22,35 @@ jobs: name: 🏗️ Build iOS IPA permissions: contents: read + repository-projects: write strategy: fail-fast: false matrix: target: [phone] -# target: [phone, tv] + # target: [phone, tv] steps: + - name: 📢 Notify artifact comment workflow (started) + if: github.event_name == 'pull_request' + uses: actions/github-script@v8 + with: + script: | + await github.rest.repos.createDispatchEvent({ + owner: context.repo.owner, + repo: context.repo.repo, + event_type: 'build-started', + client_payload: { + workflow_name: 'iOS IPA Build', + target: '${{ matrix.target }}', + status: 'in_progress', + pr_number: ${{ github.event.pull_request.number }}, + commit_sha: '${{ github.event.pull_request.head.sha }}', + run_id: ${{ github.run_id }}, + run_url: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' + } + }); + - name: 📥 Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: @@ -93,3 +114,43 @@ jobs: name: streamyfin-ios-${{ matrix.target }}-ipa-${{ env.DATE_TAG }} path: build-*.ipa retention-days: 7 + + - name: 🔔 Notify artifact comment workflow (success) + if: success() && github.event_name == 'pull_request' + uses: actions/github-script@v8 + with: + script: | + await github.rest.repos.createDispatchEvent({ + owner: context.repo.owner, + repo: context.repo.repo, + event_type: 'build-completed', + client_payload: { + workflow_name: 'iOS IPA Build', + target: '${{ matrix.target }}', + status: 'success', + pr_number: ${{ github.event.pull_request.number }}, + commit_sha: '${{ github.event.pull_request.head.sha }}', + run_id: ${{ github.run_id }}, + run_url: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' + } + }); + + - name: 🔔 Notify artifact comment workflow (failure) + if: failure() && github.event_name == 'pull_request' + uses: actions/github-script@v8 + with: + script: | + await github.rest.repos.createDispatchEvent({ + owner: context.repo.owner, + repo: context.repo.repo, + event_type: 'build-failed', + client_payload: { + workflow_name: 'iOS IPA Build', + target: '${{ matrix.target }}', + status: 'failure', + pr_number: ${{ github.event.pull_request.number }}, + commit_sha: '${{ github.event.pull_request.head.sha }}', + run_id: ${{ github.run_id }}, + run_url: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' + } + });