Skip to content

Commit 5e41043

Browse files
emily8rownmeta-codesync[bot]
authored andcommitted
Add PR body validation to analyze-pr workflow (#55382)
Summary: Pull Request resolved: #55382 This adds a `validatePRBody.js` script that checks pull request descriptions for required sections as part of a new `analyze-pr.yml` workflow that will replace part of the Danger-pr workflow. The validation includes: - **Description length check**: Fails if the PR body is missing or less than 50 characters - **Summary section check**: Warns if the PR lacks a "## Summary" section - **Test plan check**: Warns if the PR lacks a "## Test Plan" section - **Changelog validation**: failing if missing or invalid Key behaviors: - Phabricator-sourced PRs (detected via "Differential Revision:" in body) skip summary, test plan, and changelog validation since these are enforced differently - Validation messages use GitHub's `[!WARNING]` and `[!CAUTION]` callout syntax for clear visual feedback - The workflow fails (via `core.setFailed`) when any required check fails - Messages are passed to `postPRComment` alongside API changes for consolidated PR comments This aims to mimic relevant parts of dangerfile.js Changelog: [Internal] Reviewed By: huntie Differential Revision: D91158803 fbshipit-source-id: 2304251d18f9fc8bf9a29e536fff2d979573bd86
1 parent 6a9d792 commit 5e41043

2 files changed

Lines changed: 139 additions & 0 deletions

File tree

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
*/
9+
10+
'use strict';
11+
12+
const changelogRegex =
13+
/\[\s?(ANDROID|GENERAL|IOS|INTERNAL)\s?\]\s?\[\s?(BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY)\s?\]\s*?-?\s*?(.*)/i;
14+
15+
const internalChangelogRegex = /\[\s?(INTERNAL)\s?\].*/gi;
16+
17+
function validateChangelog(commitMsg) {
18+
if (!commitMsg.toLowerCase().includes('changelog:')) {
19+
return 'missing';
20+
}
21+
const hasValidChangelog = changelogRegex.test(commitMsg);
22+
const hasValidInternalChangelog = internalChangelogRegex.test(commitMsg);
23+
24+
if (hasValidChangelog || hasValidInternalChangelog) {
25+
return 'valid';
26+
}
27+
28+
return 'invalid';
29+
}
30+
31+
function validatePRBody(prBody) {
32+
const body = prBody ?? '';
33+
const bodyLower = body.toLowerCase();
34+
const isFromPhabricator = bodyLower.includes('differential revision:');
35+
36+
const messages = [];
37+
let hasFail = false;
38+
39+
function addWarning(title, text) {
40+
messages.push(`> [!WARNING]
41+
> **${title}**
42+
>
43+
> ${text}`);
44+
}
45+
46+
function addFail(title, text) {
47+
hasFail = true;
48+
messages.push(`> [!CAUTION]
49+
> **${title}**
50+
>
51+
> ${text}`);
52+
}
53+
54+
if (!body || body.length < 50) {
55+
addFail('Missing Description', 'This pull request needs a description.');
56+
} else {
57+
const hasSummary =
58+
bodyLower.includes('## summary') || bodyLower.includes('summary:');
59+
if (!hasSummary && body.split('\n').length <= 2 && !isFromPhabricator) {
60+
addWarning(
61+
'Missing Summary',
62+
'Please add a "## Summary" section to your PR description. This is a good place to explain the motivation for making this change.',
63+
);
64+
}
65+
}
66+
67+
if (!isFromPhabricator) {
68+
const hasTestPlan = ['## test plan', 'test plan:', 'tests:', 'test:'].some(
69+
t => bodyLower.includes(t),
70+
);
71+
if (!hasTestPlan) {
72+
addWarning(
73+
'Missing Test Plan',
74+
'Please add a "## Test Plan" section to your PR description. A Test Plan lets us know how these changes were tested.',
75+
);
76+
}
77+
}
78+
79+
if (!isFromPhabricator) {
80+
const status = validateChangelog(body);
81+
const link =
82+
'https://reactnative.dev/contributing/changelogs-in-pull-requests';
83+
if (status === 'missing') {
84+
addFail(
85+
'Missing Changelog',
86+
`Please add a Changelog to your PR description. See [Changelog format](${link})`,
87+
);
88+
} else if (status === 'invalid') {
89+
addFail(
90+
'Invalid Changelog Format',
91+
`Please verify your Changelog format. See [Changelog format](${link})`,
92+
);
93+
}
94+
}
95+
96+
return {
97+
message: messages.join('\n\n'),
98+
status: hasFail ? 'FAIL' : 'PASS',
99+
};
100+
}
101+
102+
module.exports = validatePRBody;

.github/workflows/analyze-pr.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Analyze Pull Request
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, edited, reopened, synchronize]
6+
7+
permissions:
8+
pull-requests: write
9+
10+
jobs:
11+
analyze-pr:
12+
runs-on: ubuntu-latest
13+
if: github.repository == 'facebook/react-native'
14+
steps:
15+
- name: Check out main branch
16+
uses: actions/checkout@v6
17+
- name: Setup Node.js
18+
uses: ./.github/actions/setup-node
19+
- name: Run yarn install
20+
uses: ./.github/actions/yarn-install
21+
- name: Check PR body
22+
id: check-pr-body
23+
uses: actions/github-script@v8
24+
with:
25+
script: |
26+
const validatePRBody = require('./.github/workflow-scripts/validatePRBody.js');
27+
const {message, status} = validatePRBody(context.payload.pull_request.body);
28+
core.setOutput('message', message);
29+
core.setOutput('status', status);
30+
- name: Post PR comment
31+
uses: ./.github/actions/post-pr-comment
32+
with:
33+
marker: '<!-- analyze-pr -->'
34+
sections: '[${{ toJSON(steps.check-pr-body.outputs.message) }}]'
35+
- name: Fail if validation errors
36+
if: steps.check-pr-body.outputs.status == 'FAIL'
37+
run: exit 1

0 commit comments

Comments
 (0)