Skip to content

Commit 013957c

Browse files
committed
feat: post release information to PR
1 parent d95c3e0 commit 013957c

3 files changed

Lines changed: 214 additions & 1 deletion

File tree

.github/workflows/pr.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,12 @@ jobs:
116116
if: steps.release-preview.outputs.has_changes == 'false'
117117
run: |
118118
echo "${{ steps.release-preview.outputs.preview }}" >> $GITHUB_STEP_SUMMARY
119+
120+
- name: Comment on PR with release preview
121+
if: github.event_name == 'pull_request'
122+
env:
123+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
124+
GH_REPO: ${{ github.repository }}
125+
PR_NUMBER: ${{ github.event.number }}
126+
run: |
127+
uv run python scripts/pr_comment.py

scripts/pr_comment.py

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
#!/usr/bin/env python3
2+
"""
3+
PR comment management for release previews.
4+
5+
This script creates or updates PR comments with release preview information
6+
when changes are detected in the monorepo using the GitHub CLI (gh).
7+
8+
Usage:
9+
python scripts/pr_comment.py [--pr-number PR_NUMBER]
10+
11+
Environment Variables:
12+
GITHUB_TOKEN - GitHub token for API access (used by gh CLI)
13+
GH_REPO - Repository in format owner/repo (used by gh CLI)
14+
PR_NUMBER - Pull request number
15+
"""
16+
17+
import argparse
18+
import json
19+
import os
20+
import subprocess
21+
import sys
22+
import tempfile
23+
from pathlib import Path
24+
25+
26+
def run_command(cmd: list[str], cwd: str | None = None) -> tuple[int, str, str]:
27+
"""Run a command and return exit code, stdout, stderr."""
28+
try:
29+
result = subprocess.run(
30+
cmd, capture_output=True, text=True, cwd=cwd
31+
)
32+
return result.returncode, result.stdout.strip(), result.stderr.strip()
33+
except Exception as e:
34+
return 1, "", str(e)
35+
36+
37+
def check_gh_cli() -> bool:
38+
"""Check if gh CLI is available."""
39+
exit_code, _, _ = run_command(["gh", "--version"])
40+
return exit_code == 0
41+
42+
43+
def get_existing_comment_id(pr_number: int) -> str | None:
44+
"""Find existing release preview comment ID using gh CLI."""
45+
cmd = ["gh", "pr", "view", str(pr_number), "--json", "comments"]
46+
47+
exit_code, stdout, stderr = run_command(cmd)
48+
49+
if exit_code != 0:
50+
raise Exception(f"Failed to get PR comments: {stderr}")
51+
52+
try:
53+
pr_data = json.loads(stdout)
54+
comments = pr_data.get("comments", [])
55+
56+
# Look for comments with our signature
57+
for comment in comments:
58+
if "This comment was automatically generated by the release preview workflow" in comment.get("body", ""):
59+
return comment.get("id")
60+
61+
return None
62+
except json.JSONDecodeError as e:
63+
raise Exception(f"Failed to parse PR comments JSON: {e}") from e
64+
65+
66+
def get_release_preview() -> str:
67+
"""Get the release preview using the version preview script."""
68+
exit_code, stdout, stderr = run_command([
69+
"uv", "run", "python", "scripts/version_preview.py", "--format", "github-summary"
70+
])
71+
72+
if exit_code != 0:
73+
raise Exception(f"Failed to get release preview: {stderr}")
74+
75+
return stdout
76+
77+
78+
def get_changelog_preview() -> str:
79+
"""Get the changelog preview using just command."""
80+
exit_code, stdout, stderr = run_command([
81+
"just", "preview-changelog"
82+
])
83+
84+
if exit_code != 0:
85+
raise Exception(f"Failed to get changelog preview: {stderr}")
86+
87+
return stdout
88+
89+
90+
def has_changes() -> bool:
91+
"""Check if there are any packages with changes."""
92+
exit_code, stdout, stderr = run_command([
93+
"uv", "run", "python", "scripts/version_preview.py", "--format", "json"
94+
])
95+
96+
if exit_code != 0:
97+
return False
98+
99+
try:
100+
changes = json.loads(stdout)
101+
return len(changes) > 0
102+
except json.JSONDecodeError:
103+
return False
104+
105+
106+
def create_comment_body() -> str:
107+
"""Create the full comment body with release preview and changelog."""
108+
release_preview = get_release_preview()
109+
changelog_preview = get_changelog_preview()
110+
111+
return f"""{release_preview}
112+
113+
### 📝 Changelog Preview
114+
115+
```
116+
{changelog_preview}
117+
```
118+
119+
---
120+
*This comment was automatically generated by the release preview workflow.*"""
121+
122+
123+
def create_or_update_comment(pr_number: int, comment_body: str) -> None:
124+
"""Create a new comment or update existing one using gh CLI."""
125+
if not check_gh_cli():
126+
raise Exception("gh CLI is not available. Please install GitHub CLI.")
127+
128+
# Create temporary file with comment body
129+
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
130+
f.write(comment_body)
131+
temp_file = f.name
132+
133+
try:
134+
# Check if we have an existing comment to update
135+
existing_comment_id = get_existing_comment_id(pr_number)
136+
137+
if existing_comment_id:
138+
# Update existing comment
139+
cmd = ["gh", "api", f"repos/{{owner}}/{{repo}}/issues/comments/{existing_comment_id}",
140+
"--method", "PATCH", "--field", f"body=@{temp_file}"]
141+
142+
exit_code, stdout, stderr = run_command(cmd)
143+
144+
if exit_code != 0:
145+
raise Exception(f"Failed to update comment: {stderr}")
146+
147+
print(f"Updated existing comment {existing_comment_id}")
148+
else:
149+
# Create new comment using gh pr comment
150+
cmd = ["gh", "pr", "comment", str(pr_number), "--body-file", temp_file]
151+
152+
exit_code, stdout, stderr = run_command(cmd)
153+
154+
if exit_code != 0:
155+
raise Exception(f"Failed to create comment: {stderr}")
156+
157+
print("Created new comment")
158+
finally:
159+
# Clean up temporary file
160+
try:
161+
Path(temp_file).unlink()
162+
except Exception:
163+
pass # Ignore cleanup errors
164+
165+
166+
def main():
167+
"""Main function with argument parsing."""
168+
parser = argparse.ArgumentParser(
169+
description="Create or update PR comment with release preview"
170+
)
171+
parser.add_argument(
172+
"--pr-number",
173+
type=int,
174+
help="Pull request number (can also use PR_NUMBER env var)"
175+
)
176+
177+
args = parser.parse_args()
178+
179+
# Get configuration from args or environment
180+
pr_number = args.pr_number or os.getenv("PR_NUMBER")
181+
182+
if not pr_number:
183+
print("Error: PR number not provided via --pr-number or PR_NUMBER env var", file=sys.stderr)
184+
sys.exit(1)
185+
186+
try:
187+
# Check if there are any changes
188+
if not has_changes():
189+
print("No changes detected, skipping PR comment")
190+
return
191+
192+
# Create comment body
193+
comment_body = create_comment_body()
194+
195+
# Create or update comment
196+
create_or_update_comment(int(pr_number), comment_body)
197+
198+
except Exception as e:
199+
print(f"Error: {e}", file=sys.stderr)
200+
sys.exit(1)
201+
202+
203+
if __name__ == "__main__":
204+
main()

scripts/version_preview.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def format_as_github_summary(version_info: list[dict[str, str]]) -> str:
138138
return "ℹ️ No packages with unreleased changes detected."
139139

140140
output = ["## 📦 Release Preview", ""]
141-
output.append("This analysis shows the expected release impact:", "")
141+
output.extend(["This analysis shows the expected release impact:", ""])
142142

143143
# Version changes section
144144
output.extend(["### 📈 Expected Version Changes", "", "```"])

0 commit comments

Comments
 (0)