Skip to content

Bump vite from 6.3.4 to 6.3.6 #38

Bump vite from 6.3.4 to 6.3.6

Bump vite from 6.3.4 to 6.3.6 #38

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.');