Skip to content

Commit 54153b2

Browse files
GeneAIclaude
authored andcommitted
feat: Add automated release scripts
Added complete release automation to streamline the release process: Scripts: - scripts/release.sh: One-command full release automation - Bumps version, updates changelog, commits, tags, and pushes - Interactive with confirmation prompts - ~30 seconds from command to PyPI publication - scripts/prepare_release.py: Version bump and changelog automation - Supports major, minor, patch, or specific versions - Interactive changelog entry with categories - Updates pyproject.toml and CHANGELOG.md - scripts/README.md: Complete documentation - Quick start guide - Step-by-step instructions - Semantic versioning guide - Troubleshooting tips Usage: ./scripts/release.sh patch # Full automation ./scripts/release.sh minor ./scripts/release.sh major Benefits: - Eliminates manual version bumping errors - Ensures consistent CHANGELOG format - Reduces release time from ~5 minutes to ~30 seconds - Automates entire flow: version → changelog → commit → tag → PyPI 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent ca09113 commit 54153b2

File tree

3 files changed

+438
-0
lines changed

3 files changed

+438
-0
lines changed

scripts/README.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Release Automation Scripts
2+
3+
Automated tools for creating new releases of empathy-framework.
4+
5+
## Quick Start
6+
7+
**One-command release:**
8+
```bash
9+
./scripts/release.sh patch # 1.6.4 → 1.6.5
10+
./scripts/release.sh minor # 1.6.4 → 1.7.0
11+
./scripts/release.sh major # 1.6.4 → 2.0.0
12+
```
13+
14+
This does everything:
15+
1. ✅ Bumps version in `pyproject.toml`
16+
2. ✅ Updates `CHANGELOG.md` (with prompts)
17+
3. ✅ Commits changes
18+
4. ✅ Pushes to GitHub
19+
5. ✅ Creates and pushes tag
20+
6. ✅ Triggers automation (GitHub Actions → PyPI)
21+
22+
**Total time: ~30 seconds from command to PyPI** 🚀
23+
24+
## Step-by-Step Mode
25+
26+
If you prefer manual control:
27+
28+
```bash
29+
# Step 1 & 2: Prepare release
30+
python3 scripts/prepare_release.py patch
31+
32+
# Step 3: Review and commit
33+
git diff
34+
git add pyproject.toml CHANGELOG.md
35+
git commit -m "release: Prepare vX.X.X"
36+
37+
# Step 4: Push
38+
git push
39+
40+
# Step 5: Tag and trigger automation
41+
git tag vX.X.X
42+
git push origin vX.X.X
43+
```
44+
45+
## Scripts
46+
47+
### `release.sh` - Full Automation
48+
Complete end-to-end release with confirmation prompts.
49+
50+
**Usage:**
51+
```bash
52+
./scripts/release.sh patch # Patch version bump (bug fixes)
53+
./scripts/release.sh minor # Minor version bump (new features)
54+
./scripts/release.sh major # Major version bump (breaking changes)
55+
./scripts/release.sh 1.7.0 # Specific version
56+
```
57+
58+
### `prepare_release.py` - Version & Changelog
59+
Updates version and CHANGELOG without committing.
60+
61+
**Usage:**
62+
```bash
63+
python3 scripts/prepare_release.py patch
64+
python3 scripts/prepare_release.py 1.7.0
65+
```
66+
67+
**Interactive Changelog Entry:**
68+
```
69+
📝 Changelog Entry
70+
==================================================
71+
Enter changelog items (one per line).
72+
Categories: Added, Changed, Fixed, Removed, Deprecated, Security
73+
Press Ctrl+D (Unix) or Ctrl+Z (Windows) when done.
74+
75+
>>> Added
76+
→ Category: Added
77+
>>> New API endpoint for batch processing
78+
✓ Added to Added
79+
>>> Changed
80+
→ Category: Changed
81+
>>> Improved error messages
82+
✓ Added to Changed
83+
>>> [Ctrl+D]
84+
```
85+
86+
## Semantic Versioning
87+
88+
- **Patch** (1.6.4 → 1.6.5): Bug fixes, documentation updates
89+
- **Minor** (1.6.4 → 1.7.0): New features, backward-compatible changes
90+
- **Major** (1.6.4 → 2.0.0): Breaking changes, major rewrites
91+
92+
## What Happens After Tag Push?
93+
94+
GitHub Actions automatically:
95+
1. **Builds** package (`python -m build`)
96+
2. **Validates** with `twine check`
97+
3. **Extracts** release notes from CHANGELOG.md
98+
4. **Creates** GitHub release with artifacts
99+
5. **Publishes** to PyPI
100+
101+
View progress:
102+
```bash
103+
gh run watch --workflow=release.yml
104+
```
105+
106+
## Troubleshooting
107+
108+
**Script won't run:**
109+
```bash
110+
chmod +x scripts/release.sh
111+
chmod +x scripts/prepare_release.py
112+
```
113+
114+
**Version conflict:**
115+
If tag already exists (immutable release), bump to next version:
116+
```bash
117+
./scripts/release.sh patch # Try again with next version
118+
```
119+
120+
**Changelog formatting:**
121+
The script accepts markdown formatting:
122+
```
123+
>>> - **Bold feature**: Description with details
124+
>>> - Bug fix for issue #123
125+
```
126+
127+
## Example Release Flow
128+
129+
```bash
130+
$ ./scripts/release.sh minor
131+
132+
🚀 Starting automated release process...
133+
134+
📝 Changelog Entry
135+
==================================================
136+
>>> Added
137+
>>> - New trajectory analysis visualization
138+
>>> - Support for custom LLM providers
139+
>>> Changed
140+
>>> - Improved memory retrieval performance (40% faster)
141+
>>> [Ctrl+D]
142+
143+
✓ Updated pyproject.toml to 1.7.0
144+
✓ Updated CHANGELOG.md with 1.7.0 entry
145+
146+
📋 Review the changes:
147+
[diff output]
148+
149+
Continue with release v1.7.0? (y/N): y
150+
151+
📦 Committing changes...
152+
⬆️ Pushing to remote...
153+
🏷️ Creating tag v1.7.0...
154+
155+
✅ Release v1.7.0 initiated!
156+
157+
🔄 GitHub Actions is now:
158+
1. Building the package
159+
2. Creating GitHub release
160+
3. Publishing to PyPI
161+
162+
⏱️ Should complete in ~30 seconds
163+
164+
View progress: gh run watch --workflow=release.yml
165+
View release: https://github.com/Smart-AI-Memory/empathy-framework/releases/tag/v1.7.0
166+
```
167+
168+
## Manual Changelog (Advanced)
169+
170+
To skip interactive prompts, edit `CHANGELOG.md` directly before running:
171+
172+
```bash
173+
# Edit CHANGELOG.md manually
174+
vim CHANGELOG.md
175+
176+
# Then just bump version
177+
python3 scripts/prepare_release.py 1.7.0
178+
179+
# Commit, tag, push
180+
git add . && git commit -m "release: v1.7.0"
181+
git tag v1.7.0 && git push && git push --tags
182+
```

scripts/prepare_release.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Release Preparation Script
4+
Automates version bumping and CHANGELOG updates for empathy-framework releases.
5+
6+
Usage:
7+
python scripts/prepare_release.py patch # 1.6.4 -> 1.6.5
8+
python scripts/prepare_release.py minor # 1.6.4 -> 1.7.0
9+
python scripts/prepare_release.py major # 1.6.4 -> 2.0.0
10+
python scripts/prepare_release.py 1.7.0 # Specific version
11+
"""
12+
13+
import re
14+
import sys
15+
from datetime import datetime
16+
from pathlib import Path
17+
18+
19+
def get_current_version():
20+
"""Extract current version from pyproject.toml"""
21+
pyproject = Path("pyproject.toml")
22+
content = pyproject.read_text()
23+
match = re.search(r'^version = "([^"]+)"', content, re.MULTILINE)
24+
if not match:
25+
raise ValueError("Could not find version in pyproject.toml")
26+
return match.group(1)
27+
28+
29+
def bump_version(current: str, bump_type: str) -> str:
30+
"""Bump version based on type (major, minor, patch) or return specific version"""
31+
# If it's a specific version like "1.7.0", return it
32+
if re.match(r"^\d+\.\d+\.\d+$", bump_type):
33+
return bump_type
34+
35+
major, minor, patch = map(int, current.split("."))
36+
37+
if bump_type == "major":
38+
return f"{major + 1}.0.0"
39+
elif bump_type == "minor":
40+
return f"{major}.{minor + 1}.0"
41+
elif bump_type == "patch":
42+
return f"{major}.{minor}.{patch + 1}"
43+
else:
44+
raise ValueError(
45+
f"Invalid bump type: {bump_type}. Use major, minor, patch, or a version number."
46+
)
47+
48+
49+
def update_pyproject(new_version: str):
50+
"""Update version in pyproject.toml"""
51+
pyproject = Path("pyproject.toml")
52+
content = pyproject.read_text()
53+
updated = re.sub(
54+
r'^version = "[^"]+"', f'version = "{new_version}"', content, flags=re.MULTILINE
55+
)
56+
pyproject.write_text(updated)
57+
print(f"✓ Updated pyproject.toml to {new_version}")
58+
59+
60+
def get_changelog_entry():
61+
"""Prompt user for changelog entry"""
62+
print("\n📝 Changelog Entry")
63+
print("=" * 50)
64+
print("Enter changelog items (one per line).")
65+
print("Categories: Added, Changed, Fixed, Removed, Deprecated, Security")
66+
print("Press Ctrl+D (Unix) or Ctrl+Z (Windows) when done, or just Enter to skip.\n")
67+
68+
entries = {
69+
"Added": [],
70+
"Changed": [],
71+
"Fixed": [],
72+
"Removed": [],
73+
"Deprecated": [],
74+
"Security": [],
75+
}
76+
77+
current_category = None
78+
79+
try:
80+
while True:
81+
line = input(">>> ").strip()
82+
if not line:
83+
break
84+
85+
# Check if line is a category header
86+
if line in entries:
87+
current_category = line
88+
print(f" → Category: {current_category}")
89+
continue
90+
91+
# If no category selected, ask
92+
if current_category is None:
93+
print("\nSelect category:")
94+
for i, cat in enumerate(entries.keys(), 1):
95+
print(f" {i}. {cat}")
96+
choice = input("Enter number (or category name): ").strip()
97+
98+
if choice.isdigit():
99+
current_category = list(entries.keys())[int(choice) - 1]
100+
else:
101+
current_category = choice if choice in entries else "Changed"
102+
103+
entries[current_category].append(line)
104+
print(f" ✓ Added to {current_category}")
105+
except EOFError:
106+
pass
107+
108+
return entries
109+
110+
111+
def format_changelog_section(entries: dict) -> str:
112+
"""Format changelog entries into markdown"""
113+
sections = []
114+
for category, items in entries.items():
115+
if items:
116+
sections.append(f"\n### {category}")
117+
for item in items:
118+
# Add bullet if not present
119+
item = item if item.startswith("-") else f"- {item}"
120+
sections.append(item)
121+
122+
return "\n".join(sections) if sections else "\n### Changed\n- Version bump"
123+
124+
125+
def update_changelog(new_version: str, entry_text: str):
126+
"""Update CHANGELOG.md with new version entry"""
127+
changelog = Path("CHANGELOG.md")
128+
content = changelog.read_text()
129+
130+
today = datetime.now().strftime("%Y-%m-%d")
131+
new_entry = f"""## [{new_version}] - {today}
132+
{entry_text}
133+
134+
"""
135+
136+
# Insert after the header (before first ## entry)
137+
updated = re.sub(
138+
r"(# Changelog.*?and this project adheres to.*?\n\n)",
139+
rf"\1{new_entry}",
140+
content,
141+
flags=re.DOTALL,
142+
)
143+
144+
changelog.write_text(updated)
145+
print(f"✓ Updated CHANGELOG.md with {new_version} entry")
146+
147+
148+
def main():
149+
if len(sys.argv) != 2:
150+
print(__doc__)
151+
sys.exit(1)
152+
153+
bump_type = sys.argv[1]
154+
155+
try:
156+
# Get current version and calculate new version
157+
current_version = get_current_version()
158+
new_version = bump_version(current_version, bump_type)
159+
160+
print("\n🚀 Release Preparation")
161+
print(f"{'=' * 50}")
162+
print(f"Current version: {current_version}")
163+
print(f"New version: {new_version}")
164+
print()
165+
166+
# Update pyproject.toml
167+
update_pyproject(new_version)
168+
169+
# Get changelog entries
170+
entries = get_changelog_entry()
171+
entry_text = format_changelog_section(entries)
172+
173+
# Update CHANGELOG.md
174+
update_changelog(new_version, entry_text)
175+
176+
print(f"\n✅ Release {new_version} prepared!")
177+
print("\nNext steps:")
178+
print(" 1. Review changes: git diff")
179+
print(f" 2. Commit: git add . && git commit -m 'release: Prepare v{new_version}'")
180+
print(" 3. Push: git push")
181+
print(f" 4. Tag: git tag v{new_version} && git push origin v{new_version}")
182+
print(" 5. Automation handles the rest! 🎉")
183+
184+
except Exception as e:
185+
print(f"❌ Error: {e}", file=sys.stderr)
186+
sys.exit(1)
187+
188+
189+
if __name__ == "__main__":
190+
main()

0 commit comments

Comments
 (0)