forked from esphome/esphome.io
-
Notifications
You must be signed in to change notification settings - Fork 0
206 lines (186 loc) · 8.29 KB
/
Copy pathcheck-component-index.yml
File metadata and controls
206 lines (186 loc) · 8.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
name: Check Component Index Order
on:
pull_request_target:
types: [opened, reopened, synchronize]
paths:
- 'src/content/docs/components/index.mdx'
permissions:
pull-requests: write
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
env:
BOT_NAME: "github-actions[bot]"
REVIEW_MARKER: "ImgTable blocks are not in alphabetical order"
jobs:
check:
name: Component Index Ordering
runs-on: ubuntu-latest
steps:
# Checkout the BASE branch (trusted code) — the script runs from here.
- name: Checkout base branch
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Sparse-checkout only the index file from the PR head into a
# separate directory. No untrusted code is executed.
# checkout v7 blocks fork-PR checkouts under pull_request_target unless
# opted in; safe here because only the single data file is fetched and it
# is never executed.
- name: Checkout PR index file
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha }}
sparse-checkout: src/content/docs/components/index.mdx
path: pr-head
allow-unsafe-pr-checkout: true
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
- name: Check ordering
id: check
run: |
set +e
node script/check_component_index.mjs --suggestions \
--file pr-head/src/content/docs/components/index.mdx \
> /tmp/suggestions.json
EXIT_CODE=$?
set -e
if [ "$EXIT_CODE" -eq 0 ]; then
echo "sorted=true" >> "$GITHUB_OUTPUT"
elif [ "$EXIT_CODE" -eq 1 ]; then
echo "sorted=false" >> "$GITHUB_OUTPUT"
else
echo "::error::check_component_index.mjs failed with exit code $EXIT_CODE"
exit "$EXIT_CODE"
fi
- name: Post or dismiss review
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
SORTED: ${{ steps.check.outputs.sorted }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
with:
script: |
const fs = require('fs');
const { owner, repo } = context.repo;
const pr_number = parseInt(process.env.PR_NUMBER);
const sorted = process.env.SORTED === 'true';
// Helper: find the most recent active bot review for this check.
// Only returns reviews in CHANGES_REQUESTED state (not dismissed).
async function findActiveBotReview() {
const { data: reviews } = await github.rest.pulls.listReviews({
owner,
repo,
pull_number: pr_number
});
return reviews
.filter(r =>
r.user.login === process.env.BOT_NAME &&
r.state === 'CHANGES_REQUESTED' &&
r.body && r.body.includes(process.env.REVIEW_MARKER)
)
.sort((a, b) => new Date(b.submitted_at) - new Date(a.submitted_at))[0];
}
if (sorted) {
console.log('All ImgTable blocks are correctly sorted.');
const botReview = await findActiveBotReview();
if (botReview) {
console.log('Dismissing previous review', botReview.id);
await github.rest.pulls.dismissReview({
owner,
repo,
pull_number: pr_number,
review_id: botReview.id,
message: 'Component index ordering has been fixed — dismissing review.'
});
}
return;
}
// Read the suggestions JSON produced by the script
const data = JSON.parse(fs.readFileSync('/tmp/suggestions.json', 'utf-8'));
console.log(`Found ${data.suggestions.length} unsorted table(s).`);
// Build inline suggestion comments
const comments = data.suggestions.map(s => ({
path: data.file,
start_line: s.startLine,
line: s.endLine,
side: 'RIGHT',
start_side: 'RIGHT',
body: [
`**${s.section}**: items are not in alphabetical order.`,
'',
'```suggestion',
s.body,
'```'
].join('\n')
}));
// Check if an active bot review already exists with the same suggestions.
// If so, skip posting a duplicate review.
const existingReview = await findActiveBotReview();
if (existingReview) {
const { data: existingComments } = await github.rest.pulls.listReviewComments({
owner,
repo,
pull_number: pr_number,
per_page: 100
});
const reviewComments = existingComments
.filter(c => c.pull_request_review_id === existingReview.id)
.map(c => ({ path: c.path, start_line: c.start_line, line: c.line, body: c.body }));
const newFingerprint = JSON.stringify(comments.map(c => ({ path: c.path, start_line: c.start_line, line: c.line, body: c.body })));
const existingFingerprint = JSON.stringify(reviewComments);
if (newFingerprint === existingFingerprint) {
console.log('Active review already has identical suggestions — skipping.');
return;
}
console.log('Suggestions have changed — dismissing stale review', existingReview.id);
await github.rest.pulls.dismissReview({
owner,
repo,
pull_number: pr_number,
review_id: existingReview.id,
message: 'Superseded by updated review on latest push.'
});
}
// Create REQUEST_CHANGES review with inline suggestions
const reviewBody = [
`### ${process.env.REVIEW_MARKER}`,
'',
`Found ${data.suggestions.length} ImgTable block(s) with incorrect ordering below **Network Protocols**.`,
'Each table has at most one Core item (matching the section name, e.g. "Sensor Core" for the Sensor section), pinned first, followed by Template items (names starting with "Template "), then all remaining items sorted alphabetically.',
'',
'You can fix this automatically by running:',
'```',
'node script/check_component_index.mjs --fix',
'```',
];
try {
await github.rest.pulls.createReview({
owner,
repo,
pull_number: pr_number,
commit_id: process.env.HEAD_SHA,
event: 'REQUEST_CHANGES',
body: [...reviewBody, '', 'See the inline suggestions below for the correct order in each section.'].join('\n'),
comments
});
console.log('Posted REQUEST_CHANGES review with inline suggestions.');
} catch (e) {
if (e?.status === 422) {
// Inline suggestions fail when unsorted lines are outside the PR diff.
// Fall back to a review without inline comments.
console.log(`Inline suggestions failed with 422, posting review without inline comments.`);
await github.rest.pulls.createReview({
owner,
repo,
pull_number: pr_number,
commit_id: process.env.HEAD_SHA,
event: 'REQUEST_CHANGES',
body: [...reviewBody, '', 'The unsorted blocks are outside this PR\'s diff. Try merging the latest base branch first, or run the fix command above.'].join('\n'),
});
console.log('Posted REQUEST_CHANGES review without inline suggestions.');
} else {
throw e;
}
}