mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-02 02:22:51 +01:00
Merges separate Android and iOS build workflows into a single "Build Apps" workflow to reduce duplication and simplify maintenance. Updates the artifact comment workflow to handle both the new unified workflow and legacy separate workflows for backward compatibility during transition. Removes redundant workflow files while preserving all existing functionality.
294 lines
14 KiB
YAML
294 lines
14 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
|
|
pull_request: # Show in PR checks and provide status updates
|
|
types: [opened, synchronize, reopened]
|
|
workflow_run: # Triggered when build workflows complete
|
|
workflows:
|
|
- "🏗️ Build Apps"
|
|
types:
|
|
- completed
|
|
|
|
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 repository_dispatch, pull_request, and manual dispatch events
|
|
let pr;
|
|
let targetCommitSha;
|
|
|
|
if (context.eventName === 'workflow_run') {
|
|
// Find PR associated with this workflow run commit
|
|
console.log('Workflow run event:', context.payload.workflow_run.name);
|
|
|
|
const { data: pullRequests } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
commit_sha: context.payload.workflow_run.head_sha
|
|
});
|
|
|
|
if (pullRequests.length === 0) {
|
|
console.log('No pull request found for commit:', context.payload.workflow_run.head_sha);
|
|
return;
|
|
}
|
|
|
|
pr = pullRequests[0];
|
|
targetCommitSha = context.payload.workflow_run.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') {
|
|
// For manual testing, try to find PR for current branch/commit
|
|
console.log('Manual workflow dispatch triggered');
|
|
|
|
// First, try to find PRs associated with current commit
|
|
try {
|
|
const { data: pullRequests } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
commit_sha: context.sha
|
|
});
|
|
|
|
if (pullRequests.length > 0) {
|
|
pr = pullRequests[0];
|
|
targetCommitSha = pr.head.sha;
|
|
console.log(`Found PR #${pr.number} for commit ${context.sha.substring(0, 7)}`);
|
|
} else {
|
|
// Fallback: get latest open PR
|
|
const { data: openPRs } = await github.rest.pulls.list({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
state: 'open',
|
|
sort: 'updated',
|
|
direction: 'desc',
|
|
per_page: 1
|
|
});
|
|
|
|
if (openPRs.length > 0) {
|
|
pr = openPRs[0];
|
|
targetCommitSha = pr.head.sha;
|
|
console.log(`Using latest open PR #${pr.number} for manual testing`);
|
|
} else {
|
|
console.log('No open PRs found for manual testing');
|
|
return;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log('Error finding PR for manual testing:', error.message);
|
|
return;
|
|
}
|
|
|
|
} 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('Build Apps') ||
|
|
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 the unified apps workflow
|
|
const latestAppsRun = buildRuns.find(run => run.name.includes('Build Apps'));
|
|
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 unified workflow (preferred) or fallback to separate workflows
|
|
const appsWorkflowRun = latestAppsRun || latestAndroidRun;
|
|
if (appsWorkflowRun) {
|
|
buildStatuses['Android'] = {
|
|
name: appsWorkflowRun.name,
|
|
status: appsWorkflowRun.status,
|
|
conclusion: appsWorkflowRun.conclusion,
|
|
url: appsWorkflowRun.html_url,
|
|
runId: appsWorkflowRun.id,
|
|
created_at: appsWorkflowRun.created_at
|
|
};
|
|
|
|
// Collect artifacts if completed successfully
|
|
if (appsWorkflowRun.conclusion === 'success') {
|
|
try {
|
|
const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
run_id: appsWorkflowRun.id
|
|
});
|
|
allArtifacts.push(...artifacts.artifacts);
|
|
} catch (error) {
|
|
console.log(`Failed to get apps artifacts for run ${appsWorkflowRun.id}:`, error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
// For iOS, use the same workflow run (since it's all in one now) or fallback to separate
|
|
const iosWorkflowRun = latestAppsRun || latestIOSRun;
|
|
if (iosWorkflowRun) {
|
|
buildStatuses['iOS'] = {
|
|
name: iosWorkflowRun.name,
|
|
status: iosWorkflowRun.status,
|
|
conclusion: iosWorkflowRun.conclusion,
|
|
url: iosWorkflowRun.html_url,
|
|
runId: iosWorkflowRun.id,
|
|
created_at: iosWorkflowRun.created_at
|
|
};
|
|
|
|
// Only collect artifacts if not already collected from apps workflow
|
|
if (!latestAppsRun && iosWorkflowRun.conclusion === 'success') {
|
|
try {
|
|
const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
run_id: iosWorkflowRun.id
|
|
});
|
|
allArtifacts.push(...artifacts.artifacts);
|
|
} catch (error) {
|
|
console.log(`Failed to get iOS artifacts for run ${iosWorkflowRun.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**: [\`${targetCommitSha.substring(0, 7)}\`](https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${targetCommitSha})\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
|
|
const matchingStatus = buildStatuses[target.workflowType];
|
|
|
|
// 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}`);
|
|
}
|