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:
Alex Ross
2026-03-10 12:52:52 +01:00
committed by GitHub
parent 35e0427ee2
commit cba3c7aac7

View 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