name: PR Build (Comment Triggered) on: issue_comment: types: [created] concurrency: group: pr-build-${{ github.event.issue.number }} cancel-in-progress: true permissions: issues: write pull-requests: write actions: read jobs: validate: runs-on: ubuntu-latest if: github.event.issue.pull_request != null outputs: build_windows: ${{ steps.parse.outputs.build_windows }} pr_ref: ${{ steps.parse.outputs.pr_ref }} pr_sha: ${{ steps.parse.outputs.pr_sha }} pr_number: ${{ steps.parse.outputs.pr_number }} steps: - name: Parse command and check permissions id: parse uses: actions/github-script@v7 with: script: | const comment = context.payload.comment.body.trim(); // Parse supported commands const commands = { '/build-windows': 'build_windows' }; const command = commands[comment]; if (!command) { console.log(`Comment "${comment}" is not a recognized build command, skipping.`); core.setOutput('build_windows', 'false'); return; } try { await github.rest.reactions.createForIssueComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: context.payload.comment.id, content: 'eyes' }); } catch (e) { console.log(`Could not add reaction: ${e}`); } // Fetch PR details const pr = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number }); // Verify PR is open if (pr.data.state !== 'open') { console.log('PR is not open, skipping.'); core.setOutput('build_windows', 'false'); return; } // Block fork PRs — fork code should not run with access to secrets const isFork = pr.data.head.repo.full_name !== context.repo.owner + '/' + context.repo.repo; if (isFork) { console.log(`PR is from fork ${pr.data.head.repo.full_name}, blocking build.`); await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: 'On-demand builds are not available for PRs from forks.' }); core.setOutput('build_windows', 'false'); return; } // Verify commenter has write access let permission = 'none'; try { const resp = await github.rest.repos.getCollaboratorPermissionLevel({ owner: context.repo.owner, repo: context.repo.repo, username: context.payload.comment.user.login }); permission = resp.data.permission; } catch (_) {} if (!['admin', 'write'].includes(permission)) { console.log(`User ${context.payload.comment.user.login} has '${permission}' permission — insufficient.`); await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: `@${context.payload.comment.user.login} Only collaborators with write access can trigger builds.` }); core.setOutput('build_windows', 'false'); return; } console.log(`User ${context.payload.comment.user.login} has '${permission}' permission — proceeding with ${command}.`); core.setOutput(command, 'true'); // Export PR details for downstream jobs core.setOutput('pr_ref', pr.data.head.ref); core.setOutput('pr_sha', pr.data.head.sha); core.setOutput('pr_number', String(pr.data.number)); build-frontend: needs: validate if: needs.validate.outputs.build_windows == 'true' uses: ./.github/workflows/build-frontend.yml with: ref: ${{ needs.validate.outputs.pr_ref }} secrets: inherit build-windows: needs: [validate, build-frontend] if: needs.validate.outputs.build_windows == 'true' uses: ./.github/workflows/build-windows-installer.yml with: ref: ${{ needs.validate.outputs.pr_ref }} secrets: inherit post-result: needs: [validate, build-windows] if: always() && needs.validate.outputs.build_windows == 'true' runs-on: ubuntu-latest steps: - name: Post result comment uses: actions/github-script@v7 with: script: | const buildResult = '${{ needs.build-windows.result }}'; const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; const prRef = '${{ needs.validate.outputs.pr_ref }}'; const prSha = '${{ needs.validate.outputs.pr_sha }}'; const shortSha = prSha.substring(0, 7); // Skip comment for skipped builds if (buildResult === 'skipped') { console.log('Build was skipped, no comment needed.'); return; } let body; if (buildResult === 'success') { body = [ `Windows installer build **succeeded** for \`${prRef}\` (\`${shortSha}\`).`, ``, `**Download:** open the [workflow run](${runUrl}), scroll to the **Artifacts** section at the bottom.`, `The artifact \`Cleanuparr-windows-installer\` is retained for 30 days.` ].join('\n'); } else if (buildResult === 'cancelled') { body = [ `Windows installer build was **cancelled** for \`${prRef}\` (\`${shortSha}\`).`, ``, `See the [workflow run](${runUrl}) for details.` ].join('\n'); } else { body = [ `Windows installer build **failed** for \`${prRef}\` (\`${shortSha}\`).`, ``, `See the [workflow run](${runUrl}) for details.` ].join('\n'); } await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: parseInt('${{ needs.validate.outputs.pr_number }}'), body });