Skip to content

Commit 49b8bf0

Browse files
authored
feat: Add scripts to auto-review PRs (#1015)
* feat: Add scripts to auto-review PRs * Fix Action file
1 parent ecf7302 commit 49b8bf0

File tree

7 files changed

+1605
-12
lines changed

7 files changed

+1605
-12
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module.exports = {
33
browser: true,
44
commonjs: true,
55
es2021: true,
6+
node: true,
67
},
78
extends: 'eslint:recommended',
89
parserOptions: {
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
name: Copilot PR Review & Auto-Fix
2+
3+
on:
4+
pull_request_target:
5+
paths:
6+
- resources/**
7+
- db/**
8+
- README.md
9+
types: [opened, synchronize]
10+
11+
permissions:
12+
contents: write
13+
pull-requests: write
14+
issues: write
15+
16+
jobs:
17+
# Job to check for changes to auto-generated files (db/ and README.md)
18+
check-auto-generated:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Check for auto-generated file changes
22+
id: check
23+
uses: actions/github-script@v7
24+
with:
25+
github-token: ${{ secrets.GITHUB_TOKEN }}
26+
script: |
27+
const pr = context.payload.pull_request;
28+
29+
// Get list of changed files
30+
const { data: files } = await github.rest.pulls.listFiles({
31+
owner: context.repo.owner,
32+
repo: context.repo.repo,
33+
pull_number: pr.number
34+
});
35+
36+
// Check for db folder changes
37+
const dbChanges = files.some(f => f.filename.startsWith('db/'));
38+
core.setOutput('has_db_changes', dbChanges.toString());
39+
40+
// Check for README changes
41+
const readmeChanges = files.some(f => f.filename.toLowerCase() === 'readme.md');
42+
core.setOutput('has_readme_changes', readmeChanges.toString());
43+
44+
if (dbChanges) {
45+
console.log('db/ folder was modified');
46+
}
47+
if (readmeChanges) {
48+
console.log('README.md was modified');
49+
}
50+
51+
- name: Post comment about db folder changes
52+
if: steps.check.outputs.has_db_changes == 'true'
53+
uses: actions/github-script@v7
54+
with:
55+
github-token: ${{ secrets.GITHUB_TOKEN }}
56+
script: |
57+
const pr = context.payload.pull_request;
58+
59+
const commentBody = 'Hi, and thank you for the contribution.\n\n' +
60+
'The `/db` folder is auto-generated, and changes must be made in the `/resources` folder.\n' +
61+
'Could you please amend this PR?\n' +
62+
'Our [CONTRIBUTING](https://github.com/marcelscruz/dev-resources/blob/main/CONTRIBUTING.md) guide has instructions that might help you.\n\n' +
63+
'Thanks!';
64+
65+
await github.rest.issues.createComment({
66+
owner: context.repo.owner,
67+
repo: context.repo.repo,
68+
issue_number: pr.number,
69+
body: commentBody
70+
});
71+
72+
- name: Post comment about README changes
73+
if: steps.check.outputs.has_readme_changes == 'true'
74+
uses: actions/github-script@v7
75+
with:
76+
github-token: ${{ secrets.GITHUB_TOKEN }}
77+
script: |
78+
const pr = context.payload.pull_request;
79+
80+
const commentBody = 'Hi, and thank you for the contribution.\n\n' +
81+
'The README file is auto-generated, and changes must be made in the `/resources` folder.\n' +
82+
'Could you please amend this PR?\n' +
83+
'Our [CONTRIBUTING](https://github.com/marcelscruz/dev-resources/blob/main/CONTRIBUTING.md) guide has instructions that might help you.\n\n' +
84+
'Thanks!';
85+
86+
await github.rest.issues.createComment({
87+
owner: context.repo.owner,
88+
repo: context.repo.repo,
89+
issue_number: pr.number,
90+
body: commentBody
91+
});
92+
93+
validate-and-review:
94+
runs-on: ubuntu-latest
95+
needs: check-auto-generated
96+
steps:
97+
- name: Checkout base branch
98+
uses: actions/checkout@v4
99+
with:
100+
ref: ${{ github.event.pull_request.base.ref }}
101+
102+
- name: Checkout PR head
103+
uses: actions/checkout@v4
104+
with:
105+
repository: ${{ github.event.pull_request.head.repo.full_name }}
106+
ref: ${{ github.event.pull_request.head.ref }}
107+
fetch-depth: 0
108+
109+
- name: Setup Node
110+
uses: actions/setup-node@v4
111+
with:
112+
node-version: 20
113+
114+
- name: Install dependencies
115+
run: npm install @actions/github
116+
117+
- name: Run resource validation
118+
id: validate
119+
run: |
120+
node scripts/validate-resources.js github 2>&1 | tee validation-output.txt
121+
EXIT_CODE=${PIPESTATUS[0]}
122+
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
123+
# Store multiline output
124+
{
125+
echo 'output<<EOF'
126+
cat validation-output.txt
127+
echo 'EOF'
128+
} >> $GITHUB_OUTPUT
129+
continue-on-error: true
130+
131+
- name: Run TypeScript check
132+
id: typescript
133+
run: |
134+
npx tsc --noEmit 2>&1 | tee typescript-output.txt || true
135+
{
136+
echo 'output<<EOF'
137+
cat typescript-output.txt
138+
echo 'EOF'
139+
} >> $GITHUB_OUTPUT
140+
continue-on-error: true
141+
142+
- name: Post validation results
143+
uses: actions/github-script@v7
144+
with:
145+
github-token: ${{ secrets.GITHUB_TOKEN }}
146+
script: |
147+
const validationOutput = `${{ steps.validate.outputs.output }}`;
148+
const typescriptOutput = `${{ steps.typescript.outputs.output }}`;
149+
const exitCode = '${{ steps.validate.outputs.exit_code }}';
150+
151+
let commentBody = '## 🤖 Automated PR Review\n\n';
152+
153+
// Add validation results
154+
if (exitCode !== '0') {
155+
commentBody += '### ❌ Resource Validation Issues Found\n\n';
156+
commentBody += '```\n' + validationOutput + '\n```\n\n';
157+
} else {
158+
commentBody += '### ✅ Resource Validation Passed\n\n';
159+
}
160+
161+
// Add TypeScript results if there are errors
162+
if (typescriptOutput && typescriptOutput.includes('error')) {
163+
commentBody += '### ❌ TypeScript Errors\n\n';
164+
commentBody += '```\n' + typescriptOutput + '\n```\n\n';
165+
}
166+
167+
// Add helpful links
168+
commentBody += '---\n\n';
169+
commentBody += '📚 **Resources:**\n';
170+
commentBody += '- [Contribution Guidelines](https://github.com/marcelscruz/dev-resources/blob/main/CONTRIBUTING.md)\n';
171+
commentBody += '- [Valid Categories](https://github.com/marcelscruz/dev-resources/blob/main/types/category.ts)\n';
172+
173+
await github.rest.issues.createComment({
174+
owner: context.repo.owner,
175+
repo: context.repo.repo,
176+
issue_number: context.payload.pull_request.number,
177+
body: commentBody
178+
});
179+
180+
copilot-review:
181+
runs-on: ubuntu-latest
182+
needs: [check-auto-generated, validate-and-review]
183+
steps:
184+
- name: Checkout PR
185+
uses: actions/checkout@v4
186+
with:
187+
repository: ${{ github.event.pull_request.head.repo.full_name }}
188+
ref: ${{ github.event.pull_request.head.ref }}
189+
190+
- name: Request Copilot Code Review
191+
uses: actions/github-script@v7
192+
with:
193+
github-token: ${{ secrets.GITHUB_TOKEN }}
194+
script: |
195+
// Request a review from GitHub Copilot (if enabled in repo settings)
196+
// This will trigger Copilot's code review feature
197+
try {
198+
// Add a label to indicate Copilot review requested
199+
await github.rest.issues.addLabels({
200+
owner: context.repo.owner,
201+
repo: context.repo.repo,
202+
issue_number: context.payload.pull_request.number,
203+
labels: ['copilot-review']
204+
});
205+
} catch (error) {
206+
console.log('Could not add label:', error.message);
207+
}
208+
209+
// Log that Copilot review was triggered
210+
console.log('Copilot review triggered for PR #' + context.payload.pull_request.number);
211+
212+
auto-fix:
213+
runs-on: ubuntu-latest
214+
needs: validate-and-review
215+
if: failure() && github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name
216+
steps:
217+
- name: Checkout PR
218+
uses: actions/checkout@v4
219+
with:
220+
ref: ${{ github.event.pull_request.head.ref }}
221+
token: ${{ secrets.GITHUB_TOKEN }}
222+
223+
- name: Setup Node
224+
uses: actions/setup-node@v4
225+
with:
226+
node-version: 20
227+
228+
- name: Configure Git
229+
run: |
230+
git config user.name "github-actions[bot]"
231+
git config user.email "github-actions[bot]@users.noreply.github.com"
232+
233+
- name: Apply automatic fixes
234+
id: auto-fix
235+
run: |
236+
node scripts/auto-fix-resources.js 2>&1 | tee auto-fix-output.txt
237+
EXIT_CODE=${PIPESTATUS[0]}
238+
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
239+
{
240+
echo 'output<<EOF'
241+
cat auto-fix-output.txt
242+
echo 'EOF'
243+
} >> $GITHUB_OUTPUT
244+
continue-on-error: true
245+
246+
- name: Commit and push fixes
247+
if: steps.auto-fix.outputs.exit_code == '0' && steps.auto-fix.outputs.output != ''
248+
run: |
249+
# Check if there are changes to commit
250+
if git diff --quiet && git diff --cached --quiet; then
251+
echo "No changes to commit"
252+
else
253+
git add resources/
254+
git commit -m "🤖 Auto-fix: Apply automatic fixes for validation errors" -m "Applied fixes based on validation errors:" -m "- Fixed invalid category names with suggested replacements" -m "- Fixed URL protocol issues (added https:// where missing)" -m "- Fixed alphabetical ordering of resources" -m "- Fixed missing commas between resource objects" -m "" -m "These fixes were automatically applied by the Copilot PR Review workflow."
255+
git push
256+
echo "✅ Automatic fixes have been applied and pushed to the PR"
257+
fi
258+
259+
- name: Post auto-fix results
260+
if: steps.auto-fix.outputs.output != ''
261+
uses: actions/github-script@v7
262+
with:
263+
github-token: ${{ secrets.GITHUB_TOKEN }}
264+
script: |
265+
const fixOutput = `${{ steps.auto-fix.outputs.output }}`;
266+
let fixData;
267+
try {
268+
fixData = JSON.parse(fixOutput);
269+
} catch (e) {
270+
fixData = null;
271+
}
272+
273+
let commentBody = '## 🤖 Automatic Fixes Applied\n\n';
274+
275+
if (fixData && fixData.totalFixes > 0) {
276+
commentBody += `✅ **${fixData.totalFixes} fix(es) applied automatically:**\n\n`;
277+
278+
// Group fixes by file
279+
const byFile = {};
280+
for (const fix of fixData.fixesApplied) {
281+
if (!byFile[fix.file]) byFile[fix.file] = [];
282+
byFile[fix.file].push(fix);
283+
}
284+
285+
for (const [file, fixes] of Object.entries(byFile)) {
286+
commentBody += `### ${file}\n`;
287+
for (const fix of fixes) {
288+
commentBody += `- Line ${fix.line}: ${fix.change}\n`;
289+
}
290+
commentBody += '\n';
291+
}
292+
293+
commentBody += '---\n\n';
294+
commentBody += '✨ These fixes have been automatically applied and committed to your PR branch.\n';
295+
commentBody += 'Please review the changes and ensure they look correct.';
296+
} else {
297+
commentBody += 'No automatic fixes were available for the issues found.\n';
298+
commentBody += 'Please review the validation errors and apply fixes manually.';
299+
}
300+
301+
await github.rest.issues.createComment({
302+
owner: context.repo.owner,
303+
repo: context.repo.repo,
304+
issue_number: context.payload.pull_request.number,
305+
body: commentBody
306+
});
307+
308+
auto-fix-suggestions:
309+
runs-on: ubuntu-latest
310+
needs: [check-auto-generated, validate-and-review]
311+
if: failure() && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
312+
steps:
313+
- name: Checkout PR
314+
uses: actions/checkout@v4
315+
with:
316+
repository: ${{ github.event.pull_request.head.repo.full_name }}
317+
ref: ${{ github.event.pull_request.head.ref }}
318+
319+
- name: Setup Node
320+
uses: actions/setup-node@v4
321+
with:
322+
node-version: 20
323+
324+
- name: Generate fix suggestions
325+
id: fixes
326+
run: |
327+
node scripts/generate-fix-suggestions.js 2>&1 | tee fixes-output.txt || true
328+
{
329+
echo 'output<<EOF'
330+
cat fixes-output.txt
331+
echo 'EOF'
332+
} >> $GITHUB_OUTPUT
333+
334+
- name: Post fix suggestions
335+
uses: actions/github-script@v7
336+
with:
337+
github-token: ${{ secrets.GITHUB_TOKEN }}
338+
script: |
339+
const fixesOutput = `${{ steps.fixes.outputs.output }}`;
340+
341+
if (fixesOutput && fixesOutput.trim()) {
342+
const commentBody = '## 💡 Suggested Fixes\n\n' +
343+
'Based on the validation errors, here are suggested fixes:\n\n' +
344+
'```diff\n' + fixesOutput + '\n```\n\n' +
345+
'---\n\n' +
346+
'🤖 *These suggestions were generated automatically. Please review them before applying.*';
347+
348+
await github.rest.issues.createComment({
349+
owner: context.repo.owner,
350+
repo: context.repo.repo,
351+
issue_number: context.payload.pull_request.number,
352+
body: commentBody
353+
});
354+
}

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
"update-db": "ts-node utils/db/update-db.js",
88
"update-readme": "ts-node utils/readme/update-readme.js",
99
"dev-order": "node utils/order/resources-extractor.js",
10-
"prettier:format": "prettier --write \"**/*.{ts,js}\""
10+
"prettier:format": "prettier --write \"**/*.{ts,js}\"",
11+
"validate": "node scripts/validate-resources.js",
12+
"validate:github": "node scripts/validate-resources.js github",
13+
"suggest-fixes": "node scripts/generate-fix-suggestions.js",
14+
"auto-fix": "node scripts/auto-fix-resources.js"
1115
},
1216
"dependencies": {
1317
"eslint": "^7.18.0",

0 commit comments

Comments
 (0)