Skip to content

Commit 378f8c4

Browse files
committed
feat: Add new workflows to improve PR review process
1 parent 313200b commit 378f8c4

3 files changed

Lines changed: 145 additions & 1 deletion

File tree

.github/workflows/pr-checklist.yml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: PR Checklist Validation
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, edited, reopened, ready_for_review]
6+
7+
jobs:
8+
validate-checklist:
9+
if: github.event.pull_request.draft == false
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Validate PR checklist and sections
13+
uses: actions/github-script@v7
14+
with:
15+
script: |
16+
const body = context.payload.pull_request.body || '';
17+
const errors = [];
18+
19+
// --- Check that all checklist items are checked ---
20+
const uncheckedPattern = /- \[ \]/g;
21+
const checkedPattern = /- \[x\]/gi;
22+
const uncheckedMatches = body.match(uncheckedPattern) || [];
23+
const checkedMatches = body.match(checkedPattern) || [];
24+
const totalCheckboxes = uncheckedMatches.length + checkedMatches.length;
25+
26+
if (totalCheckboxes > 0 && uncheckedMatches.length > 0) {
27+
errors.push(
28+
`${uncheckedMatches.length} checklist item(s) are unchecked. ` +
29+
'All checklist items must be checked before merging — ' +
30+
'including items that don\'t apply (check them and note why if needed).'
31+
);
32+
}
33+
34+
// --- Warn if "Automated Tests" section is empty ---
35+
const automatedTestsMatch = body.match(/### Automated Tests\s*\n([\s\S]*?)(?=###|$)/);
36+
const automatedTestsContent = (automatedTestsMatch?.[1] || '')
37+
.replace(/<!-[\s\S]*?->/g, '')
38+
.trim();
39+
40+
if (automatedTestsContent.length === 0) {
41+
core.warning(
42+
'The "Automated Tests" section is empty. Please describe the automated tests you added, ' +
43+
'or explain why automated tests are not needed for this change.'
44+
);
45+
}
46+
47+
// --- Warn if "Manual Tests" section is empty ---
48+
const manualTestsMatch = body.match(/### Manual Tests\s*\n([\s\S]*?)(?=###|$)/);
49+
const manualTestsContent = (manualTestsMatch?.[1] || '')
50+
.replace(/<!-[\s\S]*?->/g, '')
51+
.trim();
52+
53+
if (manualTestsContent.length === 0) {
54+
core.warning(
55+
'The "Manual Tests" section is empty. Please describe how you manually tested this change.'
56+
);
57+
}
58+
59+
// --- Warn if GH_LINK placeholder is still present ---
60+
const relatedIssuesMatch = body.match(/### Related Issues\s*\n([\s\S]*?)(?=###|$)/);
61+
const relatedIssuesContent = (relatedIssuesMatch?.[1] || '')
62+
.replace(/<!-[\s\S]*?->/g, '')
63+
.trim();
64+
65+
if (relatedIssuesContent === 'GH_LINK' || relatedIssuesContent.length === 0) {
66+
core.warning(
67+
'The "Related Issues" section still contains the GH_LINK placeholder or is empty. ' +
68+
'Please replace it with the actual GitHub issue link.'
69+
);
70+
}
71+
72+
// --- Fail if there are errors ---
73+
if (errors.length > 0) {
74+
const summary = '## PR Checklist Validation Failed\n\n' +
75+
errors.map((e) => `- ${e}`).join('\n') +
76+
'\n\nPlease complete the checklist and update the PR description.';
77+
78+
core.summary.addRaw(summary);
79+
await core.summary.write();
80+
core.setFailed(errors.join('\n'));
81+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: Require Tests
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, ready_for_review]
6+
7+
jobs:
8+
require-tests:
9+
if: github.event.pull_request.draft == false
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Check for test file changes
13+
uses: actions/github-script@v7
14+
with:
15+
script: |
16+
const {owner, repo} = context.repo;
17+
const pullNumber = context.payload.pull_request.number;
18+
19+
// Fetch all changed files (handles pagination for large PRs)
20+
const files = await github.paginate(
21+
github.rest.pulls.listFiles,
22+
{owner, repo, pull_number: pullNumber, per_page: 100}
23+
);
24+
25+
const changedFileNames = files.map((f) => f.filename);
26+
27+
// Identify source file changes in lib/ (excluding types, .d.ts, and mocks)
28+
const sourceFiles = changedFileNames.filter((f) => {
29+
if (!f.startsWith('lib/')) return false;
30+
if (!f.endsWith('.ts') && !f.endsWith('.tsx')) return false;
31+
if (f.endsWith('.d.ts')) return false;
32+
if (f.startsWith('lib/types/')) return false;
33+
if (f.includes('__mocks__')) return false;
34+
return true;
35+
});
36+
37+
// Identify test file changes in tests/
38+
const testFiles = changedFileNames.filter((f) => {
39+
if (!f.startsWith('tests/')) return false;
40+
if (!f.endsWith('.ts') && !f.endsWith('.tsx')) return false;
41+
return true;
42+
});
43+
44+
// If source files changed but no test files changed, fail
45+
if (sourceFiles.length > 0 && testFiles.length === 0) {
46+
const fileList = sourceFiles.map((f) => `- \`${f}\``).join('\n');
47+
48+
const summary = '## Tests Required\n\n' +
49+
'This PR modifies source files in `lib/` but does not include any test file changes in `tests/`.\n\n' +
50+
'**Changed source files:**\n' + fileList + '\n\n' +
51+
'Please add or update tests to cover these changes.';
52+
53+
core.summary.addRaw(summary);
54+
await core.summary.write();
55+
core.setFailed(
56+
`This PR modifies ${sourceFiles.length} source file(s) in lib/ but no test files were added or modified. ` +
57+
'Please add or update tests to cover the changes.'
58+
);
59+
} else if (sourceFiles.length > 0 && testFiles.length > 0) {
60+
core.info(`Source files changed: ${sourceFiles.length}, test files changed: ${testFiles.length}. Looks good!`);
61+
} else {
62+
core.info('No source files in lib/ were changed. Test requirement check passed.');
63+
}

.github/workflows/typecheck.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
types: [opened, synchronize]
66

77
jobs:
8-
lint:
8+
typecheck:
99
runs-on: ubuntu-latest
1010
steps:
1111
# v5.0.0

0 commit comments

Comments
 (0)