caa0001922
- Add MiniJinja dependency for Jinja2-compatible templating - Create new project_template.rs module for template rendering - Bundle snowflake template in templates/snowflake/ directory - Remove dependency on external cookiecutter Python tool - Handle Gitea publishing natively in Rust instead of Python hooks - Update default template path to use bundled template with fallback - Update README to reflect new built-in templating - All tests pass Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
79 lines
2.9 KiB
Python
79 lines
2.9 KiB
Python
"""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)
|