"""Post-generation hook: optionally publish the new project to Gitea. Cookiecutter renders this file as a Jinja template before running it, so a doubled opening brace anywhere outside a cookiecutter variable is read as the start of a Jinja expression and breaks the render. The JSON body below is therefore built with ``json.dumps`` rather than written out with literal braces. """ import json import os import subprocess import sys # This one comes from cookiecutter.json. PROJECT_NAME = "{{cookiecutter.project_name}}" GITEA_URL = os.environ.get("GITEA_URL") # e.g. https://gitea.example.com GITEA_USER = os.environ.get("GITEA_USER") GITEA_TOKEN = os.environ.get("GITEA_TOKEN") def run(cmd, **kwargs): """Run a command given as an argument list, raising if it fails.""" return subprocess.run(cmd, check=True, **kwargs) def create_gitea_repo(): # 1. Create the repo via the Gitea API. `--fail` makes curl exit non-zero on # an HTTP error, so a bad token surfaces here instead of silently # "succeeding" and leaving a push to fail later. api_url = f"{GITEA_URL.rstrip('/')}/api/v1/user/repos" payload = json.dumps({"name": PROJECT_NAME, "private": False, "auto_init": False}) run([ "curl", "-sS", "--fail", "-X", "POST", api_url, "-H", "Content-Type: application/json", "-H", f"Authorization: token {GITEA_TOKEN}", "-d", payload, ]) # 2. Init git and push. `-b main` matters because the push below names # `main`, while git's built-in default is still `master`. repo_url = f"{GITEA_URL.rstrip('/')}/{GITEA_USER}/{PROJECT_NAME}.git" auth_url = repo_url.replace("://", f"://{GITEA_USER}:{GITEA_TOKEN}@", 1) run(["git", "init", "-b", "main"]) run(["git", "add", "."]) run(["git", "commit", "-m", "Initial commit from cookiecutter"]) # The stored remote keeps the plain URL: embedding the token would write it # in cleartext into .git/config for the life of the project. It is passed to # this one push only. run(["git", "remote", "add", "origin", repo_url]) run(["git", "push", auth_url, "main"]) run(["git", "fetch", "origin"]) run(["git", "branch", "--set-upstream-to=origin/main", "main"]) print(f"✅ Repo created and pushed: {repo_url}") if __name__ == "__main__": missing = [ name for name, value in ( ("GITEA_URL", GITEA_URL), ("GITEA_USER", GITEA_USER), ("GITEA_TOKEN", GITEA_TOKEN), ) if not value ] if missing: # Publishing is opt-in. Without credentials the project is still # perfectly good on disk, so skip rather than fail the generation and # have cookiecutter throw the whole tree away. print(f"ℹ Skipping Gitea publish; not set: {', '.join(missing)}") sys.exit(0) try: create_gitea_repo() except subprocess.CalledProcessError as e: print(f"❌ Error: {e}", file=sys.stderr) sys.exit(1)