Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions devctl/commands/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,53 @@
from devctl.generators.scaffold_spring import generate_spring_resource
from devctl.orchestrator.scanner import detect_environment

app = typer.Typer(help="Ajoute des ressources au projet courant (Scaffolding).")
app = typer.Typer(help="Adds resources to the current project (Scaffolding).")


@app.command()
def resource(
name: str = typer.Argument(..., help="Le nom de la ressource (ex: Client, Produit)"),
name: str = typer.Argument(..., help="The name of the resource (e.g., Client, Product)"),
fields: str = typer.Option(
"", "--fields", "-f", help="Les champs au format 'nom:type, age:int'"
"", "--fields", "-f", help="Fields in the format 'name:type, age:int'"
),
):
"""
Scanne le dossier courant et génère une architecture métier adaptée.
Scans the current folder and generates a suitable business architecture.
"""
typer.echo("🔍 Analyse du contexte courant...")
typer.secho("🔍 Analyzing current context...", fg=typer.colors.CYAN)
env_state = detect_environment(".")

original_dir = os.getcwd()
project_detected = False # <-- On change le nom pour que ce soit plus logique
project_detected = False

if env_state["has_spring"]:
project_detected = True
typer.secho(
"🍃 Projet Spring Boot détecté. Lancement du générateur Java...", fg=typer.colors.GREEN
"🍃 Spring Boot project detected. Launching Java generator...", fg=typer.colors.GREEN
)
os.chdir(env_state["spring_path"])
try:
generate_spring_resource(name, fields)
except Exception as e:
typer.secho(f"❌ Erreur lors de la génération Spring : {e}", fg=typer.colors.RED)
typer.secho(f"❌ Error during Spring generation: {e}", fg=typer.colors.RED)
finally:
os.chdir(original_dir)

if env_state["has_angular"]:
project_detected = True
typer.secho(
"🅰️ Projet Angular détecté. Lancement du générateur TypeScript...", fg=typer.colors.CYAN
"🅰️ Angular project detected. Launching TypeScript generator...", fg=typer.colors.CYAN
)
try:
generate_angular_resource(name, fields, root_path=".")
except Exception as e:
typer.secho(f"❌ Erreur lors de la génération Angular : {e}", fg=typer.colors.RED)
typer.secho(f"❌ Error during Angular generation: {e}", fg=typer.colors.RED)

# Le message d'erreur ne s'affiche que s'il n'y a VRAIMENT aucun projet
# Error message only if NO project detected
if not project_detected:
typer.secho(
"❌ Impossible de déterminer le type de projet. "
"Place-toi dans ou au-dessus d'un projet Spring ou Angular.",
"❌ Unable to determine project type. "
"Please run from within a Spring or Angular project directory.",
fg=typer.colors.RED,
)
raise typer.Exit(code=1)
37 changes: 21 additions & 16 deletions devctl/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,61 @@
from devctl.generators.spring import download_spring_boilerplate
from devctl.generators.vue import generate_vue_boilerplate
from devctl.orchestrator.config_builder import generate_config
from devctl.utils.dependencies import check_tool

# L'application Typer locale pour le groupe de commandes "init"
app = typer.Typer(help="Initialise un nouveau projet selon le framework choisi.")
app = typer.Typer(help="Initializes a new project based on the chosen framework.")


@app.command("spring")
def init_spring(
name: str,
db: str = typer.Option("postgres", help="Type de base de données (postgres ou mysql)"),
port: int = typer.Option(None, help="Port local (optionnel)"),
db: str = typer.Option("postgres", help="Database type (postgres or mysql)"),
port: int = typer.Option(None, help="Local port (optional)"),
):
"""
Initialise un nouveau projet backend Spring Boot avec sa base de données.
Initializes a new Spring Boot backend project with its database.
"""
check_tool("java", "initializing a Spring Boot project")

# Validation stricte des entrées
if db not in ["postgres", "mysql"]:
typer.secho(
f"❌ Erreur : La base de données '{db}' n'est pas supportée.", fg=typer.colors.RED
)
typer.secho(f"❌ Error: Database '{db}' is not supported.", fg=typer.colors.RED)
raise typer.Exit(code=1)

typer.echo(f"🚀 Initialisation d'un projet Spring Boot : '{name}'...")
typer.secho(f"🚀 Initializing Spring Boot project: '{name}'...", fg=typer.colors.CYAN)

success_download = download_spring_boilerplate(name, db_type=db)

if success_download:
generate_config(name, db_type=db, custom_port=port)
typer.secho("\n✨ Projet Spring prêt !", fg=typer.colors.CYAN)
typer.secho("\n✨ Spring project ready!", fg=typer.colors.GREEN)


@app.command("angular")
def init_angular(name: str):
"""
(Bientôt) Initialise un nouveau projet frontend Angular.
Initializes a new Angular frontend project.
"""
# Pour l'instant, c'est juste un espace réservé (placeholder)
typer.echo(f"🚀 Initialisation d'un projet Angular : '{name}'...")
check_tool("npm", "initializing an Angular project")
check_tool("ng", "initializing an Angular project")

typer.secho(f"🚀 Initializing Angular project: '{name}'...", fg=typer.colors.CYAN)
success = generate_angular_boilerplate(name)

if success:
typer.secho("\n✨ Projet Angular prêt !", fg=typer.colors.CYAN)
typer.secho("\n✨ Angular project ready!", fg=typer.colors.GREEN)


@app.command("vue")
def init_vue(name: str):
"""
Initialise un nouveau projet frontend Vue.js (Vite + TS).
Initializes a new Vue.js frontend project (Vite + TS).
"""
typer.echo(f"🚀 Initialisation d'un projet Vue.js : '{name}'...")
check_tool("npm", "initializing a Vue.js project")

typer.secho(f"🚀 Initializing Vue.js project: '{name}'...", fg=typer.colors.CYAN)
success = generate_vue_boilerplate(name)

if success:
typer.secho("\n✨ Projet Vue.js prêt !", fg=typer.colors.CYAN)
typer.secho("\n✨ Vue.js project ready!", fg=typer.colors.GREEN)
29 changes: 19 additions & 10 deletions devctl/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,37 @@

from devctl.orchestrator.runner import launch_dev_environment
from devctl.orchestrator.scanner import detect_environment
from devctl.utils.dependencies import check_tool

app = typer.Typer(help="Commandes d'exécution et de développement local.")
app = typer.Typer(help="Local execution and development commands.")


@app.callback(invoke_without_command=True)
def run_env(ctx: typer.Context):
"""
Scanne l'arborescence courante et lance automatiquement Spring, Angular et la Base de données.
Scans the current tree and automatically launches Spring, Angular, and the Database.
"""
if ctx.invoked_subcommand is not None:
return

typer.echo("🔍 Analyse de l'arborescence courante...")
typer.secho("🔍 Analyzing the current directory tree...", fg=typer.colors.CYAN)
env_state = detect_environment(".")

# Check dependencies based on detection
if env_state["has_docker_compose"]:
check_tool("docker", "running the database environment")

if env_state["has_spring"]:
check_tool("java", "running the Spring Boot backend")

if env_state["has_angular"] or env_state.get("has_vue"):
check_tool("npm", "running the frontend project")

# Résumé visuel de la détection pour l'utilisateur
typer.echo(f" - Base de données Docker : {'✅' if env_state['has_docker_compose'] else '❌'}")
typer.echo(f" - Backend Spring Boot : {'✅' if env_state['has_spring'] else '❌'}")
typer.echo(f" - Frontend Angular : {'✅' if env_state['has_angular'] else '❌'}")
typer.echo(f" - Frontend Vue.js : {'✅' if env_state['has_vue'] else '❌'}")
typer.echo(f" - Docker Database : {'✅' if env_state['has_docker_compose'] else '❌'}")
typer.echo(f" - Spring Boot Backend : {'✅' if env_state['has_spring'] else '❌'}")
typer.echo(f" - Angular Frontend : {'✅' if env_state['has_angular'] else '❌'}")
typer.echo(f" - Vue.js Frontend : {'✅' if env_state['has_vue'] else '❌'}")

has_env = any(
[
Expand All @@ -33,9 +44,7 @@ def run_env(ctx: typer.Context):
)

if not has_env:
typer.secho(
"\n❌ Aucun environnement de développement valide détecté ici.", fg=typer.colors.RED
)
typer.secho("\n❌ No valid development environment detected here.", fg=typer.colors.RED)
raise typer.Exit(code=1)

# Transfert à la couche d'orchestration système
Expand Down
25 changes: 12 additions & 13 deletions devctl/generators/angular.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

def setup_angular_environments(project_path: str):
"""
Génère les environnements et le proxy pour un projet Angular.
Generates environments and proxy for an Angular project.
"""
typer.echo("⚙️ Configuration du Proxy et des Environnements...")
typer.secho("⚙️ Configuring Proxy and Environments...", fg=typer.colors.CYAN)

# 1. Chemins cibles
src_dir = os.path.join(project_path, "src")
Expand Down Expand Up @@ -70,30 +70,30 @@ def setup_angular_environments(project_path: str):

def generate_angular_boilerplate(project_name: str) -> bool:
"""
Génère un projet Angular via le CLI natif (@angular/cli) et le configure.
Generates an Angular project via the native CLI (@angular/cli) and configures it.
"""
typer.echo(f"🔄 Génération du frontend Angular '{project_name}'...")
typer.secho(f"🔄 Generating Angular frontend '{project_name}'...", fg=typer.colors.CYAN)

safe_name = project_name.lower().replace("_", "-")

try:
subprocess.run(["ng", "version"], capture_output=True, check=True)
except FileNotFoundError:
typer.secho(
"❌ Erreur : Le CLI Angular ('ng') est introuvable sur ton système.",
"❌ Error: Angular CLI ('ng') not found on your system.",
fg=typer.colors.RED,
)
return False
except subprocess.CalledProcessError:
typer.secho(
"❌ Erreur : Le CLI Angular est installé mais ne répond pas.", fg=typer.colors.RED
)
typer.secho("❌ Error: Angular CLI is installed but not responding.", fg=typer.colors.RED)
return False

try:
command = ["ng", "new", safe_name, "--routing=true", "--style=scss", "--skip-git=true"]

typer.echo("📦 Téléchargement des packages npm... (Cela peut prendre 1 à 2 minutes)")
typer.secho(
"📦 Downloading npm packages... (This may take 1-2 minutes)", fg=typer.colors.CYAN
)
subprocess.run(command, check=True)

# --- NOUVEAU : Appel de la configuration post-installation ---
Expand All @@ -102,12 +102,11 @@ def generate_angular_boilerplate(project_name: str) -> bool:
# -------------------------------------------------------------

typer.secho(
f"✅ Frontend '{safe_name}' généré et configuré avec succès !", fg=typer.colors.GREEN
f"✅ Frontend '{safe_name}' successfully generated and configured!",
fg=typer.colors.GREEN,
)
return True

except subprocess.CalledProcessError as e:
typer.secho(
f"❌ Le processus Angular a échoué avec le code : {e.returncode}", fg=typer.colors.RED
)
typer.secho(f"❌ Angular process failed with code: {e.returncode}", fg=typer.colors.RED)
return False
16 changes: 7 additions & 9 deletions devctl/generators/scaffold_angular.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def generate_angular_resource(resource_name: str, fields_str: str, root_path: st
env_state = detect_environment(root_path)

if not env_state["has_angular"]:
typer.secho("❌ Erreur : Aucun projet Angular détecté ici.", fg=typer.colors.RED)
typer.secho("❌ Error: No Angular project detected here.", fg=typer.colors.RED)
raise typer.Exit(code=1)

angular_root = env_state["angular_path"]
Expand Down Expand Up @@ -115,7 +115,7 @@ def generate_angular_resource(resource_name: str, fields_str: str, root_path: st
templates_dir = os.path.join(os.path.dirname(__file__), "..", "templates", "angular", "feature")
env = Environment(loader=FileSystemLoader(templates_dir))

typer.echo(f"⚙️ Génération de la feature Angular '{entity_name}'...")
typer.secho(f"⚙️ Generating Angular feature '{entity_name}'...", fg=typer.colors.CYAN)

# Données pour les templates
context = {
Expand All @@ -140,19 +140,17 @@ def generate_angular_resource(resource_name: str, fields_str: str, root_path: st
with open(os.path.join(target_dir, target_file_name), "w", encoding="utf-8") as f:
f.write(content)

display_dir = comp["dir"] if comp["dir"] else "racine feature"
typer.echo(f" - Créé : {display_dir}/{target_file_name}")
display_dir = comp["dir"] if comp["dir"] else "feature root"
typer.echo(f" - Created: {display_dir}/{target_file_name}")

except Exception as e:
# Astuce : si tu n'as pas créé de template .scss.j2, ça crée un fichier vide
# automatiquement
if comp["ext"] == ".scss":
with open(os.path.join(target_dir, target_file_name), "w") as f:
f.write("")
typer.echo(f" - Créé (vide) : {comp['dir']}/{target_file_name}")
typer.echo(f" - Created (empty): {comp['dir']}/{target_file_name}")
else:
typer.secho(f"⚠️ Erreur sur {comp['template']} : {e}", fg=typer.colors.YELLOW)
typer.secho(f"⚠️ Error on {comp['template']}: {e}", fg=typer.colors.YELLOW)

typer.secho(
f"✅ Feature {entity_name} générée avec succès côté Angular !", fg=typer.colors.GREEN
)
typer.secho(f"✅ {entity_name} feature successfully generated!", fg=typer.colors.GREEN)
23 changes: 12 additions & 11 deletions devctl/generators/scaffold_spring.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def generate_spring_resource(resource_name: str, fields_str: str):

if not base_package:
typer.secho(
"❌ Erreur : Impossible de trouver un projet Spring Boot valide ici.",
"❌ Error: Unable to find a valid Spring Boot project here.",
fg=typer.colors.RED,
)
raise typer.Exit(code=1)
Expand All @@ -84,7 +84,10 @@ def generate_spring_resource(resource_name: str, fields_str: str):
templates_dir = os.path.join(os.path.dirname(__file__), "..", "templates", "spring")
env = Environment(loader=FileSystemLoader(templates_dir))

typer.echo(f"⚙️ Génération de la ressource Spring '{entity_name}' (avec MapStruct & DTOs)...")
typer.secho(
f"⚙️ Generating Spring resource '{entity_name}' (with MapStruct & DTOs)...",
fg=typer.colors.CYAN,
)

for comp in components:
class_name = f"{entity_name}{comp['suffix']}"
Expand All @@ -110,23 +113,21 @@ def generate_spring_resource(resource_name: str, fields_str: str):
with open(os.path.join(target_dir, target_file_name), "w", encoding="utf-8") as f:
f.write(content)

typer.echo(f" - Créé : {comp['dir']}/{target_file_name}")
typer.echo(f" - Created: {comp['dir']}/{target_file_name}")

typer.secho(
f"✅ Architecture {entity_name} complète générée avec succès !", fg=typer.colors.GREEN
)
typer.secho(f"✅ {entity_name} architecture successfully generated!", fg=typer.colors.GREEN)


def generate_spring_security(_root_path: str = "."):
"""
Génère la base de sécurité JWT de manière dynamique.
Dynamically generates the JWT security base.
"""
# Réutilisation de ta fonction de détection dynamique
base_package, base_path = find_spring_base_package_and_path()

if not base_package:
typer.secho(
"❌ Erreur : Impossible de localiser le package Java pour la sécurité.",
"❌ Error: Unable to locate Java package for security.",
fg=typer.colors.RED,
)
return
Expand All @@ -146,14 +147,14 @@ def generate_spring_security(_root_path: str = "."):
"ApplicationConfig.java",
]

typer.echo(f"🛡️ Injection de la sécurité JWT dans {base_package}.config...")
typer.secho(f"🛡️ Injecting JWT security into {base_package}.config...", fg=typer.colors.CYAN)

for filename in security_files:
template = env.get_template(f"{filename}.j2")
content = template.render(base_package=base_package)

with open(os.path.join(target_dir, filename), "w", encoding="utf-8") as f:
f.write(content)
typer.echo(f" - Créé : config/{filename}")
typer.echo(f" - Created: config/{filename}")

typer.secho("✅ Sécurité initialisée avec succès !", fg=typer.colors.GREEN)
typer.secho("✅ Security initialized successfully!", fg=typer.colors.GREEN)
Loading
Loading