Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .github/workflows/changelog-enforcement.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Changelog Enforcement

on:
workflow_call:

jobs:
check-changelog:
name: Check Changelog Changes
runs-on: ubuntu-24.04
if: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read
pull-requests: write

steps:
- name: Check for CHANGELOG.md modifications
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # 8.0.0
with:
script: |
const prNumber = context.payload.pull_request.number;
const prTitle = context.payload.pull_request.title;
const isChangelogPR = prTitle.startsWith('meta(changelog):');

// Paginate through all files to handle PRs with >100 changed files
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
per_page: 100
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing pagination allows CHANGELOG.md bypass in large PRs

Medium Severity

The listFiles call only fetches the first 100 files without handling pagination. If a PR modifies more than 100 files and CHANGELOG.md is not in the first page of results, the enforcement check will fail to detect it and allow the PR to merge without proper validation. This silently bypasses the intended changelog protection mechanism.

Fix in Cursor Fix in Web


const changelogModified = files.some(file => file.filename === 'CHANGELOG.md');

console.log(`PR Title: ${prTitle}`);
console.log(`Is Changelog PR: ${isChangelogPR}`);
console.log(`CHANGELOG.md Modified: ${changelogModified}`);

const commentMarker = '<!--changelog_enforcement_violation_comment-->';
const commentBody = `${commentMarker}
## :warning: Manual Changelog Changes Detected

This PR modifies \`CHANGELOG.md\`, but the PR title does not start with \`meta(changelog):\`.

**Changelogs in this repository are generated automatically during releases.** Manual edits to \`CHANGELOG.md\` are not allowed in regular PRs.

### What to do:

1. **If this change is intentional**: Create a separate PR with a title starting with \`meta(changelog):\` to make changelog edits.

2. **If this was unintentional**: Remove the \`CHANGELOG.md\` changes from this PR.`;

// Paginate through all comments to handle PRs with many comments
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100
});

const botComment = comments.find(comment =>
comment.body.includes(commentMarker)
);

if (changelogModified && !isChangelogPR) {
// Violation: post comment if not already present
if (!botComment) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: commentBody
});
console.log('Posted comment explaining changelog policy');
} else {
console.log('Comment already exists, skipping');
}

core.setFailed('CHANGELOG.md changes are not allowed unless PR title starts with "meta(changelog):"');
} else {
// No violation: delete comment if it exists from a previous run
if (botComment) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id
});
console.log('Deleted outdated violation comment');
}

if (changelogModified && isChangelogPR) {
console.log('CHANGELOG.md changes allowed in meta(changelog): PR');
} else {
console.log('No CHANGELOG.md changes detected');
}
}
9 changes: 7 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,23 @@ jobs:
name: Test Swift
uses: ./.github/workflows/swift-test.yml

changelog:
name: Changelog
uses: ./.github/workflows/changelog-enforcement.yml

required:
name: Check required jobs
runs-on: ubuntu-24.04
needs: [lint, test, test_node, test_swift]
needs: [lint, test, test_node, test_swift, changelog]
if: always()
steps:
- name: Check for failure
if: ${{
needs.lint.result != 'success' ||
needs.test.result != 'success' ||
needs.test_node.result != 'success' ||
needs.test_swift.result != 'success'
needs.test_swift.result != 'success' ||
(needs.changelog.result != 'success' && needs.changelog.result != 'skipped')
}}
run: |
echo "One or more jobs failed"
Expand Down
Loading