mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-24 23:00:29 +01:00
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.
310 lines
14 KiB
YAML
310 lines
14 KiB
YAML
name: 📝 Artifact Comment on PR
|
|
|
|
concurrency:
|
|
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]
|
|
repository_dispatch: # Triggered by build workflows when they start/complete
|
|
types:
|
|
- build-started
|
|
- build-completed
|
|
- build-failed
|
|
|
|
jobs:
|
|
comment-artifacts:
|
|
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 repository_dispatch, pull_request, and manual dispatch events
|
|
let pr;
|
|
let targetCommitSha;
|
|
|
|
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 (!payload || !payload.pr_number) {
|
|
console.log('No PR information in repository_dispatch payload');
|
|
return;
|
|
}
|
|
|
|
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
|
|
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;
|
|
targetCommitSha = pr.head.sha;
|
|
|
|
} else {
|
|
console.log('Unsupported event type:', context.eventName);
|
|
return;
|
|
}
|
|
|
|
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: targetCommitSha,
|
|
per_page: 30
|
|
});
|
|
|
|
// Filter for build workflows only and sort by creation time (most recent first)
|
|
const buildRuns = workflowRuns.workflow_runs
|
|
.filter(run =>
|
|
run.name.includes('Android APK Build') ||
|
|
run.name.includes('iOS IPA Build')
|
|
)
|
|
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
|
|
|
console.log(`Found ${buildRuns.length} build workflow runs for this commit`);
|
|
|
|
// Log current status of each build for debugging
|
|
buildRuns.forEach(run => {
|
|
console.log(`- ${run.name}: ${run.status} (${run.conclusion || 'no conclusion yet'}) - Created: ${run.created_at}`);
|
|
});
|
|
|
|
// Collect artifacts and statuses from builds - get most recent run for each workflow type
|
|
let allArtifacts = [];
|
|
let buildStatuses = {};
|
|
|
|
// Get the most recent run for each workflow type
|
|
const latestAndroidRun = buildRuns.find(run => run.name.includes('Android APK Build'));
|
|
const latestIOSRun = buildRuns.find(run => run.name.includes('iOS IPA Build'));
|
|
|
|
// Store status for each workflow type
|
|
if (latestAndroidRun) {
|
|
buildStatuses['Android'] = {
|
|
name: latestAndroidRun.name,
|
|
status: latestAndroidRun.status,
|
|
conclusion: latestAndroidRun.conclusion,
|
|
url: latestAndroidRun.html_url,
|
|
runId: latestAndroidRun.id,
|
|
created_at: latestAndroidRun.created_at
|
|
};
|
|
|
|
// Collect artifacts if completed successfully
|
|
if (latestAndroidRun.conclusion === 'success') {
|
|
try {
|
|
const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
run_id: latestAndroidRun.id
|
|
});
|
|
allArtifacts.push(...artifacts.artifacts);
|
|
} catch (error) {
|
|
console.log(`Failed to get Android artifacts for run ${latestAndroidRun.id}:`, error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (latestIOSRun) {
|
|
buildStatuses['iOS'] = {
|
|
name: latestIOSRun.name,
|
|
status: latestIOSRun.status,
|
|
conclusion: latestIOSRun.conclusion,
|
|
url: latestIOSRun.html_url,
|
|
runId: latestIOSRun.id,
|
|
created_at: latestIOSRun.created_at
|
|
};
|
|
|
|
// Collect artifacts if completed successfully
|
|
if (latestIOSRun.conclusion === 'success') {
|
|
try {
|
|
const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
run_id: latestIOSRun.id
|
|
});
|
|
allArtifacts.push(...artifacts.artifacts);
|
|
} catch (error) {
|
|
console.log(`Failed to get iOS artifacts for run ${latestIOSRun.id}:`, error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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**: [\`${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`;
|
|
commentBody += `| Platform | Device | Status | Download |\n`;
|
|
commentBody += `|----------|--------|--------|---------|\n`;
|
|
|
|
// Process each expected build target individually
|
|
const buildTargets = [
|
|
{ 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
|
|
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 =>
|
|
target.pattern.test(artifact.name)
|
|
);
|
|
|
|
let status = '⏳ Pending';
|
|
let downloadLink = '*Waiting for build...*';
|
|
|
|
if (matchingStatus) {
|
|
if (matchingStatus.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 (matchingStatus.conclusion === 'failure') {
|
|
status = `❌ [Failed](${matchingStatus.url})`;
|
|
downloadLink = '*Build failed*';
|
|
} else if (matchingStatus.status === 'in_progress') {
|
|
status = `🔄 [Building...](${matchingStatus.url})`;
|
|
downloadLink = '*Build in progress...*';
|
|
} else if (matchingStatus.status === 'queued') {
|
|
status = `⏳ [Queued](${matchingStatus.url})`;
|
|
downloadLink = '*Waiting to start...*';
|
|
} else {
|
|
// Show any other status with timestamp for debugging
|
|
status = `🔄 [${matchingStatus.status}](${matchingStatus.url})`;
|
|
downloadLink = `*Status: ${matchingStatus.status}*`;
|
|
}
|
|
}
|
|
|
|
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}`);
|
|
}
|