1- # This workflow labels merged PRs that modify `*.baseline.json` files with `api-review`,
2- # computes ApiChief deltas between the original and merged baseline files, and posts the
3- # results back to the pull request as a review comment.
1+ # This workflow labels PRs that modify `*.baseline.json` files with `api-review`,
2+ # computes ApiChief deltas between the base and selected PR baseline files, and posts the
3+ # results back to the pull request as a comment.
44
5- name : Comment API baseline deltas on merged PRs
5+ name : Comment API baseline deltas on PRs
66
77on :
88 pull_request_target :
99 types : [closed]
1010 branches :
1111 - main
1212 - release/**
13+ workflow_dispatch :
14+ inputs :
15+ pr-number :
16+ description : Pull request number to process
17+ required : true
18+ type : number
1319
1420permissions :
1521 contents : read
1622 issues : write
17- pull-requests : read
23+ pull-requests : write
1824
1925jobs :
2026 api-review :
21- if : github.event.pull_request.merged == true
27+ if : github.event_name == 'workflow_dispatch' || github. event.pull_request.merged == true
2228 runs-on : ubuntu-latest
2329 steps :
2430 - name : Detect changed baseline files and add label
2834 script : |
2935 const owner = context.repo.owner;
3036 const repo = context.repo.repo;
31- const prNumber = context.payload.pull_request.number;
37+ const prNumber = context.eventName === 'workflow_dispatch'
38+ ? Number(core.getInput('pr-number'))
39+ : context.payload.pull_request.number;
40+
41+ if (!Number.isInteger(prNumber) || prNumber <= 0) {
42+ core.setFailed(`Invalid PR number: ${prNumber}`);
43+ return;
44+ }
45+
46+ const { data: pullRequest } = await github.rest.pulls.get({
47+ owner,
48+ repo,
49+ pull_number: prNumber
50+ });
3251
3352 const files = await github.paginate(github.rest.pulls.listFiles, {
3453 owner,
@@ -45,11 +64,18 @@ jobs:
4564 previous_filename: file.previous_filename ?? null
4665 }));
4766
67+ core.setOutput('pr_number', String(prNumber));
68+ core.setOutput('base_sha', pullRequest.base.sha);
69+ core.setOutput('target_sha', pullRequest.merge_commit_sha ?? pullRequest.head.sha);
4870 core.setOutput('has_baselines', String(baselineFiles.length > 0));
4971 core.setOutput('files_json', JSON.stringify(baselineFiles));
5072
73+ if (!pullRequest.merged) {
74+ console.log(`PR #${prNumber} is not merged; using head SHA ${pullRequest.head.sha}.`);
75+ }
76+
5177 if (baselineFiles.length === 0) {
52- console.log(' No changed baseline files detected on this merged PR.' );
78+ console.log(` No changed baseline files detected on PR #${prNumber}.` );
5379 return;
5480 }
5581
@@ -60,13 +86,13 @@ jobs:
6086 labels: ['api-review']
6187 });
6288
63- console.log(`Detected ${baselineFiles.length} changed baseline file(s).`);
89+ console.log(`Detected ${baselineFiles.length} changed baseline file(s) on PR #${prNumber} .`);
6490
65- - name : Check out merged commit
91+ - name : Check out selected commit
6692 if : steps.detect.outputs.has_baselines == 'true'
6793 uses : actions/checkout@v4
6894 with :
69- ref : ${{ github.event.pull_request.merge_commit_sha }}
95+ ref : ${{ steps.detect.outputs.target_sha }}
7096 fetch-depth : 1
7197
7298 - name : Restore repo-local .NET SDK
82108 FILES_JSON : ${{ steps.detect.outputs.files_json }}
83109 OWNER : ${{ github.repository_owner }}
84110 REPO : ${{ github.event.repository.name }}
85- BASE_SHA : ${{ github.event.pull_request.base.sha }}
86- MERGE_SHA : ${{ github.event.pull_request.merge_commit_sha }}
111+ BASE_SHA : ${{ steps.detect.outputs.base_sha }}
112+ TARGET_SHA : ${{ steps.detect.outputs.target_sha }}
87113 run : |
88114 set -euo pipefail
89115
@@ -110,7 +136,7 @@ jobs:
110136 owner = os.environ['OWNER']
111137 repo = os.environ['REPO']
112138 base_sha = os.environ['BASE_SHA']
113- merge_sha = os.environ['MERGE_SHA ']
139+ target_sha = os.environ['TARGET_SHA ']
114140 dotnet = os.environ['DOTNET']
115141 apichief = os.environ['APICHIEF_PATH']
116142 workdir = pathlib.Path(os.environ['WORKDIR'])
@@ -144,7 +170,7 @@ jobs:
144170 delta_path = workdir / f'{index}.delta.json'
145171
146172 old_exists = download_file(base_sha, filename, old_path)
147- new_exists = download_file(merge_sha , filename, new_path)
173+ new_exists = download_file(target_sha , filename, new_path)
148174
149175 if old_exists and new_exists:
150176 result = subprocess.run(
@@ -166,18 +192,17 @@ jobs:
166192 f"### `{filename}`\n\n"
167193 f"<details>\n<summary>Show delta</summary>\n\n```json\n{delta_text}\n```\n</details>")
168194 elif new_exists:
169- sections.append(f"### `{filename}`\n\nThis baseline file was **added** in the merged PR.")
195+ sections.append(f"### `{filename}`\n\nThis baseline file was **added** in the selected PR.")
170196 elif old_exists:
171- sections.append(f"### `{filename}`\n\nThis baseline file was **removed** in the merged PR.")
197+ sections.append(f"### `{filename}`\n\nThis baseline file was **removed** in the selected PR.")
172198
173199 if not sections:
174200 sections.append('No API deltas were produced for the modified baseline files.')
175201
176202 body = (
177- '<!-- api-review-delta-comment -->\n'
178203 '## API review baseline changes\n\n'
179- 'This merged PR modified one or more `*.baseline.json` files. '
180- 'The deltas below were generated by `ApiChief` between the pre-merge and merged versions.\n\n'
204+ 'This PR modified one or more `*.baseline.json` files. '
205+ 'The deltas below were generated by `ApiChief` between the base and selected PR versions.\n\n'
181206 + '\n\n'.join(sections)
182207 )
183208
@@ -188,43 +213,25 @@ jobs:
188213 output.write(f'comment_path={comment_path}\n')
189214 PY
190215
191- - name : Upsert PR comment with delta
216+ - name : Create PR comment with delta
192217 if : steps.detect.outputs.has_baselines == 'true'
193218 uses : actions/github-script@v8
194219 env :
195220 COMMENT_PATH : ${{ steps.delta.outputs.comment_path }}
221+ PR_NUMBER : ${{ steps.detect.outputs.pr_number }}
196222 with :
197223 script : |
198224 const fs = require('fs');
199225 const owner = context.repo.owner;
200226 const repo = context.repo.repo;
201- const issue_number = context.payload.pull_request.number;
202- const marker = '<!-- api-review-delta-comment -->';
227+ const issue_number = Number(process.env.PR_NUMBER);
203228 const body = fs.readFileSync(process.env.COMMENT_PATH, 'utf8');
204229
205- const comments = await github.paginate(github. rest.issues.listComments, {
230+ await github.rest.issues.createComment( {
206231 owner,
207232 repo,
208233 issue_number,
209- per_page: 100
234+ body
210235 });
211236
212- const existing = comments.find(comment => comment.body?.includes(marker));
213-
214- if (existing) {
215- await github.rest.issues.updateComment({
216- owner,
217- repo,
218- comment_id: existing.id,
219- body
220- });
221- console.log(`Updated existing API review comment (${existing.id}).`);
222- } else {
223- await github.rest.issues.createComment({
224- owner,
225- repo,
226- issue_number,
227- body
228- });
229- console.log('Created new API review comment.');
230- }
237+ console.log(`Created new API review comment for PR #${issue_number}.`);
0 commit comments