返回 CodeWhale
issue-gate.yml
根目录 / .github / workflows / issue-gate.yml
1 name: Contribution intake - issues
2
3 on:
4 issues:
5 types: [opened, reopened]
6
7 permissions:
8 contents: read
9 issues: write
10
11 jobs:
12 gate:
13 runs-on: ubuntu-latest
14 timeout-minutes: 10
15 steps:
16 # Labels only, never comments (founder, 2026-09-22): the intake note used
17 # to thank other bots, and a comment is noise a label does not make.
18 # Maintainers still see who needs triage; `/lgtmi` still skips it.
19 - name: Label new external issues for triage
20 uses: actions/github-script@v9
21 with:
22 script: |
23 const issue = context.payload.issue;
24 const owner = context.repo.owner;
25 const repo = context.repo.repo;
26 const privileged = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
27
28 async function label(name, color, description) {
29 try {
30 await github.rest.issues.createLabel({ owner, repo, name, color, description });
31 } catch (error) {
32 if (error.status !== 422) throw error; // 422: label already exists
33 }
34 await github.rest.issues.addLabels({
35 owner,
36 repo,
37 issue_number: issue.number,
38 labels: [name],
39 });
40 }
41
42 if (privileged.has(issue.author_association)) return;
43 // `user.type` is set by GitHub for app and bot accounts and cannot
44 // be spoofed by a login that merely ends in "[bot]".
45 if (issue.user.type === 'Bot') {
46 await label('bot-authored', 'ededed', 'Opened by a bot or app account');
47 return;
48 }
49
50 function parseAllowlist(content) {
51 return new Set(
52 content
53 .split(/\r?\n/)
54 .map(line => line.replace(/#.*/, '').trim().toLowerCase())
55 .filter(Boolean)
56 );
57 }
58
59 async function readAllowlist() {
60 try {
61 const { data } = await github.rest.repos.getContent({
62 owner,
63 repo,
64 path: '.github/APPROVED_CONTRIBUTORS',
65 ref: context.payload.repository.default_branch,
66 });
67 if (Array.isArray(data) || data.type !== 'file') return new Set();
68 return parseAllowlist(
69 Buffer.from(data.content, data.encoding || 'base64').toString('utf8')
70 );
71 } catch (error) {
72 if (error.status === 404) return new Set();
73 throw error;
74 }
75 }
76
77 const allowlist = await readAllowlist();
78 const login = issue.user.login.toLowerCase();
79 if (
80 allowlist.has(`all:${login}`) ||
81 allowlist.has(`issue:${login}`)
82 ) {
83 return;
84 }
85
86 await label(
87 'needs-triage',
88 'fbca04',
89 'New external report awaiting maintainer triage; repro, logs and version output help'
90 );
91
91 lines YAML