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.
Files changed (92) hide show
  1. devctl/__init__.py +3 -0
  2. devctl/commands/__init__.py +3 -0
  3. devctl/commands/add.py +166 -0
  4. devctl/commands/deploy.py +61 -0
  5. devctl/commands/docker.py +65 -0
  6. devctl/commands/init.py +193 -0
  7. devctl/commands/run.py +67 -0
  8. devctl/generators/__init__.py +3 -0
  9. devctl/generators/angular.py +112 -0
  10. devctl/generators/django.py +61 -0
  11. devctl/generators/docker_scaffold.py +656 -0
  12. devctl/generators/fastapi.py +67 -0
  13. devctl/generators/go_fiber.py +61 -0
  14. devctl/generators/nestjs.py +49 -0
  15. devctl/generators/nextjs.py +53 -0
  16. devctl/generators/nodejs.py +109 -0
  17. devctl/generators/react.py +43 -0
  18. devctl/generators/scaffold_angular.py +163 -0
  19. devctl/generators/scaffold_django.py +79 -0
  20. devctl/generators/scaffold_fastapi.py +83 -0
  21. devctl/generators/scaffold_go.py +67 -0
  22. devctl/generators/scaffold_nestjs.py +52 -0
  23. devctl/generators/scaffold_nextjs.py +73 -0
  24. devctl/generators/scaffold_nodejs.py +81 -0
  25. devctl/generators/scaffold_react.py +80 -0
  26. devctl/generators/scaffold_spring.py +166 -0
  27. devctl/generators/scaffold_svelte.py +73 -0
  28. devctl/generators/scaffold_vue.py +111 -0
  29. devctl/generators/spring.py +221 -0
  30. devctl/generators/svelte.py +52 -0
  31. devctl/generators/vue.py +105 -0
  32. devctl/main.py +45 -0
  33. devctl/orchestrator/__init__.py +3 -0
  34. devctl/orchestrator/config_builder.py +64 -0
  35. devctl/orchestrator/runner.py +219 -0
  36. devctl/orchestrator/scanner.py +155 -0
  37. devctl/templates/angular/config/environment.development.ts.j2 +4 -0
  38. devctl/templates/angular/config/environment.ts.j2 +5 -0
  39. devctl/templates/angular/config/proxy.conf.json.j2 +8 -0
  40. devctl/templates/angular/feature/models/request.model.ts.j2 +5 -0
  41. devctl/templates/angular/feature/models/response.model.ts.j2 +6 -0
  42. devctl/templates/angular/feature/pages/form/form.component.html.j2 +21 -0
  43. devctl/templates/angular/feature/pages/form/form.component.scss.j2 +0 -0
  44. devctl/templates/angular/feature/pages/form/form.component.ts.j2 +63 -0
  45. devctl/templates/angular/feature/pages/list/list.component.html.j2 +28 -0
  46. devctl/templates/angular/feature/pages/list/list.component.scss.j2 +0 -0
  47. devctl/templates/angular/feature/pages/list/list.component.ts.j2 +34 -0
  48. devctl/templates/angular/feature/routes.ts.j2 +9 -0
  49. devctl/templates/angular/feature/services/service.ts.j2 +34 -0
  50. devctl/templates/docker/deploy.yml.j2 +37 -0
  51. devctl/templates/docker/django/Dockerfile.j2 +21 -0
  52. devctl/templates/docker/fastapi/Dockerfile.j2 +15 -0
  53. devctl/templates/docker/frontend/Dockerfile.j2 +31 -0
  54. devctl/templates/docker/go/Dockerfile.j2 +24 -0
  55. devctl/templates/docker/nestjs/Dockerfile.j2 +26 -0
  56. devctl/templates/docker/nextjs/Dockerfile.j2 +43 -0
  57. devctl/templates/docker/nodejs/Dockerfile.j2 +24 -0
  58. devctl/templates/docker/spring/Dockerfile.j2 +24 -0
  59. devctl/templates/docker/svelte/Dockerfile.j2 +24 -0
  60. devctl/templates/proxy.conf.json.j2 +0 -0
  61. devctl/templates/spring/Controller.java.j2 +50 -0
  62. devctl/templates/spring/Entity.java.j2 +22 -0
  63. devctl/templates/spring/Repository.java.j2 +9 -0
  64. devctl/templates/spring/Service.java.j2 +20 -0
  65. devctl/templates/spring/ServiceImpl.java.j2 +62 -0
  66. devctl/templates/spring/application.properties.j2 +19 -0
  67. devctl/templates/spring/config/ApplicationConfig.java.j2 +46 -0
  68. devctl/templates/spring/config/JwtAuthenticationFilter.java.j2 +54 -0
  69. devctl/templates/spring/config/JwtService.java.j2 +68 -0
  70. devctl/templates/spring/config/SecurityConfig.java.j2 +42 -0
  71. devctl/templates/spring/docker-compose.yml.j2 +29 -0
  72. devctl/templates/spring/dto/Request.java.j2 +20 -0
  73. devctl/templates/spring/dto/Response.java.j2 +22 -0
  74. devctl/templates/spring/mapper/Mapper.java.j2 +30 -0
  75. devctl/templates/vue/config/App.vue.j2 +35 -0
  76. devctl/templates/vue/config/main.ts.j2 +9 -0
  77. devctl/templates/vue/config/router.ts.j2 +18 -0
  78. devctl/templates/vue/config/vite.config.ts.j2 +16 -0
  79. devctl/templates/vue/feature/Form.vue.j2 +162 -0
  80. devctl/templates/vue/feature/List.vue.j2 +155 -0
  81. devctl/templates/vue/feature/models.ts.j2 +12 -0
  82. devctl/templates/vue/feature/routes.ts.j2 +19 -0
  83. devctl/templates/vue/feature/service.ts.j2 +44 -0
  84. devctl/utils/__init__.py +3 -0
  85. devctl/utils/dependencies.py +36 -0
  86. devctl/utils/env_loader.py +57 -0
  87. devctl-1.0.0.dist-info/METADATA +127 -0
  88. devctl-1.0.0.dist-info/RECORD +92 -0
  89. devctl-1.0.0.dist-info/WHEEL +5 -0
  90. devctl-1.0.0.dist-info/entry_points.txt +2 -0
  91. devctl-1.0.0.dist-info/licenses/LICENSE +21 -0
  92. 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")