-
Notifications
You must be signed in to change notification settings - Fork 38.5k
298 lines (258 loc) · 12.3 KB
/
api-proposal-version-check.yml
File metadata and controls
298 lines (258 loc) · 12.3 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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
name: API Proposal Version Check
on:
pull_request:
branches:
- main
- 'release/*'
paths:
- 'src/vscode-dts/vscode.proposed.*.d.ts'
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write
actions: write
concurrency:
group: api-proposal-${{ github.event.pull_request.number || github.event.issue.number }}
cancel-in-progress: true
jobs:
check-version-changes:
name: Check API Proposal Version Changes
# Run on PR events, or on issue_comment if it's on a PR and contains the override command
if: |
github.event_name == 'pull_request' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '/api-proposal-change-required') &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'))
runs-on: ubuntu-latest
steps:
- name: Get PR info
id: pr_info
uses: actions/github-script@v8
with:
script: |
let prNumber, headSha, baseSha;
if (context.eventName === 'pull_request') {
prNumber = context.payload.pull_request.number;
headSha = context.payload.pull_request.head.sha;
baseSha = context.payload.pull_request.base.sha;
} else {
// issue_comment event - need to fetch PR details
prNumber = context.payload.issue.number;
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
headSha = pr.head.sha;
baseSha = pr.base.sha;
}
core.setOutput('number', prNumber);
core.setOutput('head_sha', headSha);
core.setOutput('base_sha', baseSha);
- name: Check for override comment
id: check_override
uses: actions/github-script@v8
with:
script: |
const prNumber = ${{ steps.pr_info.outputs.number }};
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
// Only accept overrides from trusted users (repo members/collaborators)
const trustedAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR'];
let overrideComment = null;
const untrustedOverrides = [];
comments.forEach((comment, index) => {
const hasOverrideText = comment.body.includes('/api-proposal-change-required');
const isTrusted = trustedAssociations.includes(comment.author_association);
console.log(`Comment ${index + 1}:`);
console.log(` Author: ${comment.user.login}`);
console.log(` Author association: ${comment.author_association}`);
console.log(` Created at: ${comment.created_at}`);
console.log(` Contains override command: ${hasOverrideText}`);
console.log(` Author is trusted: ${isTrusted}`);
console.log(` Would be valid override: ${hasOverrideText && isTrusted}`);
if (hasOverrideText) {
if (isTrusted && !overrideComment) {
overrideComment = comment;
} else if (!isTrusted) {
untrustedOverrides.push(comment);
}
}
});
if (overrideComment) {
console.log(`✅ Override comment FOUND`);
console.log(` Comment ID: ${overrideComment.id}`);
console.log(` Author: ${overrideComment.user.login}`);
console.log(` Association: ${overrideComment.author_association}`);
console.log(` Created at: ${overrideComment.created_at}`);
core.setOutput('override_found', 'true');
core.setOutput('override_user', overrideComment.user.login);
} else {
if (untrustedOverrides.length > 0) {
console.log(`⚠️ Found ${untrustedOverrides.length} override comment(s) from UNTRUSTED user(s):`);
untrustedOverrides.forEach((comment, index) => {
console.log(` Untrusted override ${index + 1}:`);
console.log(` Author: ${comment.user.login}`);
console.log(` Association: ${comment.author_association}`);
console.log(` Created at: ${comment.created_at}`);
console.log(` Comment ID: ${comment.id}`);
});
console.log(` Trusted associations are: ${trustedAssociations.join(', ')}`);
}
console.log('❌ No valid override comment found');
core.setOutput('override_found', 'false');
}
# If triggered by the override comment, re-run the failed workflow to update its status
# Only allow trusted users to trigger re-runs to prevent spam
- name: Re-run failed workflow on override
if: |
steps.check_override.outputs.override_found == 'true' &&
github.event_name == 'issue_comment' &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR')
uses: actions/github-script@v8
with:
script: |
const headSha = '${{ steps.pr_info.outputs.head_sha }}';
console.log(`Override comment found by ${{ steps.check_override.outputs.override_user }}`);
console.log('API proposal version change has been acknowledged.');
// Find the failed workflow run for this PR's head SHA
const { data: runs } = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'api-proposal-version-check.yml',
head_sha: headSha,
status: 'completed',
per_page: 10
});
// Find the most recent failed run
const failedRun = runs.workflow_runs.find(run =>
run.conclusion === 'failure' && run.event === 'pull_request'
);
if (failedRun) {
console.log(`Re-running failed workflow run ${failedRun.id}`);
await github.rest.actions.reRunWorkflow({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: failedRun.id
});
console.log('Workflow re-run triggered successfully');
} else {
console.log('No failed pull_request workflow run found to re-run');
// The check will pass on this run since override exists
}
- name: Pass on override comment
if: steps.check_override.outputs.override_found == 'true'
run: |
echo "Override comment found by ${{ steps.check_override.outputs.override_user }}"
echo "API proposal version change has been acknowledged."
# Only continue checking if no override found
- name: Checkout repository
if: steps.check_override.outputs.override_found != 'true'
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Check for version changes
if: steps.check_override.outputs.override_found != 'true'
id: version_check
env:
HEAD_SHA: ${{ steps.pr_info.outputs.head_sha }}
BASE_SHA: ${{ steps.pr_info.outputs.base_sha }}
run: |
set -e
# Use merge-base to get accurate diff of what the PR actually changes
MERGE_BASE=$(git merge-base "$BASE_SHA" "$HEAD_SHA")
echo "Merge base: $MERGE_BASE"
# Get the list of changed proposed API files (diff against merge-base)
CHANGED_FILES=$(git diff --name-only "$MERGE_BASE" "$HEAD_SHA" -- 'src/vscode-dts/vscode.proposed.*.d.ts' || true)
if [ -z "$CHANGED_FILES" ]; then
echo "No proposed API files changed"
echo "version_changed=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "Changed proposed API files:"
echo "$CHANGED_FILES"
VERSION_CHANGED="false"
CHANGED_LIST=""
for FILE in $CHANGED_FILES; do
# Check if file exists in head
if ! git cat-file -e "$HEAD_SHA:$FILE" 2>/dev/null; then
echo "File $FILE was deleted, skipping version check"
continue
fi
# Get version from head (current PR)
HEAD_VERSION=$(git show "$HEAD_SHA:$FILE" | grep -E '^// version: [0-9]+' | sed 's/.*version: //' || echo "")
# Get version from merge-base (what the PR is based on)
BASE_VERSION=$(git show "$MERGE_BASE:$FILE" 2>/dev/null | grep -E '^// version: [0-9]+' | sed 's/.*version: //' || echo "")
echo "File: $FILE"
echo " Base version: ${BASE_VERSION:-'(none)'}"
echo " Head version: ${HEAD_VERSION:-'(none)'}"
# Check if version was added or changed
if [ -n "$HEAD_VERSION" ] && [ "$HEAD_VERSION" != "$BASE_VERSION" ]; then
echo " -> Version changed!"
VERSION_CHANGED="true"
FILENAME=$(basename "$FILE")
if [ -n "$CHANGED_LIST" ]; then
CHANGED_LIST="$CHANGED_LIST, $FILENAME"
else
CHANGED_LIST="$FILENAME"
fi
fi
done
echo "version_changed=$VERSION_CHANGED" >> $GITHUB_OUTPUT
echo "changed_files=$CHANGED_LIST" >> $GITHUB_OUTPUT
- name: Post warning comment
if: steps.check_override.outputs.override_found != 'true' && steps.version_check.outputs.version_changed == 'true'
uses: actions/github-script@v8
with:
script: |
const prNumber = ${{ steps.pr_info.outputs.number }};
const changedFiles = '${{ steps.version_check.outputs.changed_files }}';
// Check if we already posted a warning comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
const marker = '<!-- api-proposal-version-warning -->';
const existingComment = comments.find(comment =>
comment.body.includes(marker)
);
const body = `${marker}
## ⚠️ API Proposal Version Change Detected
The following proposed API files have version changes: **${changedFiles}**
API proposal version changes should only be used when maintaining compatibility is not possible. Consider keeping the version as is and maintaining backward compatibility.
**Any version changes must be adopted by the consuming extensions before the next insiders for the extension to work.**
---
If the version change is required, comment \`/api-proposal-change-required\` to unblock this check and acknowledge that you will update any critical consuming extensions (Copilot Chat).`;
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: body
});
console.log('Updated existing warning comment');
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: body
});
console.log('Posted new warning comment');
}
- name: Fail if version changed without override
if: steps.check_override.outputs.override_found != 'true' && steps.version_check.outputs.version_changed == 'true'
run: |
echo "::error::API proposal version changed in: ${{ steps.version_check.outputs.changed_files }}"
echo "To unblock, comment '/api-proposal-change-required' on the PR."
exit 1