Skip to content

Commit ba7592d

Browse files
committed
feat: implement post-release status system
- Add update_release_notes() to append status to GitHub releases - Implement find_pr_for_sha() for PR detection - Prioritize release notes updates (critical) over PR comments (graceful) - Add comprehensive test coverage following TDD principles - Update create-release action to output release ID - Integrate with GitHub Actions workflows
1 parent f527c81 commit ba7592d

File tree

3 files changed

+569
-36
lines changed

3 files changed

+569
-36
lines changed

.github/actions/create-release/action.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ inputs:
1818
default: 'true'
1919

2020
outputs:
21+
release-id:
22+
description: 'ID of the created release'
23+
value: ${{ steps.create-release.outputs.id }}
2124
release-url:
2225
description: 'URL of the created release'
2326
value: ${{ steps.create-release.outputs.url }}
@@ -122,7 +125,7 @@ runs:
122125
path: dist/*.mcpb
123126
retention-days: 90
124127

125-
- name: Post release status to PR
128+
- name: Post release status
126129
shell: bash
127130
env:
128131
GITHUB_TOKEN: ${{ github.token }}
@@ -132,5 +135,6 @@ runs:
132135
--release-url "${{ steps.create-release.outputs.url }}" \
133136
--pypi-url "${{ steps.package-urls.outputs.pypi-url }}" \
134137
--docker-image "${{ steps.docker-info.outputs.image-uri }}" \
138+
--release-id "${{ steps.create-release.outputs.id }}" \
135139
--sha "${{ github.sha }}" \
136140
--repo "${{ github.repository }}"

scripts/post_release_status.py

Lines changed: 129 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
#!/usr/bin/env python3
22
"""
3-
Post release status comments to GitHub PRs.
3+
Post release status to GitHub releases and associated PRs.
44
5-
This script posts release information as a comment on associated PRs,
6-
including package locations, Docker images, and installation instructions.
5+
This script updates GitHub release notes with release information and optionally
6+
posts comments to associated PRs, including package locations, Docker images,
7+
and installation instructions.
8+
9+
PRIMARY: Always update GitHub release notes (failure = workflow failure)
10+
SECONDARY: Post PR comment when possible (failure = graceful continue)
711
"""
812

913
import argparse
@@ -53,7 +57,59 @@ def generate_release_comment(
5357
return body
5458

5559

56-
def find_pr_for_tag(github_token: str, repo: str, sha: str) -> Optional[int]:
60+
def update_release_notes(
61+
github_token: str,
62+
repo: str,
63+
release_id: str,
64+
additional_content: str,
65+
) -> bool:
66+
"""
67+
Update GitHub release notes by appending status information.
68+
69+
Args:
70+
github_token: GitHub API token
71+
repo: Repository in "owner/repo" format
72+
release_id: GitHub release ID
73+
additional_content: Formatted status content to append
74+
75+
Returns:
76+
True if successful, False otherwise
77+
"""
78+
import requests
79+
80+
headers = {
81+
"Authorization": f"token {github_token}",
82+
"Accept": "application/vnd.github.v3+json",
83+
}
84+
85+
# First, get the current release to preserve existing body
86+
get_url = f"https://api.github.com/repos/{repo}/releases/{release_id}"
87+
88+
try:
89+
response = requests.get(get_url, headers=headers)
90+
response.raise_for_status()
91+
92+
release_data = response.json()
93+
existing_body = release_data.get("body") or ""
94+
95+
# Append the additional content
96+
updated_body = existing_body + additional_content
97+
98+
# Update the release with the new body
99+
patch_url = f"https://api.github.com/repos/{repo}/releases/{release_id}"
100+
patch_data = {"body": updated_body}
101+
102+
response = requests.patch(patch_url, headers=headers, json=patch_data)
103+
response.raise_for_status()
104+
105+
return True
106+
107+
except requests.RequestException as e:
108+
print(f"Error updating release notes: {e}", file=sys.stderr)
109+
return False
110+
111+
112+
def find_pr_for_sha(github_token: str, repo: str, sha: str) -> Optional[int]:
57113
"""
58114
Find the PR number associated with a git SHA.
59115
@@ -89,6 +145,21 @@ def find_pr_for_tag(github_token: str, repo: str, sha: str) -> Optional[int]:
89145
return None
90146

91147

148+
def find_pr_for_tag(github_token: str, repo: str, sha: str) -> Optional[int]:
149+
"""
150+
Legacy alias for find_pr_for_sha for backward compatibility.
151+
152+
Args:
153+
github_token: GitHub API token
154+
repo: Repository in format "owner/repo"
155+
sha: Git commit SHA to search for
156+
157+
Returns:
158+
PR number if found, None otherwise
159+
"""
160+
return find_pr_for_sha(github_token, repo, sha)
161+
162+
92163
def post_comment_to_pr(
93164
github_token: str,
94165
repo: str,
@@ -129,7 +200,7 @@ def post_comment_to_pr(
129200
def main():
130201
"""Main entry point."""
131202
parser = argparse.ArgumentParser(
132-
description="Post release status to GitHub PRs"
203+
description="Post release status to GitHub releases and PRs"
133204
)
134205
parser.add_argument(
135206
"--version",
@@ -150,6 +221,10 @@ def main():
150221
"--docker-image",
151222
help="Docker image URI",
152223
)
224+
parser.add_argument(
225+
"--release-id",
226+
help="GitHub release ID for updating notes",
227+
)
153228
parser.add_argument(
154229
"--pr-number",
155230
type=int,
@@ -177,8 +252,8 @@ def main():
177252

178253
args = parser.parse_args()
179254

180-
# Generate the comment body
181-
comment_body = generate_release_comment(
255+
# Generate the status content
256+
status_content = generate_release_comment(
182257
version=args.version,
183258
release_url=args.release_url,
184259
pypi_url=args.pypi_url,
@@ -187,48 +262,67 @@ def main():
187262

188263
if args.dry_run:
189264
print("=== DRY RUN - Comment Body ===")
190-
print(comment_body)
265+
print(status_content)
191266
return 0
192267

193-
# Determine PR number
268+
# PRIMARY GOAL: Update release notes if release-id is provided
269+
if args.release_id:
270+
if not args.github_token:
271+
print("Error: GitHub token required to update release notes", file=sys.stderr)
272+
return 1
273+
274+
if not args.repo:
275+
print("Error: Repository required to update release notes", file=sys.stderr)
276+
return 1
277+
278+
# Format content for release notes (add separator)
279+
release_notes_content = f"\n---\n\n{status_content}"
280+
281+
success = update_release_notes(
282+
github_token=args.github_token,
283+
repo=args.repo,
284+
release_id=args.release_id,
285+
additional_content=release_notes_content,
286+
)
287+
288+
if not success:
289+
print("Failed to update release notes", file=sys.stderr)
290+
return 1 # CRITICAL: Release notes update failure should fail the workflow
291+
292+
print(f"Updated release notes for release {args.release_id}")
293+
294+
# SECONDARY GOAL: Post PR comment if possible (failure = graceful continue)
194295
pr_number = args.pr_number
195296

196297
if not pr_number and args.sha and args.github_token:
197298
# Try to find PR from SHA
198-
pr_number = find_pr_for_tag(
299+
pr_number = find_pr_for_sha(
199300
github_token=args.github_token,
200301
repo=args.repo,
201302
sha=args.sha,
202303
)
203304

204-
if not pr_number:
305+
if pr_number and args.github_token and args.repo:
306+
# Attempt to post PR comment
307+
pr_success = post_comment_to_pr(
308+
github_token=args.github_token,
309+
repo=args.repo,
310+
pr_number=pr_number,
311+
comment_body=status_content,
312+
)
313+
314+
if pr_success:
315+
print(f"Posted release status to PR #{pr_number}")
316+
else:
317+
print(f"Warning: Failed to post comment to PR #{pr_number}", file=sys.stderr)
318+
# Don't fail the workflow for PR comment failures
319+
elif not args.release_id:
320+
# Legacy behavior: if no release-id and no PR found, show status
205321
print("No PR found to comment on", file=sys.stderr)
206322
print("Release Status:")
207-
print(comment_body)
208-
return 0 # Not an error - just no PR to comment on
209-
210-
# Post the comment
211-
if not args.github_token:
212-
print("Error: GitHub token required to post comment", file=sys.stderr)
213-
return 1
214-
215-
if not args.repo:
216-
print("Error: Repository required to post comment", file=sys.stderr)
217-
return 1
218-
219-
success = post_comment_to_pr(
220-
github_token=args.github_token,
221-
repo=args.repo,
222-
pr_number=pr_number,
223-
comment_body=comment_body,
224-
)
323+
print(status_content)
225324

226-
if success:
227-
print(f"Posted release status to PR #{pr_number}")
228-
return 0
229-
else:
230-
print(f"Failed to post comment to PR #{pr_number}", file=sys.stderr)
231-
return 1
325+
return 0
232326

233327

234328
if __name__ == "__main__":

0 commit comments

Comments
 (0)