mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-18 07:47:23 +01:00
Add API proposal version check (#300392)
* Add API proposal version check * CCR feedback * Test with version bump * Comment improvements * Undo test version bump * More comment improvement
This commit is contained in:
245
.github/workflows/api-proposal-version-check.yml
vendored
Normal file
245
.github/workflows/api-proposal-version-check.yml
vendored
Normal file
@@ -0,0 +1,245 @@
|
||||
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
|
||||
checks: 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'))
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get PR info
|
||||
id: pr_info
|
||||
uses: actions/github-script@v7
|
||||
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@v7
|
||||
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'];
|
||||
const overrideComment = comments.find(comment =>
|
||||
comment.body.includes('/api-proposal-change-required') &&
|
||||
trustedAssociations.includes(comment.author_association)
|
||||
);
|
||||
|
||||
if (overrideComment) {
|
||||
console.log(`Override comment found by ${overrideComment.user.login} (${overrideComment.author_association})`);
|
||||
core.setOutput('override_found', 'true');
|
||||
core.setOutput('override_user', overrideComment.user.login);
|
||||
} else {
|
||||
// Check if there's an override from an untrusted user
|
||||
const untrustedOverride = comments.find(comment =>
|
||||
comment.body.includes('/api-proposal-change-required') &&
|
||||
!trustedAssociations.includes(comment.author_association)
|
||||
);
|
||||
if (untrustedOverride) {
|
||||
console.log(`Override comment by ${untrustedOverride.user.login} ignored (${untrustedOverride.author_association} is not trusted)`);
|
||||
}
|
||||
console.log('No valid override comment found');
|
||||
core.setOutput('override_found', 'false');
|
||||
}
|
||||
|
||||
# If triggered by the override comment, create a successful check run on the PR head
|
||||
- name: Pass on override comment
|
||||
if: steps.check_override.outputs.override_found == 'true'
|
||||
uses: actions/github-script@v7
|
||||
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.');
|
||||
|
||||
// Create a successful check run on the PR head SHA so the status updates
|
||||
await github.rest.checks.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'Check API Proposal Version Changes',
|
||||
head_sha: headSha,
|
||||
status: 'completed',
|
||||
conclusion: 'success',
|
||||
output: {
|
||||
title: 'API Proposal Version Change Acknowledged',
|
||||
summary: `Override approved by @${{ steps.check_override.outputs.override_user }}`
|
||||
}
|
||||
});
|
||||
|
||||
# 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@v7
|
||||
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
|
||||
Reference in New Issue
Block a user