Files
soleprint/init/cli.py
2026-04-12 12:34:25 -03:00

172 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""
spr init — CLI room scaffolding wizard
Interactive terminal wizard that walks through layers 0-6,
generating cfg/<room>/ for a new soleprint room.
Usage:
python -m init.cli myroom # Interactive wizard
python -m init.cli myroom --from sample # Clone existing room as variant
"""
import argparse
import logging
import sys
from init.core import (
CFG_DIR,
BACKEND_FRAMEWORKS,
FRONTEND_FRAMEWORKS,
clone_room,
generate_layer0,
generate_layer1,
generate_layer2,
generate_layer3,
generate_layer4,
generate_layer5,
generate_layer6,
next_free_port,
)
log = logging.getLogger("init")
# ---------------------------------------------------------------------------
# Terminal prompts
# ---------------------------------------------------------------------------
def ask(prompt: str, default: str = "") -> str:
try:
if default:
val = input(f"{prompt} [{default}]: ").strip()
return val or default
return input(f"{prompt}: ").strip()
except EOFError:
return default or ""
def ask_yn(prompt: str, default: bool = True) -> bool:
suffix = "[Y/n]" if default else "[y/N]"
try:
val = input(f"{prompt} {suffix}: ").strip().lower()
except EOFError:
return default
if not val:
return default
return val in ("y", "yes")
def ask_choice(prompt: str, choices: list[str], default: str = "") -> str:
choices_str = "/".join(choices)
try:
if default:
val = input(f"{prompt} [{choices_str}] ({default}): ").strip().lower()
return val if val in choices else default
while True:
val = input(f"{prompt} [{choices_str}]: ").strip().lower()
if val in choices:
return val
log.warning(" Choose one of: %s", choices_str)
except EOFError:
return default or choices[0]
# ---------------------------------------------------------------------------
# Wizard
# ---------------------------------------------------------------------------
def wizard(room: str):
room_dir = CFG_DIR / room
if room_dir.exists():
log.error("'%s' already exists in cfg/", room)
sys.exit(1)
port = next_free_port()
log.info("\n=== Soleprint Room Setup: %s ===", room)
log.info(" Target: cfg/%s/", room)
room_type = ask_choice("Room type?", ["standalone", "managed"], "standalone")
port = int(ask("Hub port", str(port)))
is_managed = room_type == "managed"
# Layer 0: always
config = generate_layer0(room_dir, room, port, is_managed)
# Layer 1: docker
if ask_yn("\nContinue to Layer 1 (Docker)?"):
generate_layer1(room_dir, room, port)
else:
log.info("Done! Build with: python build.py --cfg %s", room)
return
app_info = None
# Layer 2: managed app
if is_managed:
if ask_yn("\nContinue to Layer 2 (Managed App)?"):
app_name = ask("App name", room)
backend_path = ask("Backend repo path")
backend_framework = ask_choice("Backend framework?", BACKEND_FRAMEWORKS, "django")
frontend_path = ask("Frontend repo path [skip]")
has_frontend = bool(frontend_path) and frontend_path.lower() != "skip"
frontend_framework = None
if has_frontend:
frontend_framework = ask_choice("Frontend framework?", FRONTEND_FRAMEWORKS, "nextjs")
app_info = generate_layer2(
room_dir, room, config,
app_name, backend_path, backend_framework,
frontend_path if has_frontend else None,
frontend_framework,
)
else:
log.info("Done! Build with: python build.py --cfg %s", room)
return
# Layer 3: link
if ask_yn("\nContinue to Layer 3 (Link - DB Bridge)?", default=False):
generate_layer3(room_dir, room, app_info["backend_framework"])
# Layer 4: scripts
app_name = app_info["app_name"] if app_info else None
if ask_yn("\nContinue to Layer 4 (Scripts)?"):
generate_layer4(room_dir, room, app_name)
# Layer 5: tester
if ask_yn("\nContinue to Layer 5 (Test Suite)?", default=False):
url = ask("Test target URL", "http://localhost:8000")
generate_layer5(room_dir, room, url)
# Layer 6: nginx (only if managed with frontend)
if is_managed and app_info and app_info["has_frontend"]:
if ask_yn("\nContinue to Layer 6 (Nginx Sidebar Injection)?", default=False):
generate_layer6(room_dir, room, app_info["app_name"])
log.info("Ready! Build with: python build.py --cfg %s", room)
def main():
logging.basicConfig(level=logging.INFO, format="%(message)s")
parser = argparse.ArgumentParser(
description="Soleprint room scaffolding wizard",
usage="python -m init.cli <room> [--from <source>]",
)
parser.add_argument("room", help="Name of the new room")
parser.add_argument("--from", dest="from_room", help="Clone from existing room")
args = parser.parse_args()
if args.from_room:
clone_room(args.from_room, args.room)
else:
wizard(args.room)
if __name__ == "__main__":
main()