Skip to content

Commit d95c3e0

Browse files
committed
feat: preview version bumps
1 parent 4a7b5f0 commit d95c3e0

File tree

3 files changed

+241
-20
lines changed

3 files changed

+241
-20
lines changed

.github/workflows/pr.yml

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
branches:
66
- main
77

8+
permissions:
9+
contents: read
10+
811
jobs:
912
validate-commits:
1013
runs-on: ubuntu-latest
@@ -80,41 +83,36 @@ jobs:
8083
- name: Install dependencies
8184
run: uv sync --all-extras
8285

83-
- name: Detect packages with changes
84-
id: detect-changes
86+
- name: Generate release preview
87+
id: release-preview
8588
run: |
86-
changes=$(just detect-changes)
87-
echo "changes<<EOF" >> $GITHUB_OUTPUT
88-
echo "$changes" >> $GITHUB_OUTPUT
89+
# Get version preview information using the Python script
90+
version_preview=$(uv run python scripts/version_preview.py --format github-summary)
91+
92+
echo "preview<<EOF" >> $GITHUB_OUTPUT
93+
echo "$version_preview" >> $GITHUB_OUTPUT
8994
echo "EOF" >> $GITHUB_OUTPUT
9095
91-
# Check if there are any changes
96+
# Check if there are any changes by looking for version information
97+
changes=$(uv run python scripts/version_preview.py --format json)
9298
if [ "$changes" = "[]" ] || [ -z "$changes" ]; then
9399
echo "has_changes=false" >> $GITHUB_OUTPUT
94100
else
95101
echo "has_changes=true" >> $GITHUB_OUTPUT
96102
fi
97103
98-
- name: Preview changelog
99-
if: steps.detect-changes.outputs.has_changes == 'true'
104+
- name: Display release preview
105+
if: steps.release-preview.outputs.has_changes == 'true'
100106
run: |
101-
echo "## 📦 Release Preview" >> $GITHUB_STEP_SUMMARY
102-
echo "" >> $GITHUB_STEP_SUMMARY
103-
echo "This PR will trigger releases for the following packages:" >> $GITHUB_STEP_SUMMARY
107+
echo "${{ steps.release-preview.outputs.preview }}" >> $GITHUB_STEP_SUMMARY
104108
echo "" >> $GITHUB_STEP_SUMMARY
105-
echo '```json' >> $GITHUB_STEP_SUMMARY
106-
echo '${{ steps.detect-changes.outputs.changes }}' >> $GITHUB_STEP_SUMMARY
107-
echo '```' >> $GITHUB_STEP_SUMMARY
108-
echo "" >> $GITHUB_STEP_SUMMARY
109-
echo "### Changelog Preview" >> $GITHUB_STEP_SUMMARY
109+
echo "### 📝 Changelog Preview" >> $GITHUB_STEP_SUMMARY
110110
echo "" >> $GITHUB_STEP_SUMMARY
111111
echo '```' >> $GITHUB_STEP_SUMMARY
112112
just preview-changelog >> $GITHUB_STEP_SUMMARY
113113
echo '```' >> $GITHUB_STEP_SUMMARY
114114
115115
- name: No release preview
116-
if: steps.detect-changes.outputs.has_changes == 'false'
116+
if: steps.release-preview.outputs.has_changes == 'false'
117117
run: |
118-
echo "## 📦 Release Preview" >> $GITHUB_STEP_SUMMARY
119-
echo "" >> $GITHUB_STEP_SUMMARY
120-
echo "ℹ️ This PR does not contain changes that would trigger a new release." >> $GITHUB_STEP_SUMMARY
118+
echo "${{ steps.release-preview.outputs.preview }}" >> $GITHUB_STEP_SUMMARY

justfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ preview-changelog BASE_BRANCH="origin/main":
5858
changelog-preview BASE_BRANCH="origin/main":
5959
uv run python scripts/changelog.py preview {{BASE_BRANCH}}
6060

61+
# Preview expected version changes for packages with unreleased changes
62+
preview-versions FORMAT="markdown":
63+
uv run python scripts/version_preview.py --format {{FORMAT}}
64+
6165
# Detect packages with unreleased changes
6266
detect-changes:
6367
uv run python scripts/changelog.py changes --output-format github

scripts/version_preview.py

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Version preview tool for workspace packages.
4+
5+
This script analyzes packages with unreleased changes and provides
6+
version bump previews using commitizen.
7+
8+
Usage:
9+
python scripts/version_preview.py [--format json|markdown|github-summary]
10+
11+
Formats:
12+
json - JSON output suitable for programmatic use
13+
markdown - Markdown output for documentation
14+
github-summary - GitHub Actions summary format (default)
15+
"""
16+
17+
import argparse
18+
import json
19+
import subprocess
20+
import sys
21+
22+
23+
def run_command(cmd: list[str], cwd: str | None = None) -> tuple[int, str, str]:
24+
"""Run a command and return exit code, stdout, stderr."""
25+
try:
26+
result = subprocess.run(
27+
cmd, capture_output=True, text=True, cwd=cwd
28+
)
29+
return result.returncode, result.stdout.strip(), result.stderr.strip()
30+
except Exception as e:
31+
return 1, "", str(e)
32+
33+
34+
def get_changed_packages() -> list[dict[str, str]]:
35+
"""Get packages with unreleased changes using the existing changelog script."""
36+
exit_code, stdout, stderr = run_command([
37+
"uv", "run", "python", "scripts/changelog.py", "changes", "--output-format", "json"
38+
])
39+
40+
if exit_code != 0:
41+
raise Exception(f"Failed to detect changed packages: {stderr}")
42+
43+
if not stdout.strip():
44+
return []
45+
46+
try:
47+
return json.loads(stdout)
48+
except json.JSONDecodeError as e:
49+
raise Exception(f"Failed to parse package changes JSON: {e}") from e
50+
51+
52+
def get_version_info(package_dir: str, package_name: str) -> dict[str, str]:
53+
"""Get version information for a package using commitizen."""
54+
exit_code, stdout, stderr = run_command([
55+
"uv", "run", "cz", "bump", "--dry-run"
56+
], cwd=package_dir)
57+
58+
result = {
59+
"package_name": package_name,
60+
"package_dir": package_dir,
61+
"has_changes": False,
62+
"current_version": None,
63+
"next_version": None,
64+
"increment": None,
65+
"error": None
66+
}
67+
68+
if exit_code != 0:
69+
result["error"] = f"Failed to get version info: {stderr}"
70+
return result
71+
72+
if not stdout.strip():
73+
result["error"] = "No output from commitizen"
74+
return result
75+
76+
# Parse commitizen output
77+
lines = stdout.split('\n')
78+
for line in lines:
79+
if "→" in line and line.strip().startswith("bump:"):
80+
# Example: "bump: keycardai-oauth 0.1.0 → 0.2.0"
81+
parts = line.split()
82+
if len(parts) >= 5 and "→" in parts:
83+
arrow_index = parts.index("→")
84+
if arrow_index >= 1:
85+
result["current_version"] = parts[arrow_index - 1]
86+
result["next_version"] = parts[arrow_index + 1]
87+
result["has_changes"] = True
88+
elif line.strip().startswith("increment detected:"):
89+
# Example: "increment detected: MINOR"
90+
parts = line.split(":")
91+
if len(parts) >= 2:
92+
result["increment"] = parts[1].strip()
93+
94+
return result
95+
96+
97+
def format_as_json(version_info: list[dict[str, str]]) -> str:
98+
"""Format version information as JSON."""
99+
return json.dumps(version_info, indent=2)
100+
101+
102+
def format_as_markdown(version_info: list[dict[str, str]]) -> str:
103+
"""Format version information as Markdown."""
104+
if not version_info:
105+
return "No packages with unreleased changes detected."
106+
107+
output = ["# Release Preview", ""]
108+
109+
# Version changes section
110+
output.extend(["## Expected Version Changes", ""])
111+
for info in version_info:
112+
if info["has_changes"]:
113+
output.append(f"- **{info['package_name']}**: {info['current_version']}{info['next_version']} ({info['increment']})")
114+
elif info["error"]:
115+
output.append(f"- **{info['package_name']}**: Error - {info['error']}")
116+
else:
117+
output.append(f"- **{info['package_name']}**: No version change detected")
118+
119+
output.append("")
120+
121+
# Package details section
122+
output.extend(["## Package Details", ""])
123+
for info in version_info:
124+
output.append(f"- **Package**: {info['package_name']}")
125+
output.append(f" - **Directory**: {info['package_dir']}")
126+
if info["has_changes"]:
127+
output.append(f" - **Current Version**: {info['current_version']}")
128+
output.append(f" - **Next Version**: {info['next_version']}")
129+
output.append(f" - **Increment Type**: {info['increment']}")
130+
output.append("")
131+
132+
return "\n".join(output)
133+
134+
135+
def format_as_github_summary(version_info: list[dict[str, str]]) -> str:
136+
"""Format version information for GitHub Actions summary."""
137+
if not version_info:
138+
return "ℹ️ No packages with unreleased changes detected."
139+
140+
output = ["## 📦 Release Preview", ""]
141+
output.append("This analysis shows the expected release impact:", "")
142+
143+
# Version changes section
144+
output.extend(["### 📈 Expected Version Changes", "", "```"])
145+
for info in version_info:
146+
if info["has_changes"]:
147+
output.append(f"{info['package_name']}: {info['current_version']}{info['next_version']} ({info['increment']})")
148+
elif info["error"]:
149+
output.append(f"{info['package_name']}: Error - {info['error']}")
150+
else:
151+
output.append(f"{info['package_name']}: No version change detected")
152+
output.extend(["```", ""])
153+
154+
# Package details section
155+
output.extend(["### 📋 Package Details", "", "```json"])
156+
package_details = []
157+
for info in version_info:
158+
package_details.append({
159+
"package_name": info["package_name"],
160+
"package_dir": info["package_dir"],
161+
"has_changes": info["has_changes"],
162+
"current_version": info["current_version"],
163+
"next_version": info["next_version"],
164+
"increment": info["increment"]
165+
})
166+
output.append(json.dumps(package_details, indent=2))
167+
output.extend(["```", ""])
168+
169+
return "\n".join(output)
170+
171+
172+
def main():
173+
"""Main function with argument parsing."""
174+
parser = argparse.ArgumentParser(
175+
description="Generate version preview for packages with unreleased changes"
176+
)
177+
parser.add_argument(
178+
"--format",
179+
choices=["json", "markdown", "github-summary"],
180+
default="github-summary",
181+
help="Output format (default: github-summary)"
182+
)
183+
184+
args = parser.parse_args()
185+
186+
try:
187+
# Get packages with changes
188+
changed_packages = get_changed_packages()
189+
190+
if not changed_packages:
191+
if args.format == "json":
192+
print("[]")
193+
elif args.format == "markdown":
194+
print("No packages with unreleased changes detected.")
195+
else: # github-summary
196+
print("ℹ️ No packages with unreleased changes detected.")
197+
return
198+
199+
# Get version information for each package
200+
version_info = []
201+
for package in changed_packages:
202+
info = get_version_info(package["package_dir"], package["package_name"])
203+
version_info.append(info)
204+
205+
# Format and output results
206+
if args.format == "json":
207+
print(format_as_json(version_info))
208+
elif args.format == "markdown":
209+
print(format_as_markdown(version_info))
210+
else: # github-summary
211+
print(format_as_github_summary(version_info))
212+
213+
except Exception as e:
214+
print(f"Error: {e}", file=sys.stderr)
215+
sys.exit(1)
216+
217+
218+
if __name__ == "__main__":
219+
main()

0 commit comments

Comments
 (0)