#!/usr/bin/env python3 import argparse import re import subprocess import sys import tempfile from pathlib import Path def parse_args(argv: list[str]) -> argparse.Namespace: parser = argparse.ArgumentParser(description="Create a tagged Codex release.") parser.add_argument( "version", help="Version string used for Cargo.toml and the Git tag (e.g. 0.1.0-alpha.4).", ) return parser.parse_args(argv[1:]) def main(argv: list[str]) -> int: args = parse_args(argv) try: with tempfile.TemporaryDirectory() as temp_dir: repo_dir = Path(temp_dir) / "codex" clone_repository(repo_dir) branch = current_branch(repo_dir) create_release(args.version, branch, repo_dir) except ReleaseError as error: print(f"ERROR: {error}", file=sys.stderr) return 1 return 0 def current_branch(repo_dir: Path) -> str: result = run_git( repo_dir, ["symbolic-ref", "--short", "-q", "HEAD"], capture_output=True, check=False, ) branch = result.stdout.strip() if result.returncode != 0 or not branch: raise ReleaseError("Could not determine the current branch (detached HEAD?).") return branch def update_version(version: str, cargo_toml: Path) -> None: content = cargo_toml.read_text(encoding="utf-8") new_content, matches = re.subn( r'^version = "[^"]+"', f'version = "{version}"', content, count=1, flags=re.MULTILINE ) if matches != 1: raise ReleaseError("Unable to update version in Cargo.toml.") cargo_toml.write_text(new_content, encoding="utf-8") def create_release(version: str, branch: str, repo_dir: Path) -> None: tag = f"rust-v{version}" run_git(repo_dir, ["checkout", "-b", tag]) try: update_version(version, repo_dir / "codex-rs" / "Cargo.toml") run_git(repo_dir, ["add", "codex-rs/Cargo.toml"]) run_git(repo_dir, ["commit", "-m", f"Release {version}"]) run_git(repo_dir, ["tag", "-a", tag, "-m", f"Release {version}"]) run_git(repo_dir, ["push", "origin", f"refs/tags/{tag}"]) finally: run_git(repo_dir, ["checkout", branch]) def clone_repository(destination: Path) -> None: result = subprocess.run( ["gh", "repo", "clone", "openai/codex", str(destination), "--", "--depth", "1"], text=True, ) if result.returncode != 0: raise ReleaseError("Failed to clone openai/codex using gh.") class ReleaseError(RuntimeError): pass def run_git( repo_dir: Path, args: list[str], *, capture_output: bool = False, check: bool = True, ) -> subprocess.CompletedProcess: result = subprocess.run( ["git", *args], cwd=repo_dir, text=True, capture_output=capture_output, ) if check and result.returncode != 0: stderr = result.stderr.strip() if result.stderr else "" stdout = result.stdout.strip() if result.stdout else "" message = stderr if stderr else stdout raise ReleaseError(message or f"git {' '.join(args)} failed") return result if __name__ == "__main__": sys.exit(main(sys.argv))