Files
landon caa0001922 feat: Replace cookiecutter with built-in MiniJinja templating
- 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>
2026-09-13 08:37:18 -05:00

79 lines
2.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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)