Require bug/feature reports via issue forms with automated format checks, while accepting legacy Bug: titles from older app builds. Co-authored-by: Cursor <cursoragent@cursor.com>
140 lines
5.1 KiB
YAML
140 lines
5.1 KiB
YAML
name: issue-format
|
|
|
|
on:
|
|
issues:
|
|
types: [opened, edited]
|
|
|
|
permissions:
|
|
issues: write
|
|
|
|
jobs:
|
|
validate:
|
|
runs-on: ubuntu-latest
|
|
# Skip issues opened by bots (e.g. dependabot) and maintainers fixing format
|
|
if: >-
|
|
github.event.issue.user.type != 'Bot' &&
|
|
!contains(github.event.issue.labels.*.name, 'format-exempt')
|
|
steps:
|
|
- name: Validate title and body
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const issue = context.payload.issue;
|
|
const title = issue.title.trim();
|
|
const body = (issue.body || '').trim();
|
|
const errors = [];
|
|
|
|
const modernTitle = /^\[(Bug|Feature)\] .{8,}/.test(title);
|
|
const legacyAppTitle = /^Bug:\s*.{5,}/i.test(title);
|
|
if (!modernTitle && !legacyAppTitle) {
|
|
errors.push(
|
|
'Title must start with `[Bug]` or `[Feature]` followed by a short summary (at least 8 characters after the prefix). Legacy app links using `Bug: ...` are also accepted. Example: `[Bug] SFTP upload fails on Windows`'
|
|
);
|
|
}
|
|
|
|
if (body.length < 120) {
|
|
errors.push(
|
|
'Body is too short. Please use the Bug Report or Feature Request template and fill in all required fields.'
|
|
);
|
|
}
|
|
|
|
const templateMarkers = [
|
|
'Steps to reproduce',
|
|
'Expected behavior',
|
|
'Actual behavior',
|
|
'Describe the problem',
|
|
'Problem / pain point',
|
|
'Proposed solution',
|
|
'Operating system',
|
|
];
|
|
const hasTemplateStructure = templateMarkers.some((marker) =>
|
|
body.includes(marker)
|
|
);
|
|
if (!hasTemplateStructure) {
|
|
errors.push(
|
|
'Body does not look like it came from an issue template. Choose **Bug Report** or **Feature Request** when opening an issue.'
|
|
);
|
|
}
|
|
|
|
const labels = new Set(
|
|
(issue.labels || []).map((label) =>
|
|
typeof label === 'string' ? label : label.name
|
|
)
|
|
);
|
|
|
|
if (errors.length === 0) {
|
|
if (
|
|
issue.state === 'closed' &&
|
|
labels.has('invalid-format')
|
|
) {
|
|
labels.delete('invalid-format');
|
|
await github.rest.issues.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issue.number,
|
|
state: 'open',
|
|
labels: [...labels],
|
|
});
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issue.number,
|
|
body: '<!-- issue-format-bot --> Format looks good now. Reopening this issue.',
|
|
});
|
|
}
|
|
core.info('Issue format OK');
|
|
return;
|
|
}
|
|
|
|
const issueNumber = issue.number;
|
|
const marker = '<!-- issue-format-bot -->';
|
|
const bodyText = [
|
|
marker,
|
|
'## Issue format check failed',
|
|
'',
|
|
'This issue was closed automatically because it does not follow the required format.',
|
|
'',
|
|
...errors.map((e) => `- ${e}`),
|
|
'',
|
|
'### How to resubmit',
|
|
'',
|
|
'1. Go to [New Issue](https://github.com/binaricat/Netcatty/issues/new/choose)',
|
|
'2. Pick **Bug Report** or **Feature Request**',
|
|
'3. Fill in every required field',
|
|
'4. Keep the `[Bug]` or `[Feature]` prefix in the title and add a clear summary after it (older app versions may use `Bug: ...`)',
|
|
'',
|
|
'For questions and open-ended discussion, use [GitHub Discussions](https://github.com/binaricat/Netcatty/discussions) instead.',
|
|
'',
|
|
'If you believe this was a mistake, reply here after fixing the title/body and a maintainer can reopen.',
|
|
].join('\n');
|
|
|
|
const { data: comments } = await github.rest.issues.listComments({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issueNumber,
|
|
per_page: 100,
|
|
});
|
|
const alreadyNotified = comments.some((c) =>
|
|
(c.body || '').includes(marker)
|
|
);
|
|
|
|
if (!alreadyNotified) {
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issueNumber,
|
|
body: bodyText,
|
|
});
|
|
}
|
|
|
|
labels.add('invalid-format');
|
|
|
|
await github.rest.issues.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issueNumber,
|
|
state: 'closed',
|
|
state_reason: 'not_planned',
|
|
labels: [...labels],
|
|
});
|