Skip to content

Commit 00480e9

Browse files
authored
feat: add PR description validation workflow (#3816)
* feat: add PR description validation workflow * fix: update PR description validation for additional info link
1 parent 111eb71 commit 00480e9

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
name: PR Description Validation
2+
3+
on:
4+
pull_request:
5+
types: [opened, edited, synchronize]
6+
7+
jobs:
8+
validate-pr-description:
9+
runs-on: ubuntu-latest
10+
name: Validate PR Description
11+
12+
steps:
13+
- name: Validate PR Description
14+
uses: actions/github-script@v7
15+
with:
16+
script: |
17+
const prBody = context.payload.pull_request.body || '';
18+
console.log('Validating PR body...');
19+
20+
// Define required sections from the PR template
21+
const requiredSections = [
22+
{
23+
name: 'Description',
24+
header: '## Description',
25+
placeholder: 'Enter description to help the reviewer understand what\'s the change about...'
26+
},
27+
{
28+
name: 'Changelog',
29+
header: '## Changelog',
30+
placeholder: 'Add a quick message for our users about this change'
31+
},
32+
{
33+
name: 'Additional info',
34+
header: '## Additional info',
35+
placeholder: 'If applicable, add additional info such as link to the bug being fixed by this PR'
36+
}
37+
];
38+
39+
const missingOrEmptySections = [];
40+
const completedSections = [];
41+
42+
// Validate each required section
43+
for (const section of requiredSections) {
44+
console.log(`Checking section: ${section.name}`);
45+
46+
// Check if section header exists
47+
if (!prBody.includes(section.header)) {
48+
missingOrEmptySections.push({
49+
name: section.name,
50+
issue: 'Section is missing completely'
51+
});
52+
continue;
53+
}
54+
55+
// Extract content after the section header
56+
const sectionRegex = new RegExp(
57+
`${section.header.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*(?:<!--[\\s\\S]*?-->)?\\s*([\\s\\S]*?)(?=##|$)`,
58+
'i'
59+
);
60+
61+
const match = prBody.match(sectionRegex);
62+
63+
if (!match) {
64+
missingOrEmptySections.push({
65+
name: section.name,
66+
issue: 'Section header found but no content detected'
67+
});
68+
continue;
69+
}
70+
71+
let content = match[1].trim();
72+
73+
// Remove HTML comments from content for validation
74+
content = content.replace(/<!--[\s\S]*?-->/g, '').trim();
75+
76+
// Check if section is empty or contains only template text
77+
if (!content || content.length === 0) {
78+
missingOrEmptySections.push({
79+
name: section.name,
80+
issue: 'Section is empty - please add content'
81+
});
82+
} else if (content.includes(section.placeholder)) {
83+
missingOrEmptySections.push({
84+
name: section.name,
85+
issue: 'Section contains template placeholder text - please replace with actual content'
86+
});
87+
} else {
88+
completedSections.push(section.name);
89+
console.log(`✅ ${section.name} section is properly filled`);
90+
}
91+
}
92+
93+
// Create feedback message
94+
let commentBody = '';
95+
let statusIcon = '';
96+
let statusTitle = '';
97+
98+
if (missingOrEmptySections.length === 0) {
99+
// All sections are complete
100+
statusIcon = '✅';
101+
statusTitle = 'PR Description Validation Passed';
102+
commentBody += `## ${statusIcon} ${statusTitle}\n\n`;
103+
commentBody += 'All required sections are properly filled out:\n\n';
104+
105+
completedSections.forEach(section => {
106+
commentBody += `- ✅ **${section}**\n`;
107+
});
108+
109+
commentBody += '\nYour PR is good for review! 🚀\n';
110+
111+
} else {
112+
// Some sections are missing or incomplete
113+
statusIcon = '❌';
114+
statusTitle = 'PR Description Validation Failed';
115+
commentBody += `## ${statusIcon} ${statusTitle}\n\n`;
116+
commentBody += 'The following required sections need attention:\n\n';
117+
118+
missingOrEmptySections.forEach(section => {
119+
commentBody += `- ❌ **${section.name}**: ${section.issue}\n`;
120+
});
121+
122+
if (completedSections.length > 0) {
123+
commentBody += '\n**Completed sections:**\n';
124+
completedSections.forEach(section => {
125+
commentBody += `- ✅ **${section}**\n`;
126+
});
127+
}
128+
129+
commentBody += '\n---\n\n';
130+
commentBody += '### Required Sections:\n';
131+
commentBody += '- [ ] **Description** - Explain what changes are being made and why\n';
132+
commentBody += '- [ ] **Changelog** - User-facing summary, this will show in the release notes (include component names, relevant props, and general purpose)\n';
133+
commentBody += '- [ ] **Additional info** - Links to related issues, Jira tickets\n\n';
134+
commentBody += 'Please update your PR description to include all required sections with meaningful content.\n';
135+
}
136+
137+
commentBody += '\n---\n';
138+
commentBody += '_This validation ensures all sections from the [PR template](/wix/react-native-ui-lib/blob/master/.github/pull_request_template.md) are properly filled._';
139+
140+
// Find existing validation comment to update it
141+
const { data: comments } = await github.rest.issues.listComments({
142+
owner: context.repo.owner,
143+
repo: context.repo.repo,
144+
issue_number: context.payload.pull_request.number,
145+
});
146+
147+
const botComment = comments.find(comment =>
148+
comment.user.type === 'Bot' &&
149+
comment.body.includes('PR Description Validation')
150+
);
151+
152+
if (botComment) {
153+
// Update existing comment
154+
await github.rest.issues.updateComment({
155+
owner: context.repo.owner,
156+
repo: context.repo.repo,
157+
comment_id: botComment.id,
158+
body: commentBody
159+
});
160+
console.log('Updated existing validation comment');
161+
} else {
162+
// Create new comment
163+
await github.rest.issues.createComment({
164+
owner: context.repo.owner,
165+
repo: context.repo.repo,
166+
issue_number: context.payload.pull_request.number,
167+
body: commentBody
168+
});
169+
console.log('Created new validation comment');
170+
}
171+
172+
// Set workflow status
173+
if (missingOrEmptySections.length > 0) {
174+
const failureMessage = `Missing or incomplete sections: ${missingOrEmptySections.map(s => s.name).join(', ')}`;
175+
core.setFailed(failureMessage);
176+
console.log(`❌ Validation failed: ${failureMessage}`);
177+
} else {
178+
console.log('✅ PR description validation passed - all sections complete!');
179+
}

0 commit comments

Comments
 (0)