#!/usr/bin/env python3 """Cut a release for Moon Household Budget. Usage: python release.py "" "" ... \\ [--title "Short title"] [--date YYYY-MM-DD] Prepends a new entry to changelog.json (the single source of truth that drives __version__, the footer version chip, and the /changelog page) and regenerates CHANGELOG.md to match. Versioning: semver-ish — bump the patch (0.1.0 -> 0.1.1) for fixes, the minor (0.1.0 -> 0.2.0) for new features. Run this for EVERY deploy (see CLAUDE.md). """ import json import os import sys from datetime import date HERE = os.path.dirname(os.path.abspath(__file__)) CHANGELOG_JSON = os.path.join(HERE, "changelog.json") CHANGELOG_MD = os.path.join(HERE, "CHANGELOG.md") def load(): try: with open(CHANGELOG_JSON) as f: return json.load(f) except (OSError, json.JSONDecodeError): return [] def render_md(data): lines = [ "# Changelog", "", "All notable changes to Moon Household Budget. Newest first.", "Generated from changelog.json by release.py — do not edit by hand.", "", ] for rel in data: head = f"## {rel['version']} — {rel.get('date', '')}" if rel.get("title"): head += f" — {rel['title']}" lines += [head, ""] lines += [f"- {c}" for c in rel.get("changes", [])] lines.append("") return "\n".join(lines) def main(argv): if len(argv) < 2: print(__doc__) return 1 version = argv[1] title, rel_date, changes = "", date.today().isoformat(), [] i = 2 while i < len(argv): if argv[i] == "--title" and i + 1 < len(argv): title = argv[i + 1]; i += 2 elif argv[i] == "--date" and i + 1 < len(argv): rel_date = argv[i + 1]; i += 2 else: changes.append(argv[i]); i += 1 if not changes: print("error: provide at least one change bullet") return 1 data = load() if data and data[0].get("version") == version: print(f"error: version {version} is already the latest entry") return 1 entry = {"version": version, "date": rel_date} if title: entry["title"] = title entry["changes"] = changes data.insert(0, entry) with open(CHANGELOG_JSON, "w") as f: json.dump(data, f, indent=2) f.write("\n") with open(CHANGELOG_MD, "w") as f: f.write(render_md(data)) print(f"released v{version} ({rel_date}) with {len(changes)} change(s)") print(" -> changelog.json + CHANGELOG.md updated. Now commit + deploy.") return 0 if __name__ == "__main__": sys.exit(main(sys.argv))