Files
streamyfin/.github/workflows/artifact-comment.yml
Uruk 0d1aeaf8aa fix(ci): improve workflow run condition and artifact collection
Fixes workflow trigger condition by explicitly checking for workflow_run event type to prevent unintended executions.

Improves artifact collection reliability by switching to the correct API method and increasing page size to capture more artifacts from multiple builds.

Removes redundant artifact fetching logic that was duplicating collection efforts.
2025-09-29 23:55:36 +02:00

210 lines
9.2 KiB
YAML

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
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 == 'workflow_run' && github.event.workflow_run.event == 'pull_request')
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 both workflow_run 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 === '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 from all completed successful builds
let allArtifacts = [];
let buildStatuses = {};
for (const run of buildRuns) {
buildStatuses[run.name] = {
status: run.status,
conclusion: run.conclusion,
url: run.html_url,
runId: run.id
};
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`);
// Sort and categorize all collected artifacts
const androidArtifacts = allArtifacts
.filter(a => a.name.includes('android'))
.sort((a, b) => a.name.localeCompare(b.name));
const iosArtifacts = allArtifacts
.filter(a => a.name.includes('ios'))
.sort((a, b) => a.name.localeCompare(b.name));
// Build comment body with progressive status
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`;
// Add build status table
commentBody += `### 🔧 Build Status\n\n`;
commentBody += `| Workflow | Status | Artifacts |\n`;
commentBody += `|----------|--------|-----------|\n`;
for (const [name, status] of Object.entries(buildStatuses)) {
const emoji = status.conclusion === 'success' ? '✅' :
status.conclusion === 'failure' ? '❌' :
status.status === 'in_progress' ? '🔄' : '⏳';
const statusText = status.conclusion || status.status || 'pending';
const artifactCount = allArtifacts.filter(a => {
// Match artifacts to workflows based on naming patterns
if (name.includes('Android')) return a.name.includes('android');
if (name.includes('iOS')) return a.name.includes('ios');
return false;
}).length;
commentBody += `| [${name}](${status.url}) | ${emoji} ${statusText} | ${artifactCount} |\n`;
}
commentBody += `\n`;
// Only show download table if there are artifacts
if (allArtifacts.length > 0) {
commentBody += `### 📦 Available Downloads (${allArtifacts.length} artifacts)\n\n`;
// Create table for better organization
commentBody += `| Platform | Device Type | Download Link |\n`;
commentBody += `|----------|-------------|---------------|\n`;
// Add Android artifacts
androidArtifacts.forEach(artifact => {
const isTV = artifact.name.includes('tv');
const deviceType = isTV ? '📺 Android TV' : '📱 Android Phone';
const nightlyLink = `https://nightly.link/${context.repo.owner}/${context.repo.repo}/actions/runs/${artifact.workflow_run.id}/${artifact.name}.zip`;
commentBody += `| 🤖 Android | ${deviceType} | [📥 Download APK](${nightlyLink}) |\n`;
});
// Add iOS artifacts
iosArtifacts.forEach(artifact => {
const isTV = artifact.name.includes('tv');
const deviceType = isTV ? '📺 Apple TV' : '📱 iPhone/iPad';
const nightlyLink = `https://nightly.link/${context.repo.owner}/${context.repo.repo}/actions/runs/${artifact.workflow_run.id}/${artifact.name}.zip`;
commentBody += `| 🍎 iOS | ${deviceType} | [📥 Download IPA](${nightlyLink}) |\n`;
});
commentBody += `\n`;
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 += `⏳ **No artifacts available yet** - builds are still in progress or haven't completed successfully.\n\n`;
}
commentBody += `<sub>*Auto-generated by [GitHub Actions](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${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}`);
}