Files
streamyfin/.github/workflows/artifact-comment.yml
Uruk e985adf062 feat: improve build status UI with progressive updates
Restructures the artifact comment workflow to display build progress in real-time with individual platform/device status tracking.

Changes the status table from workflow-based to target-based (Android Phone/TV, iOS Phone/TV) with dedicated status indicators and download links that update as builds complete.

Improves user experience by showing pending builds with appropriate messaging instead of waiting for all builds to finish before displaying any information.
2025-09-30 00:22:51 +02:00

224 lines
9.9 KiB
YAML
Raw Blame History

name: 📝 Artifact Comment on PR
concurrency:
group: artifact-comment-${{ github.event.workflow_run.head_sha || 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)"
types:
- completed
- requested # Trigger when build starts
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')
name: 📦 Post Build Artifacts
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
actions: read
steps:
- name: 🔍 Get PR and Artifacts
uses: actions/github-script@v8
with:
script: |
// Handle workflow_run, pull_request, and manual dispatch events
let pr;
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 (pullRequests.length === 0) {
console.log('No pull request found for commit:', github.event.workflow_run.head_sha);
return;
}
pr = pullRequests[0];
} else if (context.eventName === 'pull_request') {
// Direct PR event
pr = context.payload.pull_request;
} else if (context.eventName === 'workflow_dispatch') {
// Get current PR for manual testing
const prNumber = context.payload.pull_request?.number || 1101;
const { data: prData } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
pr = prData;
} else {
console.log('Unsupported event type:', context.eventName);
return;
}
console.log(`Processing PR #${pr.number} for commit ${pr.head.sha.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,
per_page: 20
});
// Filter for build workflows only
const buildRuns = workflowRuns.workflow_runs.filter(run =>
run.name.includes('Android APK Build') ||
run.name.includes('iOS IPA Build')
);
console.log(`Found ${buildRuns.length} build workflow runs for this commit`);
// Collect artifacts and statuses from all builds (completed and in-progress)
let allArtifacts = [];
let buildStatuses = {};
// Define all expected build targets
const expectedBuilds = {
'Android Phone': { platform: 'Android', device: 'Phone', emoji: '📱', pattern: 'android.*phone' },
'Android TV': { platform: 'Android', device: 'TV', emoji: '📺', pattern: 'android.*tv' },
'iOS Phone': { platform: 'iOS', device: 'Phone', emoji: '📱', pattern: 'ios.*phone' },
'iOS TV': { platform: 'iOS', device: 'TV', emoji: '📺', pattern: 'ios.*tv' }
};
for (const run of buildRuns) {
buildStatuses[run.name] = {
status: run.status,
conclusion: run.conclusion,
url: run.html_url,
runId: run.id
};
// Collect artifacts from any completed successful builds
if (run.conclusion === 'success') {
try {
const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: run.id
});
allArtifacts.push(...artifacts.artifacts);
} catch (error) {
console.log(`Failed to get artifacts for run ${run.id}:`, error.message);
}
}
}
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`;
// Progressive build status and downloads table
commentBody += `### <20> Build Artifacts\n\n`;
commentBody += `| Platform | Device | Status | Download |\n`;
commentBody += `|----------|--------|--------|---------|\n`;
// 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: '<27>', pattern: /ios.*phone/i },
{ name: 'iOS TV', platform: '🍎', device: '📺', pattern: /ios.*tv/i }
];
for (const target of buildTargets) {
// Find matching workflow run
const matchingRun = buildRuns.find(run => {
return (run.name.includes('Android') && target.name.includes('Android')) ||
(run.name.includes('iOS') && target.name.includes('iOS'));
});
// Find matching artifact
const matchingArtifact = allArtifacts.find(artifact =>
target.pattern.test(artifact.name)
);
let status = '⏳ Pending';
let downloadLink = '*Waiting for build...*';
if (matchingRun) {
if (matchingRun.conclusion === 'success' && matchingArtifact) {
status = '✅ Complete';
const nightlyLink = `https://nightly.link/${context.repo.owner}/${context.repo.repo}/actions/runs/${matchingArtifact.workflow_run.id}/${matchingArtifact.name}.zip`;
const fileType = target.name.includes('Android') ? 'APK' : 'IPA';
downloadLink = `[📥 Download ${fileType}](${nightlyLink})`;
} else if (matchingRun.conclusion === 'failure') {
status = `❌ [Failed](${matchingRun.html_url})`;
downloadLink = '*Build failed*';
} else if (matchingRun.status === 'in_progress') {
status = `🔄 [Building...](${matchingRun.html_url})`;
downloadLink = '*Build in progress...*';
} else if (matchingRun.status === 'queued') {
status = `⏳ [Queued](${matchingRun.html_url})`;
downloadLink = '*Waiting to start...*';
}
}
commentBody += `| ${target.platform} ${target.name.split(' ')[0]} | ${target.device} ${target.name.split(' ')[1]} | ${status} | ${downloadLink} |\n`;
}
commentBody += `\n`;
// Show installation instructions if we have any artifacts
if (allArtifacts.length > 0) {
commentBody += `### 🔧 Installation Instructions\n\n`;
commentBody += `- **Android APK**: Download and install directly on your device (enable "Install from unknown sources")\n`;
commentBody += `- **iOS IPA**: Install using [AltStore](https://altstore.io/), [Sideloadly](https://sideloadly.io/), or Xcode\n\n`;
commentBody += `> ⚠️ **Note**: Artifacts expire in 7 days from build date\n\n`;
} else {
commentBody += `⏳ **Builds are starting up...** This comment will update automatically as each build completes.\n\n`;
}
commentBody += `<sub>*Auto-generated by [GitHub Actions](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})*</sub>`;
commentBody += `\n<!-- streamyfin-artifact-comment -->`;
// Find existing bot comment to update
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('<!-- streamyfin-artifact-comment -->')
);
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: commentBody
});
console.log(`✅ Updated comment ${botComment.id} on PR #${pr.number}`);
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: commentBody
});
console.log(`✅ Created new comment on PR #${pr.number}`);
}