on: workflow_call: jobs: pr-gate: runs-on: ubuntu-latest steps: - name: Fetch latest PR info id: pr if: github.event_name == 'pull_request' uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const pr = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number }); core.setOutput("labels", JSON.stringify(pr.data.labels.map(l => l.name))); core.setOutput("draft", pr.data.draft); core.setOutput("user", pr.data.user.login); - name: Block draft PR if: github.event_name == 'pull_request' && fromJson(steps.pr.outputs.draft) run: | echo "PR is draft. Blocking CI." exit 1 - name: Require run-ci label if: github.event_name == 'pull_request' run: | labels='${{ steps.pr.outputs.labels }}' echo "Labels: $labels" if [[ "${{ contains(fromJson(steps.pr.outputs.labels), 'run-ci') }}" == "false" ]]; then echo "Missing required label 'run-ci'." exit 1 fi - name: Enforce rate limit for low-permission actors if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const HOURS = 2; const owner = context.repo.owner; const repo = context.repo.repo; const eventName = context.eventName; const curRun = await github.rest.actions.getWorkflowRun({ owner, repo, run_id: context.runId }); const triggeringActor = curRun.data.triggering_actor?.login || context.actor; async function hasHighPermission(username) { try { const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username }); const perm = data.permission || 'none'; return perm === 'write' || perm === 'maintain' || perm === 'admin'; } catch (e) { if (e.status === 404 || e.status === 403) return false; throw e; } } if (await hasHighPermission(triggeringActor)) { core.info(`Triggering user '${triggeringActor}' has high permission. No rate limit applied.`); return; } const cutoff = new Date(Date.now() - HOURS * 60 * 60 * 1000); core.info(`Checking for workflow runs since ${cutoff.toISOString()} (last ${HOURS} hours) for event '${eventName}'.`); const { data } = await github.rest.actions.listWorkflowRuns({ owner, repo, workflow_id: 'pr-test.yml', event: eventName, per_page: 100, }); const runs = data.workflow_runs || []; const recentFound = runs.find((run) => { if (String(run.id) === String(context.runId)) return false; if (new Date(run.created_at) < cutoff) return false; return (run.actor?.login === triggeringActor) || (run.triggering_actor?.login === triggeringActor); }); if (recentFound) { core.setFailed( `User '${triggeringActor}' already triggered '${context.workflow}' via '${eventName}' at ${recentFound.created_at}. ` + `Please wait ${HOURS} hours before triggering again.` ); } else { core.info(`No recent runs detected within the last ${HOURS} hours; proceeding.`); }