-
Notifications
You must be signed in to change notification settings - Fork 0
165 lines (144 loc) · 7.43 KB
/
issue-precheck.yml
File metadata and controls
165 lines (144 loc) · 7.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
name: Issue Pre-check
on:
issues:
types: [opened, reopened]
permissions:
issues: write
jobs:
precheck:
# Only run on real issues, not PRs converted to issues
if: github.event.issue.pull_request == null
runs-on: ubuntu-latest
steps:
- name: Validate and label issue
uses: actions/github-script@v7
with:
script: |
const body = context.payload.issue.body ?? '';
const labels = [];
const problems = [];
// ── Helpers ────────────────────────────────────────────────────────
/** Extract the content of a ### Section from the issue body. */
function section(heading) {
const re = new RegExp(
`###\\s+${heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\n([\\s\\S]*?)(?=\\n###|$)`,
'i'
);
const m = body.match(re);
return m ? m[1].trim() : '';
}
const PLACEHOLDER_RE = [
/^e\.g\./i,
/\/\/\s*paste your minimal repro here/i,
/\/\/\s*appsettings\.json snippet/i,
/\/\/\s*client code or test/i,
];
function isPlaceholder(text) {
return PLACEHOLDER_RE.some(re => re.test(text));
}
// ── Only validate bug reports (they have a "Minimal reproduction" section) ──
const isBugReport = body.includes('### Minimal reproduction');
if (!isBugReport) {
// Feature requests get a simple triage label
await github.rest.issues.addLabels({
...context.repo,
issue_number: context.issue.number,
labels: ['triage'],
});
return;
}
// ── Field checks ───────────────────────────────────────────────────
const version = section('BLite.Server version');
if (!version || isPlaceholder(version)) {
problems.push('**BLite.Server version** — missing or still contains the placeholder (e.g. `1.2.3`).');
}
const dotnet = section('.NET version');
if (!dotnet || isPlaceholder(dotnet)) {
problems.push('**\\.NET version** — missing or still contains the placeholder.');
}
const os = section('Operating system');
if (!os || isPlaceholder(os)) {
problems.push('**Operating system** — missing or still contains the placeholder.');
}
const description = section('Description');
if (!description || description.length < 15) {
problems.push('**Description** — too short or missing.');
}
const repro = section('Minimal reproduction');
if (!repro || repro.length < 30 || isPlaceholder(repro)) {
problems.push('**Minimal reproduction** — missing or appears to be just the placeholder. Please provide a small, self-contained snippet (config + client code or curl request).');
}
const expected = section('Expected behavior');
if (!expected || expected.length < 10) {
problems.push('**Expected behavior** — missing or too short.');
}
const actual = section('Actual behavior');
if (!actual || actual.length < 10) {
problems.push('**Actual behavior** — missing or too short.');
}
// ── Auto-label by Affected area ────────────────────────────────────
const area = section('Affected area').toLowerCase();
const areaMap = {
'grpc': 'area/grpc',
'rest api': 'area/rest',
'blazor studio': 'area/studio',
'authentication': 'area/auth',
'transaction': 'area/transactions',
'configuration': 'area/config',
};
for (const [key, label] of Object.entries(areaMap)) {
if (area.includes(key)) { labels.push(label); break; }
}
// ── Status label ───────────────────────────────────────────────────
labels.push(problems.length > 0 ? 'incomplete' : 'triage');
// ── Create labels (best-effort, ignore if they already exist) ──────
const labelDefs = {
'incomplete': { color: 'e11d48', description: 'Issue is missing required information' },
'triage': { color: '0ea5e9', description: 'Needs triage by a maintainer' },
'area/grpc': { color: '7c3aed', description: 'Affects the gRPC layer' },
'area/rest': { color: '7c3aed', description: 'Affects the REST API' },
'area/studio': { color: '7c3aed', description: 'Affects the Blazor Studio' },
'area/auth': { color: '7c3aed', description: 'Affects authentication or authorization' },
'area/transactions': { color: '7c3aed', description: 'Affects the transaction manager' },
'area/config': { color: '7c3aed', description: 'Affects configuration or startup' },
};
for (const label of labels) {
if (labelDefs[label]) {
try {
await github.rest.issues.createLabel({
...context.repo,
name: label,
...labelDefs[label],
});
} catch (_) { /* already exists */ }
}
}
await github.rest.issues.addLabels({
...context.repo,
issue_number: context.issue.number,
labels,
});
// ── Comment ────────────────────────────────────────────────────────
if (problems.length > 0) {
const list = problems.map(p => `- ${p}`).join('\n');
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body: [
':wave: Thanks for opening this issue!',
'',
'It looks like some required information is missing or incomplete:',
'',
list,
'',
'Please **edit the issue** and fill in all the missing sections.',
'We will start the investigation once the issue is complete.',
].join('\n'),
});
} else {
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body: ':wave: Thanks for the well-formed bug report! It has been added to the triage queue.',
});
}