devctl 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- devctl/__init__.py +3 -0
- devctl/commands/__init__.py +3 -0
- devctl/commands/add.py +166 -0
- devctl/commands/deploy.py +61 -0
- devctl/commands/docker.py +65 -0
- devctl/commands/init.py +193 -0
- devctl/commands/run.py +67 -0
- devctl/generators/__init__.py +3 -0
- devctl/generators/angular.py +112 -0
- devctl/generators/django.py +61 -0
- devctl/generators/docker_scaffold.py +656 -0
- devctl/generators/fastapi.py +67 -0
- devctl/generators/go_fiber.py +61 -0
- devctl/generators/nestjs.py +49 -0
- devctl/generators/nextjs.py +53 -0
- devctl/generators/nodejs.py +109 -0
- devctl/generators/react.py +43 -0
- devctl/generators/scaffold_angular.py +163 -0
- devctl/generators/scaffold_django.py +79 -0
- devctl/generators/scaffold_fastapi.py +83 -0
- devctl/generators/scaffold_go.py +67 -0
- devctl/generators/scaffold_nestjs.py +52 -0
- devctl/generators/scaffold_nextjs.py +73 -0
- devctl/generators/scaffold_nodejs.py +81 -0
- devctl/generators/scaffold_react.py +80 -0
- devctl/generators/scaffold_spring.py +166 -0
- devctl/generators/scaffold_svelte.py +73 -0
- devctl/generators/scaffold_vue.py +111 -0
- devctl/generators/spring.py +221 -0
- devctl/generators/svelte.py +52 -0
- devctl/generators/vue.py +105 -0
- devctl/main.py +45 -0
- devctl/orchestrator/__init__.py +3 -0
- devctl/orchestrator/config_builder.py +64 -0
- devctl/orchestrator/runner.py +219 -0
- devctl/orchestrator/scanner.py +155 -0
- devctl/templates/angular/config/environment.development.ts.j2 +4 -0
- devctl/templates/angular/config/environment.ts.j2 +5 -0
- devctl/templates/angular/config/proxy.conf.json.j2 +8 -0
- devctl/templates/angular/feature/models/request.model.ts.j2 +5 -0
- devctl/templates/angular/feature/models/response.model.ts.j2 +6 -0
- devctl/templates/angular/feature/pages/form/form.component.html.j2 +21 -0
- devctl/templates/angular/feature/pages/form/form.component.scss.j2 +0 -0
- devctl/templates/angular/feature/pages/form/form.component.ts.j2 +63 -0
- devctl/templates/angular/feature/pages/list/list.component.html.j2 +28 -0
- devctl/templates/angular/feature/pages/list/list.component.scss.j2 +0 -0
- devctl/templates/angular/feature/pages/list/list.component.ts.j2 +34 -0
- devctl/templates/angular/feature/routes.ts.j2 +9 -0
- devctl/templates/angular/feature/services/service.ts.j2 +34 -0
- devctl/templates/docker/deploy.yml.j2 +37 -0
- devctl/templates/docker/django/Dockerfile.j2 +21 -0
- devctl/templates/docker/fastapi/Dockerfile.j2 +15 -0
- devctl/templates/docker/frontend/Dockerfile.j2 +31 -0
- devctl/templates/docker/go/Dockerfile.j2 +24 -0
- devctl/templates/docker/nestjs/Dockerfile.j2 +26 -0
- devctl/templates/docker/nextjs/Dockerfile.j2 +43 -0
- devctl/templates/docker/nodejs/Dockerfile.j2 +24 -0
- devctl/templates/docker/spring/Dockerfile.j2 +24 -0
- devctl/templates/docker/svelte/Dockerfile.j2 +24 -0
- devctl/templates/proxy.conf.json.j2 +0 -0
- devctl/templates/spring/Controller.java.j2 +50 -0
- devctl/templates/spring/Entity.java.j2 +22 -0
- devctl/templates/spring/Repository.java.j2 +9 -0
- devctl/templates/spring/Service.java.j2 +20 -0
- devctl/templates/spring/ServiceImpl.java.j2 +62 -0
- devctl/templates/spring/application.properties.j2 +19 -0
- devctl/templates/spring/config/ApplicationConfig.java.j2 +46 -0
- devctl/templates/spring/config/JwtAuthenticationFilter.java.j2 +54 -0
- devctl/templates/spring/config/JwtService.java.j2 +68 -0
- devctl/templates/spring/config/SecurityConfig.java.j2 +42 -0
- devctl/templates/spring/docker-compose.yml.j2 +29 -0
- devctl/templates/spring/dto/Request.java.j2 +20 -0
- devctl/templates/spring/dto/Response.java.j2 +22 -0
- devctl/templates/spring/mapper/Mapper.java.j2 +30 -0
- devctl/templates/vue/config/App.vue.j2 +35 -0
- devctl/templates/vue/config/main.ts.j2 +9 -0
- devctl/templates/vue/config/router.ts.j2 +18 -0
- devctl/templates/vue/config/vite.config.ts.j2 +16 -0
- devctl/templates/vue/feature/Form.vue.j2 +162 -0
- devctl/templates/vue/feature/List.vue.j2 +155 -0
- devctl/templates/vue/feature/models.ts.j2 +12 -0
- devctl/templates/vue/feature/routes.ts.j2 +19 -0
- devctl/templates/vue/feature/service.ts.j2 +44 -0
- devctl/utils/__init__.py +3 -0
- devctl/utils/dependencies.py +36 -0
- devctl/utils/env_loader.py +57 -0
- devctl-1.0.0.dist-info/METADATA +127 -0
- devctl-1.0.0.dist-info/RECORD +92 -0
- devctl-1.0.0.dist-info/WHEEL +5 -0
- devctl-1.0.0.dist-info/entry_points.txt +2 -0
- devctl-1.0.0.dist-info/licenses/LICENSE +21 -0
- devctl-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI resource scaffolding generator.
|
|
3
|
+
Handles the creation of routers, schemas, and models.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from devctl.orchestrator.scanner import detect_environment
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_fastapi_resource(resource_name: str, fields_str: str, root_path: str = "."):
|
|
14
|
+
"""
|
|
15
|
+
Scaffolds a FastAPI resource.
|
|
16
|
+
"""
|
|
17
|
+
env_state = detect_environment(root_path)
|
|
18
|
+
|
|
19
|
+
if not env_state["has_fastapi"]:
|
|
20
|
+
typer.secho("❌ Error: No FastAPI project detected here.", fg=typer.colors.RED)
|
|
21
|
+
raise typer.Exit(code=1)
|
|
22
|
+
|
|
23
|
+
fastapi_root = env_state["fastapi_path"]
|
|
24
|
+
resource_lower = resource_name.lower()
|
|
25
|
+
entity_name = resource_name.capitalize()
|
|
26
|
+
|
|
27
|
+
# Create directories
|
|
28
|
+
routers_dir = os.path.join(fastapi_root, "routers")
|
|
29
|
+
schemas_dir = os.path.join(fastapi_root, "schemas")
|
|
30
|
+
models_dir = os.path.join(fastapi_root, "models")
|
|
31
|
+
|
|
32
|
+
for d in [routers_dir, schemas_dir, models_dir]:
|
|
33
|
+
os.makedirs(d, exist_ok=True)
|
|
34
|
+
# Ensure __init__.py exists
|
|
35
|
+
with open(os.path.join(d, "__init__.py"), "a"):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
typer.secho(f"⚙️ Generating FastAPI resource '{entity_name}'...", fg=typer.colors.CYAN)
|
|
39
|
+
|
|
40
|
+
# 1. Generate Schema (Pydantic)
|
|
41
|
+
schema_content = f"""from pydantic import BaseModel
|
|
42
|
+
from typing import Optional
|
|
43
|
+
|
|
44
|
+
class {entity_name}Base(BaseModel):
|
|
45
|
+
# Fields: {fields_str}
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
class {entity_name}Create({entity_name}Base):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
class {entity_name}({entity_name}Base):
|
|
52
|
+
id: int
|
|
53
|
+
|
|
54
|
+
class Config:
|
|
55
|
+
orm_mode = True
|
|
56
|
+
"""
|
|
57
|
+
with open(os.path.join(schemas_dir, f"{resource_lower}.py"), "w") as f:
|
|
58
|
+
f.write(schema_content)
|
|
59
|
+
|
|
60
|
+
# 2. Generate Router
|
|
61
|
+
router_content = f"""from fastapi import APIRouter, HTTPException
|
|
62
|
+
from typing import List
|
|
63
|
+
from schemas import {resource_lower} as schemas
|
|
64
|
+
|
|
65
|
+
router = APIRouter(
|
|
66
|
+
prefix="/{resource_lower}s",
|
|
67
|
+
tags=["{resource_lower}s"]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
@router.get("/", response_model=List[schemas.{entity_name}])
|
|
71
|
+
def read_{resource_lower}s():
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
@router.post("/", response_model=schemas.{entity_name})
|
|
75
|
+
def create_{resource_lower}(item: schemas.{entity_name}Create):
|
|
76
|
+
return {{"id": 1, **item.dict()}}
|
|
77
|
+
"""
|
|
78
|
+
with open(os.path.join(routers_dir, f"{resource_lower}.py"), "w") as f:
|
|
79
|
+
f.write(router_content)
|
|
80
|
+
|
|
81
|
+
typer.secho(f"{entity_name} FastAPI feature successfully generated!", fg=typer.colors.GREEN)
|
|
82
|
+
typer.echo(f" - Created: schemas/{resource_lower}.py")
|
|
83
|
+
typer.echo(f" - Created: routers/{resource_lower}.py")
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Go (Fiber) resource scaffolding generator.
|
|
3
|
+
Handles the creation of handlers and models.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from devctl.orchestrator.scanner import detect_environment
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_go_resource(resource_name: str, fields_str: str, root_path: str = "."):
|
|
14
|
+
"""
|
|
15
|
+
Scaffolds a Go resource.
|
|
16
|
+
"""
|
|
17
|
+
env_state = detect_environment(root_path)
|
|
18
|
+
|
|
19
|
+
if not env_state["has_go"]:
|
|
20
|
+
typer.secho("❌ Error: No Go project detected here.", fg=typer.colors.RED)
|
|
21
|
+
raise typer.Exit(code=1)
|
|
22
|
+
|
|
23
|
+
go_root = env_state["go_path"]
|
|
24
|
+
resource_lower = resource_name.lower()
|
|
25
|
+
entity_name = resource_name.capitalize()
|
|
26
|
+
|
|
27
|
+
# Structure: handlers/resource.go, models/resource.go
|
|
28
|
+
handlers_dir = os.path.join(go_root, "handlers")
|
|
29
|
+
models_dir = os.path.join(go_root, "models")
|
|
30
|
+
|
|
31
|
+
os.makedirs(handlers_dir, exist_ok=True)
|
|
32
|
+
os.makedirs(models_dir, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
typer.secho(f"⚙️ Generating Go resource '{entity_name}'...", fg=typer.colors.CYAN)
|
|
35
|
+
|
|
36
|
+
# 1. Generate Model
|
|
37
|
+
model_content = f"""package models
|
|
38
|
+
|
|
39
|
+
type {entity_name} struct {{
|
|
40
|
+
ID uint `json:"id"`
|
|
41
|
+
// Fields: {fields_str}
|
|
42
|
+
}}
|
|
43
|
+
"""
|
|
44
|
+
with open(os.path.join(models_dir, f"{resource_lower}.go"), "w") as f:
|
|
45
|
+
f.write(model_content)
|
|
46
|
+
|
|
47
|
+
# 2. Generate Handler
|
|
48
|
+
handler_content = f"""package handlers
|
|
49
|
+
|
|
50
|
+
import (
|
|
51
|
+
"github.com/gofiber/fiber/v2"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
func Get{entity_name}s(c *fiber.Ctx) error {{
|
|
55
|
+
return c.JSON(fiber.Map{{"message": "Get all {resource_lower}s"}})
|
|
56
|
+
}}
|
|
57
|
+
|
|
58
|
+
func Create{entity_name}(c *fiber.Ctx) error {{
|
|
59
|
+
return c.Status(201).JSON(fiber.Map{{"message": "{entity_name} created"}})
|
|
60
|
+
}}
|
|
61
|
+
"""
|
|
62
|
+
with open(os.path.join(handlers_dir, f"{resource_lower}.go"), "w") as f:
|
|
63
|
+
f.write(handler_content)
|
|
64
|
+
|
|
65
|
+
typer.secho(f"✅ {entity_name} Go feature successfully generated!", fg=typer.colors.GREEN)
|
|
66
|
+
typer.echo(f" - Created: models/{resource_lower}.go")
|
|
67
|
+
typer.echo(f" - Created: handlers/{resource_lower}.go")
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NestJS resource scaffolding generator.
|
|
3
|
+
Handles the creation of modules, controllers, and services using Nest CLI.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from devctl.orchestrator.scanner import detect_environment
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_nest_resource(resource_name: str, fields_str: str, root_path: str = "."):
|
|
14
|
+
"""
|
|
15
|
+
Scaffolds a NestJS resource using the Nest CLI.
|
|
16
|
+
"""
|
|
17
|
+
env_state = detect_environment(root_path)
|
|
18
|
+
|
|
19
|
+
if not env_state["has_nest"]:
|
|
20
|
+
typer.secho("❌ Error: No NestJS project detected here.", fg=typer.colors.RED)
|
|
21
|
+
raise typer.Exit(code=1)
|
|
22
|
+
|
|
23
|
+
nest_root = env_state["nest_path"]
|
|
24
|
+
resource_lower = resource_name.lower()
|
|
25
|
+
|
|
26
|
+
typer.secho(f"⚙️ Generating NestJS resource '{resource_name}'...", fg=typer.colors.CYAN)
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
# Use npx to run Nest CLI
|
|
30
|
+
# 'g res' is shorthand for 'generate resource'
|
|
31
|
+
# --no-spec skips test files for a cleaner scaffold
|
|
32
|
+
subprocess.run(
|
|
33
|
+
["npx", "@nestjs/cli", "g", "resource", resource_lower, "--no-spec"],
|
|
34
|
+
cwd=nest_root,
|
|
35
|
+
check=True,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
typer.secho(
|
|
39
|
+
f"✅ {resource_name} NestJS resource successfully generated!", fg=typer.colors.GREEN
|
|
40
|
+
)
|
|
41
|
+
typer.echo(
|
|
42
|
+
f"💡 Note: Fields [{fields_str}] were provided but manual DTO update "
|
|
43
|
+
"is recommended for NestJS."
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
except subprocess.CalledProcessError as e:
|
|
47
|
+
typer.secho(
|
|
48
|
+
f"❌ Nest CLI resource generation failed with code: {e.returncode}",
|
|
49
|
+
fg=typer.colors.RED,
|
|
50
|
+
)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
typer.secho(f"⚠️ An unexpected error occurred: {e}", fg=typer.colors.YELLOW)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NextJS resource scaffolding generator.
|
|
3
|
+
Handles the creation of pages and components in the App Router.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from devctl.orchestrator.scanner import detect_environment
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_nextjs_resource(resource_name: str, _fields_str: str, root_path: str = "."):
|
|
14
|
+
"""
|
|
15
|
+
Scaffolds a NextJS resource (Page, Component).
|
|
16
|
+
"""
|
|
17
|
+
env_state = detect_environment(root_path)
|
|
18
|
+
|
|
19
|
+
if not env_state["has_nextjs"]:
|
|
20
|
+
typer.secho("❌ Error: No NextJS project detected here.", fg=typer.colors.RED)
|
|
21
|
+
raise typer.Exit(code=1)
|
|
22
|
+
|
|
23
|
+
nextjs_root = env_state["nextjs_path"]
|
|
24
|
+
resource_lower = resource_name.lower()
|
|
25
|
+
entity_name = resource_name.capitalize()
|
|
26
|
+
|
|
27
|
+
# Structure: src/app/resource-name/page.tsx
|
|
28
|
+
app_dir = os.path.join(nextjs_root, "src", "app", resource_lower)
|
|
29
|
+
components_dir = os.path.join(nextjs_root, "src", "components")
|
|
30
|
+
|
|
31
|
+
os.makedirs(app_dir, exist_ok=True)
|
|
32
|
+
os.makedirs(components_dir, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
typer.secho(f"⚙️ Generating NextJS resource '{entity_name}'...", fg=typer.colors.CYAN)
|
|
35
|
+
|
|
36
|
+
# 1. Generate Page (tsx)
|
|
37
|
+
page_content = f"""import React from 'react';
|
|
38
|
+
import {{ {entity_name}List }} from '@/components/{entity_name}List';
|
|
39
|
+
|
|
40
|
+
export default function {entity_name}Page() {{
|
|
41
|
+
return (
|
|
42
|
+
<main className="p-8">
|
|
43
|
+
<h1 className="text-2xl font-bold mb-4">{entity_name} Management</h1>
|
|
44
|
+
<{entity_name}List />
|
|
45
|
+
</main>
|
|
46
|
+
);
|
|
47
|
+
}}
|
|
48
|
+
"""
|
|
49
|
+
with open(os.path.join(app_dir, "page.tsx"), "w") as f:
|
|
50
|
+
f.write(page_content)
|
|
51
|
+
|
|
52
|
+
# 2. Generate Component
|
|
53
|
+
component_content = f"""import React from 'react';
|
|
54
|
+
|
|
55
|
+
export const {entity_name}List: React.FC = () => {{
|
|
56
|
+
return (
|
|
57
|
+
<div className="border p-4 rounded shadow">
|
|
58
|
+
<h2 className="text-xl font-semibold mb-2">{entity_name} List</h2>
|
|
59
|
+
<p>This component was generated by devctl.</p>
|
|
60
|
+
<ul className="list-disc pl-5 mt-4">
|
|
61
|
+
<li>Item 1</li>
|
|
62
|
+
<li>Item 2</li>
|
|
63
|
+
</ul>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}};
|
|
67
|
+
"""
|
|
68
|
+
with open(os.path.join(components_dir, f"{entity_name}List.tsx"), "w") as f:
|
|
69
|
+
f.write(component_content)
|
|
70
|
+
|
|
71
|
+
typer.secho(f"✅ {entity_name} NextJS feature successfully generated!", fg=typer.colors.GREEN)
|
|
72
|
+
typer.echo(f" - Created: src/app/{resource_lower}/page.tsx")
|
|
73
|
+
typer.echo(f" - Created: src/components/{entity_name}List.tsx")
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NodeJS/Express resource scaffolding generator.
|
|
3
|
+
Handles the creation of routes and controllers.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from devctl.orchestrator.scanner import detect_environment
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_nodejs_resource(resource_name: str, _fields_str: str, root_path: str = "."):
|
|
14
|
+
"""
|
|
15
|
+
Scaffolds a NodeJS/Express resource.
|
|
16
|
+
"""
|
|
17
|
+
# Using basic manual file writing for simplicity and speed
|
|
18
|
+
# In a more advanced version, we could use Jinja2 templates
|
|
19
|
+
|
|
20
|
+
resource_lower = resource_name.lower()
|
|
21
|
+
entity_name = resource_name.capitalize()
|
|
22
|
+
|
|
23
|
+
src_dir = os.path.join(root_path, "src")
|
|
24
|
+
if not os.path.exists(src_dir):
|
|
25
|
+
# Try to find nodejs path from scanner
|
|
26
|
+
detect_environment(root_path)
|
|
27
|
+
# We need to add has_nodejs to scanner or just check if src exists in root
|
|
28
|
+
src_dir = os.path.join(root_path, "src")
|
|
29
|
+
|
|
30
|
+
routes_dir = os.path.join(src_dir, "routes")
|
|
31
|
+
controllers_dir = os.path.join(src_dir, "controllers")
|
|
32
|
+
|
|
33
|
+
os.makedirs(routes_dir, exist_ok=True)
|
|
34
|
+
os.makedirs(controllers_dir, exist_ok=True)
|
|
35
|
+
|
|
36
|
+
typer.secho(f"⚙️ Generating NodeJS/Express resource '{entity_name}'...", fg=typer.colors.CYAN)
|
|
37
|
+
|
|
38
|
+
# 1. Generate Controller
|
|
39
|
+
controller_content = f"""import {{ Request, Response }} from 'express';
|
|
40
|
+
|
|
41
|
+
export const get{entity_name}s = (req: Request, res: Response) => {{
|
|
42
|
+
res.json({{ message: 'Get all {resource_lower}s' }});
|
|
43
|
+
}};
|
|
44
|
+
|
|
45
|
+
export const get{entity_name}ById = (req: Request, res: Response) => {{
|
|
46
|
+
const {{ id }} = req.params;
|
|
47
|
+
res.json({{ message: `Get {resource_lower} with id ${{id}}` }});
|
|
48
|
+
}};
|
|
49
|
+
|
|
50
|
+
export const create{entity_name} = (req: Request, res: Response) => {{
|
|
51
|
+
const data = req.body;
|
|
52
|
+
res.status(201).json({{
|
|
53
|
+
message: '{entity_name} created',
|
|
54
|
+
data
|
|
55
|
+
}});
|
|
56
|
+
}};
|
|
57
|
+
"""
|
|
58
|
+
controller_path = os.path.join(controllers_dir, f"{resource_lower}.controller.ts")
|
|
59
|
+
with open(controller_path, "w") as f:
|
|
60
|
+
f.write(controller_content)
|
|
61
|
+
|
|
62
|
+
# 2. Generate Route
|
|
63
|
+
route_content = f"""import {{ Router }} from 'express';
|
|
64
|
+
import * as {resource_lower}Controller from '../controllers/{resource_lower}.controller';
|
|
65
|
+
|
|
66
|
+
const router = Router();
|
|
67
|
+
|
|
68
|
+
router.get('/', {resource_lower}Controller.get{entity_name}s);
|
|
69
|
+
router.get('/:id', {resource_lower}Controller.get{entity_name}ById);
|
|
70
|
+
router.post('/', {resource_lower}Controller.create{entity_name});
|
|
71
|
+
|
|
72
|
+
export default router;
|
|
73
|
+
"""
|
|
74
|
+
route_path = os.path.join(routes_dir, f"{resource_lower}.routes.ts")
|
|
75
|
+
with open(route_path, "w") as f:
|
|
76
|
+
f.write(route_content)
|
|
77
|
+
|
|
78
|
+
typer.secho(f"✅ {entity_name} NodeJS resource successfully generated!", fg=typer.colors.GREEN)
|
|
79
|
+
typer.echo(f" - Created: controllers/{resource_lower}.controller.ts")
|
|
80
|
+
typer.echo(f" - Created: routes/{resource_lower}.routes.ts")
|
|
81
|
+
typer.echo("💡 Don't forget to register the route in your main app file.")
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ReactJS resource scaffolding generator.
|
|
3
|
+
Handles the creation of components, hooks, and services.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from devctl.orchestrator.scanner import detect_environment
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_react_resource(resource_name: str, fields_str: str, root_path: str = "."):
|
|
14
|
+
"""
|
|
15
|
+
Scaffolds a React resource (Component, Service).
|
|
16
|
+
"""
|
|
17
|
+
env_state = detect_environment(root_path)
|
|
18
|
+
|
|
19
|
+
if not env_state["has_react"]:
|
|
20
|
+
typer.secho("❌ Error: No ReactJS project detected here.", fg=typer.colors.RED)
|
|
21
|
+
raise typer.Exit(code=1)
|
|
22
|
+
|
|
23
|
+
react_root = env_state["react_path"]
|
|
24
|
+
resource_lower = resource_name.lower()
|
|
25
|
+
entity_name = resource_name.capitalize()
|
|
26
|
+
|
|
27
|
+
# Structure: src/components/ResourceName/...
|
|
28
|
+
components_dir = os.path.join(react_root, "src", "components", entity_name)
|
|
29
|
+
services_dir = os.path.join(react_root, "src", "services")
|
|
30
|
+
|
|
31
|
+
os.makedirs(components_dir, exist_ok=True)
|
|
32
|
+
os.makedirs(services_dir, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
typer.secho(f"⚙️ Generating ReactJS resource '{entity_name}'...", fg=typer.colors.CYAN)
|
|
35
|
+
|
|
36
|
+
# 1. Generate Component (tsx)
|
|
37
|
+
component_content = f"""import React from 'react';
|
|
38
|
+
|
|
39
|
+
export const {entity_name}List: React.FC = () => {{
|
|
40
|
+
return (
|
|
41
|
+
<div>
|
|
42
|
+
<h1>{entity_name} List</h1>
|
|
43
|
+
<p>Manage your {resource_lower}s here.</p>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}};
|
|
47
|
+
|
|
48
|
+
export const {entity_name}Form: React.FC = () => {{
|
|
49
|
+
return (
|
|
50
|
+
<form>
|
|
51
|
+
<h1>Create {entity_name}</h1>
|
|
52
|
+
{fields_str}
|
|
53
|
+
</form>
|
|
54
|
+
);
|
|
55
|
+
}};
|
|
56
|
+
"""
|
|
57
|
+
with open(os.path.join(components_dir, f"{entity_name}.tsx"), "w") as f:
|
|
58
|
+
f.write(component_content)
|
|
59
|
+
|
|
60
|
+
# 2. Generate Service
|
|
61
|
+
service_content = f"""export const fetch{entity_name}s = async () => {{
|
|
62
|
+
const response = await fetch('/api/{resource_lower}s');
|
|
63
|
+
return response.json();
|
|
64
|
+
}};
|
|
65
|
+
|
|
66
|
+
export const create{entity_name} = async (data: any) => {{
|
|
67
|
+
const response = await fetch('/api/{resource_lower}s', {{
|
|
68
|
+
method: 'POST',
|
|
69
|
+
body: JSON.stringify(data),
|
|
70
|
+
headers: {{ 'Content-Type': 'application/json' }}
|
|
71
|
+
}});
|
|
72
|
+
return response.json();
|
|
73
|
+
}};
|
|
74
|
+
"""
|
|
75
|
+
with open(os.path.join(services_dir, f"{resource_lower}.service.ts"), "w") as f:
|
|
76
|
+
f.write(service_content)
|
|
77
|
+
|
|
78
|
+
typer.secho(f"✅ {entity_name} React feature successfully generated!", fg=typer.colors.GREEN)
|
|
79
|
+
typer.echo(f" - Created: src/components/{entity_name}/{entity_name}.tsx")
|
|
80
|
+
typer.echo(f" - Created: src/services/{resource_lower}.service.ts")
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Spring Boot resource scaffolding generator.
|
|
3
|
+
Handles the creation of MVC components, DTOs, Mappers, and Security configuration.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from jinja2 import Environment, FileSystemLoader
|
|
10
|
+
|
|
11
|
+
# Dictionary mapping CLI types to Java types
|
|
12
|
+
JAVA_TYPE_MAP = {
|
|
13
|
+
"string": "String",
|
|
14
|
+
"int": "Integer",
|
|
15
|
+
"integer": "Integer",
|
|
16
|
+
"double": "Double",
|
|
17
|
+
"float": "Float",
|
|
18
|
+
"boolean": "Boolean",
|
|
19
|
+
"date": "LocalDate",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_fields(fields_str: str):
|
|
24
|
+
"""
|
|
25
|
+
Transforms terminal field strings into injectable data.
|
|
26
|
+
Example: "name:string, age:int" -> [{"name": "name", "java_type": "String"}, ...]
|
|
27
|
+
"""
|
|
28
|
+
if not fields_str:
|
|
29
|
+
return []
|
|
30
|
+
|
|
31
|
+
parsed_fields = []
|
|
32
|
+
raw_fields = [f.strip() for f in fields_str.split(",") if f.strip()]
|
|
33
|
+
|
|
34
|
+
for raw in raw_fields:
|
|
35
|
+
if ":" not in raw:
|
|
36
|
+
continue
|
|
37
|
+
name, field_type = raw.split(":", 1)
|
|
38
|
+
java_type = JAVA_TYPE_MAP.get(field_type.lower().strip(), "String")
|
|
39
|
+
parsed_fields.append({"name": name.strip(), "java_type": java_type})
|
|
40
|
+
return parsed_fields
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def find_spring_base_package_and_path():
|
|
44
|
+
"""
|
|
45
|
+
Searches for the directory containing the @SpringBootApplication class.
|
|
46
|
+
This is the most reliable way to find the base package without parsing pom.xml.
|
|
47
|
+
"""
|
|
48
|
+
java_src_dir = os.path.join(os.getcwd(), "src", "main", "java")
|
|
49
|
+
|
|
50
|
+
if not os.path.exists(java_src_dir):
|
|
51
|
+
return None, None
|
|
52
|
+
|
|
53
|
+
for root, _dirs, files in os.walk(java_src_dir):
|
|
54
|
+
for file in files:
|
|
55
|
+
if file.endswith("Application.java"):
|
|
56
|
+
rel_path = os.path.relpath(root, java_src_dir)
|
|
57
|
+
base_package = rel_path.replace(os.sep, ".")
|
|
58
|
+
return base_package, root
|
|
59
|
+
|
|
60
|
+
return None, None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def generate_spring_resource(resource_name: str, fields_str: str):
|
|
64
|
+
"""
|
|
65
|
+
Orchestrates the creation of MVC + DTOs + Mapper architecture.
|
|
66
|
+
"""
|
|
67
|
+
base_package, base_path = find_spring_base_package_and_path()
|
|
68
|
+
|
|
69
|
+
if not base_package:
|
|
70
|
+
typer.secho(
|
|
71
|
+
"Error: Unable to find a valid Spring Boot project here.",
|
|
72
|
+
fg=typer.colors.RED,
|
|
73
|
+
)
|
|
74
|
+
raise typer.Exit(code=1)
|
|
75
|
+
|
|
76
|
+
entity_name = resource_name.capitalize()
|
|
77
|
+
|
|
78
|
+
# Detailed configuration for sub-folders (DTOs, Mappers)
|
|
79
|
+
components = [
|
|
80
|
+
{"dir": "entity", "suffix": "Entity", "template": "Entity.java.j2"},
|
|
81
|
+
{"dir": "repository", "suffix": "Repository", "template": "Repository.java.j2"},
|
|
82
|
+
{"dir": "service", "suffix": "Service", "template": "Service.java.j2"},
|
|
83
|
+
{"dir": "service/impl", "suffix": "ServiceImpl", "template": "ServiceImpl.java.j2"},
|
|
84
|
+
{"dir": "controller", "suffix": "Controller", "template": "Controller.java.j2"},
|
|
85
|
+
{"dir": "dto/request", "suffix": "Request", "template": "dto/Request.java.j2"},
|
|
86
|
+
{"dir": "dto/response", "suffix": "Response", "template": "dto/Response.java.j2"},
|
|
87
|
+
{"dir": "mapper", "suffix": "Mapper", "template": "mapper/Mapper.java.j2"},
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
templates_dir = os.path.join(os.path.dirname(__file__), "..", "templates", "spring")
|
|
91
|
+
env = Environment(loader=FileSystemLoader(templates_dir))
|
|
92
|
+
|
|
93
|
+
typer.secho(
|
|
94
|
+
f"Generating Spring resource '{entity_name}' (with MapStruct & DTOs)...",
|
|
95
|
+
fg=typer.colors.CYAN,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
for comp in components:
|
|
99
|
+
class_name = f"{entity_name}{comp['suffix']}"
|
|
100
|
+
target_file_name = f"{class_name}.java"
|
|
101
|
+
|
|
102
|
+
# Create sub-folder if it doesn't exist (e.g., src/.../dto/request)
|
|
103
|
+
# os.path.normpath handles OS-specific slashes
|
|
104
|
+
target_dir = os.path.join(base_path, os.path.normpath(comp["dir"]))
|
|
105
|
+
os.makedirs(target_dir, exist_ok=True)
|
|
106
|
+
|
|
107
|
+
# Data passed to the Jinja2 template
|
|
108
|
+
context = {
|
|
109
|
+
"base_package": base_package,
|
|
110
|
+
"class_name": class_name,
|
|
111
|
+
"entity_name": entity_name,
|
|
112
|
+
"table_name": f"{resource_name.lower()}s",
|
|
113
|
+
"fields": parse_fields(fields_str),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
template = env.get_template(comp["template"])
|
|
117
|
+
content = template.render(context)
|
|
118
|
+
|
|
119
|
+
with open(os.path.join(target_dir, target_file_name), "w", encoding="utf-8") as f:
|
|
120
|
+
f.write(content)
|
|
121
|
+
|
|
122
|
+
typer.echo(f" - Created: {comp['dir']}/{target_file_name}")
|
|
123
|
+
|
|
124
|
+
typer.secho(f"{entity_name} architecture successfully generated!", fg=typer.colors.GREEN)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def generate_spring_security(_root_path: str = "."):
|
|
128
|
+
"""
|
|
129
|
+
Dynamically generates the JWT security base.
|
|
130
|
+
"""
|
|
131
|
+
# Reuse the dynamic detection function
|
|
132
|
+
base_package, base_path = find_spring_base_package_and_path()
|
|
133
|
+
|
|
134
|
+
if not base_package:
|
|
135
|
+
typer.secho(
|
|
136
|
+
"Error: Unable to locate Java package for security.",
|
|
137
|
+
fg=typer.colors.RED,
|
|
138
|
+
)
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
# Create config folder in the right place
|
|
142
|
+
target_dir = os.path.join(base_path, "config")
|
|
143
|
+
os.makedirs(target_dir, exist_ok=True)
|
|
144
|
+
|
|
145
|
+
# Setup Jinja2
|
|
146
|
+
templates_dir = os.path.join(os.path.dirname(__file__), "..", "templates", "spring", "config")
|
|
147
|
+
env = Environment(loader=FileSystemLoader(templates_dir))
|
|
148
|
+
|
|
149
|
+
security_files = [
|
|
150
|
+
"JwtService.java",
|
|
151
|
+
"JwtAuthenticationFilter.java",
|
|
152
|
+
"SecurityConfig.java",
|
|
153
|
+
"ApplicationConfig.java",
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
typer.secho(f"Injecting JWT security into {base_package}.config...", fg=typer.colors.CYAN)
|
|
157
|
+
|
|
158
|
+
for filename in security_files:
|
|
159
|
+
template = env.get_template(f"{filename}.j2")
|
|
160
|
+
content = template.render(base_package=base_package)
|
|
161
|
+
|
|
162
|
+
with open(os.path.join(target_dir, filename), "w", encoding="utf-8") as f:
|
|
163
|
+
f.write(content)
|
|
164
|
+
typer.echo(f" - Created: config/{filename}")
|
|
165
|
+
|
|
166
|
+
typer.secho("Security initialized successfully!", fg=typer.colors.GREEN)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Svelte resource scaffolding generator.
|
|
3
|
+
Handles the creation of components and routes.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from devctl.orchestrator.scanner import detect_environment
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_svelte_resource(resource_name: str, _fields_str: str, root_path: str = "."):
|
|
14
|
+
"""
|
|
15
|
+
Scaffolds a Svelte resource (Route, Component).
|
|
16
|
+
"""
|
|
17
|
+
env_state = detect_environment(root_path)
|
|
18
|
+
|
|
19
|
+
if not env_state["has_svelte"]:
|
|
20
|
+
typer.secho("❌ Error: No Svelte project detected here.", fg=typer.colors.RED)
|
|
21
|
+
raise typer.Exit(code=1)
|
|
22
|
+
|
|
23
|
+
svelte_root = env_state["svelte_path"]
|
|
24
|
+
resource_lower = resource_name.lower()
|
|
25
|
+
entity_name = resource_name.capitalize()
|
|
26
|
+
|
|
27
|
+
# Structure: src/routes/resource-name/+page.svelte
|
|
28
|
+
routes_dir = os.path.join(svelte_root, "src", "routes", resource_lower)
|
|
29
|
+
components_dir = os.path.join(svelte_root, "src", "lib", "components")
|
|
30
|
+
|
|
31
|
+
os.makedirs(routes_dir, exist_ok=True)
|
|
32
|
+
os.makedirs(components_dir, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
typer.secho(f"⚙️ Generating Svelte resource '{entity_name}'...", fg=typer.colors.CYAN)
|
|
35
|
+
|
|
36
|
+
# 1. Generate +page.svelte
|
|
37
|
+
page_content = f"""<script lang="ts">
|
|
38
|
+
import {entity_name}List from '$lib/components/{entity_name}List.svelte';
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<h1>{entity_name} Management</h1>
|
|
42
|
+
|
|
43
|
+
<{entity_name}List />
|
|
44
|
+
"""
|
|
45
|
+
with open(os.path.join(routes_dir, "+page.svelte"), "w") as f:
|
|
46
|
+
f.write(page_content)
|
|
47
|
+
|
|
48
|
+
# 2. Generate Component
|
|
49
|
+
component_content = f"""<script lang="ts">
|
|
50
|
+
export let items = [];
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<div class="list">
|
|
54
|
+
<h2>{entity_name} List</h2>
|
|
55
|
+
<p>Generated by devctl</p>
|
|
56
|
+
<ul>
|
|
57
|
+
<li>Sample Item</li>
|
|
58
|
+
</ul>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<style>
|
|
62
|
+
.list {{
|
|
63
|
+
padding: 1rem;
|
|
64
|
+
border: 1px solid #ccc;
|
|
65
|
+
}}
|
|
66
|
+
</style>
|
|
67
|
+
"""
|
|
68
|
+
with open(os.path.join(components_dir, f"{entity_name}List.svelte"), "w") as f:
|
|
69
|
+
f.write(component_content)
|
|
70
|
+
|
|
71
|
+
typer.secho(f"✅ {entity_name} Svelte feature successfully generated!", fg=typer.colors.GREEN)
|
|
72
|
+
typer.echo(f" - Created: src/routes/{resource_lower}/+page.svelte")
|
|
73
|
+
typer.echo(f" - Created: src/lib/components/{entity_name}List.svelte")
|