Bump vite from 6.3.4 to 6.3.6 #38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Require dedicated contributor approval | |
on: | |
# Use pull_request_target so secrets are available for forks; do NOT checkout PR code in this job | |
pull_request_target: | |
types: [opened, reopened, synchronize, ready_for_review] | |
# Keep review events for same-repo PRs; for forks, the target run above enforces the rule | |
pull_request_review: | |
types: [submitted, dismissed] | |
permissions: | |
pull-requests: read | |
jobs: | |
check-approvals: | |
name: check-approvals | |
runs-on: ubuntu-latest | |
env: | |
TEAM_MEMBERS_TOKEN: ${{ secrets.TEAM_MEMBERS_TOKEN }} | |
steps: | |
- name: Validate approvals from required users | |
uses: actions/github-script@v7 | |
with: | |
# Use default token for PR operations | |
github-token: ${{ github.token }} | |
script: | | |
const pr = context.payload.pull_request; | |
if (!pr) { | |
core.info('No pull_request payload; skipping.'); | |
return; | |
} | |
if (pr.draft) { | |
core.info('Draft PR detected; skipping approval enforcement.'); | |
return; | |
} | |
const prAuthor = pr.user && pr.user.login ? pr.user.login.toLowerCase() : undefined; | |
// Hardcoded team configuration | |
const teamOrg = 'stevenson-space'; | |
const teamSlug = 'dedicated-contributors'; | |
let teamMembers = []; | |
try { | |
const teamToken = process.env.TEAM_MEMBERS_TOKEN; | |
if (!teamToken) { | |
core.setFailed('TEAM_MEMBERS_TOKEN is not set. Add a repo secret with a PAT that has read:org.'); | |
return; | |
} | |
let page = 1; | |
while (true) { | |
const url = `https://api.github.com/orgs/${teamOrg}/teams/${teamSlug}/members?per_page=100&page=${page}`; | |
const res = await fetch(url, { | |
headers: { | |
'Authorization': `Bearer ${teamToken}`, | |
'Accept': 'application/vnd.github+json', | |
'X-GitHub-Api-Version': '2022-11-28', | |
'User-Agent': 'actions/github-script' | |
} | |
}); | |
if (!res.ok) { | |
const text = await res.text(); | |
core.setFailed(`Failed to list team members (${teamOrg}/${teamSlug}): HTTP ${res.status}. ${text}`); | |
return; | |
} | |
const data = await res.json(); | |
teamMembers = teamMembers.concat((data || []).map(u => (u.login || '').toLowerCase()).filter(Boolean)); | |
if (!Array.isArray(data) || data.length < 100) break; | |
page += 1; | |
if (page > 20) break; // safety cap | |
} | |
} catch (e) { | |
core.setFailed(`Unable to list team members for ${teamOrg}/${teamSlug}. ${e && e.message ? e.message : e}`); | |
return; | |
} | |
const eligible = [...new Set(teamMembers.filter(u => u !== prAuthor))]; | |
if (eligible.length === 0) { | |
core.setFailed('No eligible approvers in the team after excluding the PR author.'); | |
return; | |
} | |
const required = eligible; | |
const { owner, repo } = context.repo; | |
const pull_number = pr.number; | |
const headSha = pr.head && pr.head.sha ? pr.head.sha : undefined; | |
if (!headSha) { | |
core.setFailed('Unable to determine PR head SHA.'); | |
return; | |
} | |
// Fetch all reviews (paginate if necessary) | |
let allReviews = []; | |
let page = 1; | |
while (true) { | |
const { data } = await github.rest.pulls.listReviews({ owner, repo, pull_number, per_page: 100, page }); | |
allReviews = allReviews.concat(data || []); | |
if (!data || data.length < 100) break; | |
page += 1; | |
if (page > 10) break; // safety cap | |
} | |
// Keep the latest review per user | |
const latestByUser = new Map(); | |
for (const r of allReviews) { | |
const username = (r.user && r.user.login ? r.user.login : '').toLowerCase(); | |
if (!username) continue; | |
const prev = latestByUser.get(username); | |
const currentTime = r.submitted_at ? new Date(r.submitted_at).getTime() : 0; | |
const prevTime = prev && prev.submitted_at ? new Date(prev.submitted_at).getTime() : 0; | |
if (!prev || currentTime >= prevTime) { | |
latestByUser.set(username, r); | |
} | |
} | |
// Evaluate approvals from the designated approvers on the current head SHA | |
const approvedBy = []; | |
for (const username of required) { | |
const review = latestByUser.get(username); | |
if (review && review.state === 'APPROVED' && review.commit_id === headSha) { | |
approvedBy.push(username); | |
} | |
} | |
const requiredCount = Math.floor(required.length / 2) + 1; | |
core.info(`PR author: ${prAuthor || 'unknown'}`); | |
core.info(`Team scope: ${teamOrg}/${teamSlug}`); | |
core.info(`Eligible approvers (author excluded): ${required.join(', ')}`); | |
core.info(`Approved by (latest head): ${approvedBy.join(', ') || 'none'}`); | |
core.info(`Approvals required: ${requiredCount} (policy: majority)`); | |
if (approvedBy.length < requiredCount) { | |
const remaining = requiredCount - approvedBy.length; | |
const stillEligible = required.filter(u => !approvedBy.includes(u)); | |
core.setFailed(`Insufficient approvals: ${approvedBy.length}/${requiredCount}. Need ${remaining} more from: ${stillEligible.join(', ')}`); | |
return; | |
} | |
core.info('All required approvers have approved the latest commit.'); |