Files
wehub-resource-sync d68f003000
CI / Change detection (push) Has been cancelled
CI / Version drift (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Workflow RLM cache (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / npm wrapper smoke (push) Has been cancelled
CI / Mobile runtime smoke (push) Has been cancelled
CI / Workflow lint (push) Has been cancelled
CI / Documentation (push) Has been cancelled
Web Frontend / Lint & Type Check (push) Failing after 1s
Auto-close harvested PRs / close (push) Failing after 1s
cargo-deny / cargo-deny (bans licenses sources) (push) Failing after 1s
Security audit / cargo-audit (push) Failing after 1s
Sync to CNB / sync (push) Failing after 1s
cargo-deny / cargo-deny (advisories) (push) Failing after 3s
Web Frontend / Deploy to Cloudflare (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:08:23 +08:00

111 lines
4.2 KiB
YAML

name: Contribution gate - pull requests
on:
pull_request_target:
types: [opened, reopened]
permissions:
contents: read
issues: write
pull-requests: write
env:
# Keep new gates observable first. Switch to "enforce" only after maintainers
# have seeded active contributors and reviewed the dry-run signal.
CONTRIBUTION_GATE_MODE: dry-run
jobs:
gate:
runs-on: ubuntu-latest
steps:
- name: Gate unapproved external pull requests
uses: actions/github-script@v9
with:
script: |
const pr = context.payload.pull_request;
const owner = context.repo.owner;
const repo = context.repo.repo;
const privileged = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
const gateMode = (process.env.CONTRIBUTION_GATE_MODE || 'dry-run').trim().toLowerCase();
const enforceGate = gateMode === 'enforce';
if (!['dry-run', 'enforce'].includes(gateMode)) {
core.warning(`Unknown CONTRIBUTION_GATE_MODE "${gateMode}"; defaulting to dry-run.`);
}
if (privileged.has(pr.author_association)) return;
if (pr.user.login === 'github-actions[bot]') return;
function parseAllowlist(content) {
return new Set(
content
.split(/\r?\n/)
.map(line => line.replace(/#.*/, '').trim().toLowerCase())
.filter(Boolean)
);
}
async function readAllowlist() {
try {
const { data } = await github.rest.repos.getContent({
owner,
repo,
path: '.github/APPROVED_CONTRIBUTORS',
ref: context.payload.repository.default_branch,
});
if (Array.isArray(data) || data.type !== 'file') return new Set();
return parseAllowlist(
Buffer.from(data.content, data.encoding || 'base64').toString('utf8')
);
} catch (error) {
if (error.status === 404) return new Set();
throw error;
}
}
const allowlist = await readAllowlist();
const login = pr.user.login.toLowerCase();
if (
allowlist.has(`all:${login}`) ||
allowlist.has(`pr:${login}`)
) {
return;
}
const gateMessage = enforceGate
? 'This repository currently limits automated PR intake to contributors listed in `.github/APPROVED_CONTRIBUTORS`. This is a maintainer-safety control for code review and CI load, not a judgment on the contribution. A maintainer can grant recurring PR access with `/lgtm` after review; once the generated allowlist PR is merged, this pull request can be reopened or resubmitted.'
: 'This repository is observing a maintainer-managed PR intake gate in dry-run mode, so this pull request is staying open. This note helps maintainers prepare the allowlist before any enforcement is considered.';
const marker = '<!-- codewhale-pr-gate -->';
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number: pr.number,
per_page: 100,
});
const alreadyNoted = comments.some(comment => (comment.body || '').includes(marker));
if (!alreadyNoted) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: pr.number,
body: [
marker,
`Thanks @${pr.user.login} for taking the time to contribute.`,
'',
gateMessage,
'',
'Please read `CONTRIBUTING.md` for the expected contribution shape. A maintainer can grant recurring PR access by commenting `/lgtm` on a pull request.',
].join('\n'),
});
}
if (!enforceGate) return;
await github.rest.pulls.update({
owner,
repo,
pull_number: pr.number,
state: 'closed',
});