fix: simplify release script (#81)

* fix: simplify release script to use gh auto-generated notes

- uses uv shebang instead of python3
- lets gh cli generate release notes with --generate-notes
- removes manual commit parsing and formatting
- adds emoji for better output
- rename from release.py to just 'release'

* redact

authored by zzstoatzz.io and committed by GitHub 4c040319 529e3096

Changed files
+75 -128
docs
scripts
src
backend
utilities
+1 -1
docs/deployment/database-migrations.md
··· 209 build docker image with migrations included 210 211 run migrations via google cloud run jobs 212 - ├─ separate job per database (orion, nebula, events) 213 ├─ jobs execute before app deployment 214 ├─ jobs block until migration completes 215 └─ deployment fails if migration fails
··· 209 build docker image with migrations included 210 211 run migrations via google cloud run jobs 212 + ├─ separate job per database (auth, background, events) 213 ├─ jobs execute before app deployment 214 ├─ jobs block until migration completes 215 └─ deployment fails if migration fails
+1 -1
justfile
··· 28 29 # create a github release (triggers production deployment) 30 release: 31 - uv run scripts/release.py
··· 28 29 # create a github release (triggers production deployment) 30 release: 31 + ./scripts/release
+72
scripts/release
···
··· 1 + #!/usr/bin/env -S uv run --script --quiet 2 + # /// script 3 + # requires-python = ">=3.12" 4 + # dependencies = [] 5 + # /// 6 + """create a github release using timestamp-based versioning 7 + 8 + version format: YYYY.MMDD.HHMMSS (e.g., 2025.1106.134523) 9 + 10 + usage: 11 + just release 12 + ./scripts/release 13 + """ 14 + 15 + import subprocess 16 + import sys 17 + from datetime import UTC, datetime 18 + 19 + 20 + def main() -> int: 21 + """create release with timestamp version.""" 22 + # ensure we're on main branch 23 + result = subprocess.run( 24 + ["git", "branch", "--show-current"], 25 + capture_output=True, 26 + text=True, 27 + check=True, 28 + ) 29 + current_branch = result.stdout.strip() 30 + 31 + if current_branch != "main": 32 + print(f"❌ must be on main branch (currently on {current_branch})") 33 + return 1 34 + 35 + # ensure working directory is clean 36 + result = subprocess.run( 37 + ["git", "status", "--porcelain"], 38 + capture_output=True, 39 + text=True, 40 + check=True, 41 + ) 42 + if result.stdout.strip(): 43 + print("❌ working directory has uncommitted changes") 44 + return 1 45 + 46 + version = datetime.now(UTC).strftime("%Y.%m%d.%H%M%S") 47 + 48 + print(f"🚀 creating release {version}") 49 + print("📝 release notes will be auto-generated by gh cli") 50 + 51 + # create release - gh will auto-generate notes from commits 52 + subprocess.run( 53 + [ 54 + "gh", 55 + "release", 56 + "create", 57 + version, 58 + "--title", 59 + version, 60 + "--generate-notes", 61 + ], 62 + check=True, 63 + ) 64 + 65 + print(f"✅ release {version} created") 66 + print("🚢 production deployment starting automatically...") 67 + 68 + return 0 69 + 70 + 71 + if __name__ == "__main__": 72 + sys.exit(main())
-125
scripts/release.py
··· 1 - #!/usr/bin/env python3 2 - """create a github release using timestamp-based versioning (nebula strategy). 3 - 4 - version format: YYYY.MMDD.HHMMSS (e.g., 2025.1106.134523) 5 - """ 6 - 7 - import subprocess 8 - import sys 9 - from datetime import UTC, datetime 10 - 11 - 12 - def get_timestamp_version() -> str: 13 - """generate version string using nebula's timestamp strategy.""" 14 - now = datetime.now(UTC) 15 - # format: YYYY.MMDD.HHMMSS 16 - return now.strftime("%Y.%m%d.%H%M%S") 17 - 18 - 19 - def get_recent_commits(since_tag: str | None = None) -> list[str]: 20 - """get commit messages since last tag or all recent commits.""" 21 - if since_tag: 22 - cmd = ["git", "log", f"{since_tag}..HEAD", "--oneline"] 23 - else: 24 - cmd = ["git", "log", "--oneline", "-20"] 25 - 26 - result = subprocess.run(cmd, capture_output=True, text=True, check=True) 27 - return [line for line in result.stdout.strip().split("\n") if line] 28 - 29 - 30 - def get_latest_tag() -> str | None: 31 - """get the most recent git tag.""" 32 - result = subprocess.run( 33 - ["git", "describe", "--tags", "--abbrev=0"], 34 - capture_output=True, 35 - text=True, 36 - ) 37 - return result.stdout.strip() if result.returncode == 0 else None 38 - 39 - 40 - def create_release(version: str, notes: str) -> None: 41 - """create github release using gh cli.""" 42 - cmd = [ 43 - "gh", 44 - "release", 45 - "create", 46 - version, 47 - "--title", 48 - version, 49 - "--notes", 50 - notes, 51 - ] 52 - 53 - subprocess.run(cmd, check=True) 54 - print(f"✓ created release {version}") 55 - print("✓ deployment to production will start automatically") 56 - 57 - 58 - def main() -> int: 59 - """main entry point.""" 60 - # ensure we're on main branch 61 - result = subprocess.run( 62 - ["git", "branch", "--show-current"], 63 - capture_output=True, 64 - text=True, 65 - check=True, 66 - ) 67 - current_branch = result.stdout.strip() 68 - 69 - if current_branch != "main": 70 - print(f"error: must be on main branch (currently on {current_branch})") 71 - return 1 72 - 73 - # ensure working directory is clean 74 - result = subprocess.run( 75 - ["git", "status", "--porcelain"], 76 - capture_output=True, 77 - text=True, 78 - check=True, 79 - ) 80 - if result.stdout.strip(): 81 - print("error: working directory has uncommitted changes") 82 - return 1 83 - 84 - # generate version 85 - version = get_timestamp_version() 86 - print(f"version: {version}") 87 - 88 - # get commits since last tag 89 - latest_tag = get_latest_tag() 90 - commits = get_recent_commits(latest_tag) 91 - 92 - if not commits: 93 - print("warning: no commits since last release") 94 - 95 - # generate release notes 96 - notes_lines = [] 97 - if latest_tag: 98 - notes_lines.append(f"changes since {latest_tag}:\n") 99 - else: 100 - notes_lines.append("initial production release\n") 101 - 102 - for commit in commits: 103 - notes_lines.append(f"- {commit}") 104 - 105 - notes = "\n".join(notes_lines) 106 - 107 - # show preview 108 - print("\nrelease notes:") 109 - print(notes) 110 - print() 111 - 112 - # confirm 113 - response = input(f"create release {version}? [y/N] ") 114 - if response.lower() != "y": 115 - print("cancelled") 116 - return 0 117 - 118 - # create release 119 - create_release(version, notes) 120 - 121 - return 0 122 - 123 - 124 - if __name__ == "__main__": 125 - sys.exit(main())
···
+1 -1
src/backend/utilities/database.py
··· 11 12 from backend.config import settings 13 14 - # per-event-loop engine cache (following nebula pattern) 15 ENGINES: dict[tuple[Any, ...], AsyncEngine] = {} 16 17
··· 11 12 from backend.config import settings 13 14 + # per-event-loop engine cache 15 ENGINES: dict[tuple[Any, ...], AsyncEngine] = {} 16 17