44 pull_request :
55 types :
66 - closed
7+ workflow_dispatch :
8+ inputs :
9+ pr_number :
10+ description : " PR number to add to the changelog (must be a merged PR)"
11+ required : true
12+ type : string
713
814permissions :
915 contents : write
1016 pull-requests : write
1117 actions : read
1218
19+ env :
20+ CHANGELOG_BASE_BRANCH : master
21+
1322jobs :
1423 update_changelog :
1524 name : Update Changelog
16- if : github.event.pull_request.merged == true
25+ if : github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
1726 runs-on : ubuntu-latest
1827
1928 steps :
@@ -25,16 +34,56 @@ jobs:
2534 .github/scripts/update-changelog.mjs
2635 sparse-checkout-cone-mode : false
2736
37+ - name : Resolve PR data
38+ id : resolve-pr
39+ uses : actions/github-script@v7
40+ with :
41+ script : |
42+ let pr;
43+
44+ if (context.eventName === 'workflow_dispatch') {
45+ const rawPrNumber = (context.payload.inputs?.pr_number ?? '').trim();
46+ if (!/^\d+$/.test(rawPrNumber) || Number(rawPrNumber) <= 0) {
47+ core.setFailed(`Invalid workflow input "pr_number": "${rawPrNumber}". Please provide a positive integer PR number.`);
48+ return;
49+ }
50+
51+ const prNumber = Number(rawPrNumber);
52+ console.log(`🔍 Fetching PR #${prNumber} from API...`);
53+
54+ const { data } = await github.rest.pulls.get({
55+ owner: context.repo.owner,
56+ repo: context.repo.repo,
57+ pull_number: prNumber,
58+ });
59+
60+ if (!data.merged) {
61+ core.setFailed(`PR #${prNumber} has not been merged. Only merged PRs can be added to the changelog.`);
62+ return;
63+ }
64+
65+ pr = data;
66+ console.log(`✅ Resolved PR #${prNumber}: ${pr.title}`);
67+ } else {
68+ pr = context.payload.pull_request;
69+ }
70+
71+ const prJson = JSON.stringify(pr);
72+ core.setOutput('pr-json-base64', Buffer.from(prJson, 'utf8').toString('base64'));
73+
2874 - name : Check if PR should be excluded
2975 id : check-exclusions
3076 uses : actions/github-script@v7
3177 with :
3278 script : |
3379 const { default: checkExclusions } = await import('${{ github.workspace }}/.github/scripts/check-changelog-exclusions.mjs');
80+ const pr = JSON.parse(Buffer.from(process.env.RESOLVED_PR_BASE64, 'base64').toString('utf8'));
3481 return await checkExclusions({
35- pr: context.payload.pull_request ,
82+ pr,
3683 core
3784 });
85+ env :
86+ RESOLVED_PR_BASE64 : " ${{ steps.resolve-pr.outputs.pr-json-base64 }}"
3887
3988 - name : Changelog update skipped
4089 if : steps.check-exclusions.outputs.should-skip == 'true'
56105 if : steps.check-exclusions.outputs.should-skip == 'false'
57106 id : check-existing-changelog-pr
58107 run : |
59- BASE_BRANCH="${{ github.event.pull_request.base.ref }}"
108+ BASE_BRANCH="${{ env.CHANGELOG_BASE_BRANCH }}"
60109
61110 # Find existing changelog PR
62111 CHANGELOG_PR=$(gh pr list --base "$BASE_BRANCH" --state open --json number,title,headRefName,body --jq '.[] | select(.title | test("^docs: Update changelog"; "i"))')
@@ -99,12 +148,12 @@ jobs:
99148
100149 if [ "${{ steps.check-existing-changelog-pr.outputs.changelog-pr-exists }}" = "true" ]; then
101150 echo "🔄 Checking out existing branch: $BRANCH_NAME"
102- git fetch origin $BRANCH_NAME
103- git checkout $BRANCH_NAME
104- git pull origin $BRANCH_NAME
151+ git fetch origin " $BRANCH_NAME"
152+ git checkout " $BRANCH_NAME"
153+ git reset --hard "origin/ $BRANCH_NAME"
105154 else
106155 echo "✨ Creating new branch: $BRANCH_NAME"
107- git checkout -b $BRANCH_NAME
156+ git checkout -b " $BRANCH_NAME"
108157 fi
109158
110159 - name : Update Changelog
@@ -114,10 +163,13 @@ jobs:
114163 with :
115164 script : |
116165 const { default: updateChangelog } = await import('/tmp/update-changelog.mjs');
166+ const pr = JSON.parse(Buffer.from(process.env.RESOLVED_PR_BASE64, 'base64').toString('utf8'));
117167 return await updateChangelog({
118- pr: context.payload.pull_request ,
168+ pr,
119169 core
120170 });
171+ env :
172+ RESOLVED_PR_BASE64 : " ${{ steps.resolve-pr.outputs.pr-json-base64 }}"
121173
122174 - name : Prettify Changelog
123175 id : prettify-changelog
@@ -133,23 +185,48 @@ jobs:
133185 git diff CHANGELOG.md
134186
135187 - name : Commit and push changes
188+ id : commit-push
136189 if : steps.check-exclusions.outputs.should-skip == 'false' && steps.update-changelog.outputs.changelog-updated == 'true'
137190 run : |
191+ set -euo pipefail
138192 BRANCH_NAME="${{ steps.check-existing-changelog-pr.outputs.changelog-pr-branch-name }}"
193+ echo "pushed=false" >> "$GITHUB_OUTPUT"
139194
140195 git add CHANGELOG.md
196+
197+ # Avoid failing when there's nothing new to commit
198+ if git diff --cached --quiet; then
199+ echo "No changelog changes to commit."
200+ exit 0
201+ fi
202+
141203 git commit -m "docs: update changelog for PR #${{ steps.update-changelog.outputs.pr-number }}"
142204
143- if [ "${{ steps.check-existing-changelog-pr.outputs.changelog-pr-exists }}" = "true" ]; then
144- git push origin $BRANCH_NAME
145- else
146- git push -u origin $BRANCH_NAME
205+ # Push and recover from race by rebasing once and retrying
206+ if ! git push -u origin "$BRANCH_NAME"; then
207+ echo "Push rejected, retrying after rebase..."
208+ if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then
209+ git fetch origin "$BRANCH_NAME"
210+ if ! git rebase "origin/$BRANCH_NAME"; then
211+ echo "Rebase failed while retrying push to $BRANCH_NAME." >&2
212+ git rebase --abort || true
213+ exit 1
214+ fi
215+ else
216+ echo "Remote branch $BRANCH_NAME not found during retry; retrying push without rebase."
217+ fi
218+ if ! git push -u origin "$BRANCH_NAME"; then
219+ echo "Failed to push changelog changes after retry." >&2
220+ exit 1
221+ fi
147222 fi
148223
224+ echo "pushed=true" >> "$GITHUB_OUTPUT"
225+
149226 - name : Create or update changelog PR
150- if : steps.check-exclusions.outputs.should-skip == 'false' && steps.update-changelog.outputs.changelog-updated == 'true'
227+ if : steps.check-exclusions.outputs.should-skip == 'false' && steps.update-changelog.outputs.changelog-updated == 'true' && steps.commit-push.outputs.pushed == 'true'
151228 run : |
152- BASE_BRANCH="${{ github.event.pull_request.base.ref }}"
229+ BASE_BRANCH="${{ env.CHANGELOG_BASE_BRANCH }}"
153230 BRANCH_NAME="${{ steps.check-existing-changelog-pr.outputs.changelog-pr-branch-name }}"
154231 PR_NUMBER="${{ steps.update-changelog.outputs.pr-number }}"
155232 CHANGELOG_PR_EXISTS="${{ steps.check-existing-changelog-pr.outputs.changelog-pr-exists }}"
0 commit comments