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,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generators for FastAPI projects.
|
|
3
|
+
Includes boilerplate generation with Uvicorn and Pydantic.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generate_fastapi_boilerplate(project_name: str) -> bool:
|
|
13
|
+
"""
|
|
14
|
+
Generates a new FastAPI project.
|
|
15
|
+
"""
|
|
16
|
+
typer.secho(f"Generating FastAPI project '{project_name}'...", fg=typer.colors.CYAN)
|
|
17
|
+
safe_name = project_name.lower().replace("_", "-")
|
|
18
|
+
project_path = os.path.join(os.getcwd(), safe_name)
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
os.makedirs(project_path, exist_ok=True)
|
|
22
|
+
|
|
23
|
+
# 1. Create main.py
|
|
24
|
+
main_py = """from fastapi import FastAPI
|
|
25
|
+
|
|
26
|
+
app = FastAPI(title="devctl FastAPI project")
|
|
27
|
+
|
|
28
|
+
@app.get("/")
|
|
29
|
+
def read_root():
|
|
30
|
+
return {"Hello": "from devctl FastAPI!"}
|
|
31
|
+
|
|
32
|
+
@app.get("/items/{item_id}")
|
|
33
|
+
def read_item(item_id: int, q: str = None):
|
|
34
|
+
return {"item_id": item_id, "q": q}
|
|
35
|
+
"""
|
|
36
|
+
with open(os.path.join(project_path, "main.py"), "w") as f:
|
|
37
|
+
f.write(main_py)
|
|
38
|
+
|
|
39
|
+
# 2. Create requirements.txt
|
|
40
|
+
requirements = """fastapi
|
|
41
|
+
uvicorn[standard]
|
|
42
|
+
pydantic
|
|
43
|
+
"""
|
|
44
|
+
with open(os.path.join(project_path, "requirements.txt"), "w") as f:
|
|
45
|
+
f.write(requirements)
|
|
46
|
+
|
|
47
|
+
# 3. Create virtual environment
|
|
48
|
+
typer.secho("Creating virtual environment...", fg=typer.colors.CYAN)
|
|
49
|
+
subprocess.run(["python3", "-m", "venv", ".venv"], cwd=project_path, check=True)
|
|
50
|
+
|
|
51
|
+
# 4. Install dependencies
|
|
52
|
+
typer.secho("Installing dependencies (fastapi, uvicorn)...", fg=typer.colors.CYAN)
|
|
53
|
+
# Note: on Linux it's .venv/bin/pip
|
|
54
|
+
pip_path = os.path.join(".venv", "bin", "pip")
|
|
55
|
+
subprocess.run(
|
|
56
|
+
[pip_path, "install", "-r", "requirements.txt"],
|
|
57
|
+
cwd=project_path,
|
|
58
|
+
check=True,
|
|
59
|
+
stdout=subprocess.DEVNULL,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
typer.secho(f"FastAPI project '{safe_name}' successfully generated!", fg=typer.colors.GREEN)
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
except Exception as e:
|
|
66
|
+
typer.secho(f"Error: FastAPI initialization failed: {e}", fg=typer.colors.RED)
|
|
67
|
+
return False
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generators for Go (Fiber) projects.
|
|
3
|
+
Includes boilerplate generation with Fiber framework.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generate_go_boilerplate(project_name: str) -> bool:
|
|
13
|
+
"""
|
|
14
|
+
Generates a new Go + Fiber project.
|
|
15
|
+
"""
|
|
16
|
+
typer.secho(f"Generating Go project '{project_name}'...", fg=typer.colors.CYAN)
|
|
17
|
+
project_path = os.path.join(os.getcwd(), project_name)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
os.makedirs(project_path, exist_ok=True)
|
|
21
|
+
|
|
22
|
+
# 1. Go mod init
|
|
23
|
+
typer.secho("Initializing Go module...", fg=typer.colors.CYAN)
|
|
24
|
+
subprocess.run(["go", "mod", "init", project_name], cwd=project_path, check=True)
|
|
25
|
+
|
|
26
|
+
# 2. Install Fiber
|
|
27
|
+
typer.secho("Installing Fiber framework...", fg=typer.colors.CYAN)
|
|
28
|
+
subprocess.run(
|
|
29
|
+
["go", "get", "github.com/gofiber/fiber/v2"],
|
|
30
|
+
cwd=project_path,
|
|
31
|
+
check=True,
|
|
32
|
+
stdout=subprocess.DEVNULL,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# 3. Create main.go
|
|
36
|
+
main_go = """package main
|
|
37
|
+
|
|
38
|
+
import (
|
|
39
|
+
"log"
|
|
40
|
+
"github.com/gofiber/fiber/v2"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
func main() {
|
|
44
|
+
app := fiber.New()
|
|
45
|
+
|
|
46
|
+
app.Get("/", func(c *fiber.Ctx) error {
|
|
47
|
+
return c.SendString("Hello from devctl Go/Fiber!")
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
log.Fatal(app.Listen(":3000"))
|
|
51
|
+
}
|
|
52
|
+
"""
|
|
53
|
+
with open(os.path.join(project_path, "main.go"), "w") as f:
|
|
54
|
+
f.write(main_go)
|
|
55
|
+
|
|
56
|
+
typer.secho(f"Go project '{project_name}' successfully generated!", fg=typer.colors.GREEN)
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
typer.secho(f"Error: Go initialization failed: {e}", fg=typer.colors.RED)
|
|
61
|
+
return False
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generators for NestJS projects.
|
|
3
|
+
Includes boilerplate generation via Nest CLI.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_nest_boilerplate(project_name: str) -> bool:
|
|
12
|
+
"""
|
|
13
|
+
Generates a new NestJS project using the Nest CLI via npx.
|
|
14
|
+
"""
|
|
15
|
+
typer.secho(f"🔄 Generating NestJS project '{project_name}'...", fg=typer.colors.CYAN)
|
|
16
|
+
safe_name = project_name.lower().replace("_", "-")
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
# Use npx to run Nest CLI without requiring global installation
|
|
20
|
+
# --package-manager npm: ensures npm is used
|
|
21
|
+
# --strict: enables strict mode
|
|
22
|
+
# --skip-git: devctl might be in a git repo already
|
|
23
|
+
typer.secho("Scaffolding NestJS project (this may take a minute)...", fg=typer.colors.CYAN)
|
|
24
|
+
subprocess.run(
|
|
25
|
+
[
|
|
26
|
+
"npx",
|
|
27
|
+
"-p",
|
|
28
|
+
"@nestjs/cli",
|
|
29
|
+
"nest",
|
|
30
|
+
"new",
|
|
31
|
+
safe_name,
|
|
32
|
+
"--package-manager",
|
|
33
|
+
"npm",
|
|
34
|
+
"--skip-git",
|
|
35
|
+
],
|
|
36
|
+
check=True,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
typer.secho(
|
|
40
|
+
f"✅ NestJS project '{safe_name}' successfully generated!", fg=typer.colors.GREEN
|
|
41
|
+
)
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
except subprocess.CalledProcessError as e:
|
|
45
|
+
typer.secho(f"❌ Nest CLI process failed with code: {e.returncode}", fg=typer.colors.RED)
|
|
46
|
+
return False
|
|
47
|
+
except FileNotFoundError:
|
|
48
|
+
typer.secho("❌ Error: 'npx' or 'npm' not found in path.", fg=typer.colors.RED)
|
|
49
|
+
return False
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generators for NextJS projects.
|
|
3
|
+
Includes boilerplate generation via create-next-app.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_nextjs_boilerplate(project_name: str) -> bool:
|
|
12
|
+
"""
|
|
13
|
+
Generates a new NextJS project using create-next-app via npx.
|
|
14
|
+
"""
|
|
15
|
+
typer.secho(f"🔄 Generating NextJS project '{project_name}'...", fg=typer.colors.CYAN)
|
|
16
|
+
safe_name = project_name.lower().replace("_", "-")
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
typer.secho("Scaffolding NextJS project (this may take a minute)...", fg=typer.colors.CYAN)
|
|
20
|
+
# --ts: TypeScript
|
|
21
|
+
# --eslint: ESLint
|
|
22
|
+
# --tailwind: Tailwind CSS
|
|
23
|
+
# --src-dir: Use src/ directory
|
|
24
|
+
# --app: Use App Router
|
|
25
|
+
# --import-alias: alias for imports
|
|
26
|
+
subprocess.run(
|
|
27
|
+
[
|
|
28
|
+
"npx",
|
|
29
|
+
"create-next-app@latest",
|
|
30
|
+
safe_name,
|
|
31
|
+
"--ts",
|
|
32
|
+
"--eslint",
|
|
33
|
+
"--tailwind",
|
|
34
|
+
"--src-dir",
|
|
35
|
+
"--app",
|
|
36
|
+
"--import-alias",
|
|
37
|
+
"@/*",
|
|
38
|
+
"--use-npm",
|
|
39
|
+
],
|
|
40
|
+
check=True,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
typer.secho(
|
|
44
|
+
f"✅ NextJS project '{safe_name}' successfully generated!", fg=typer.colors.GREEN
|
|
45
|
+
)
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
except subprocess.CalledProcessError as e:
|
|
49
|
+
typer.secho(f"❌ NextJS creation failed with code: {e.returncode}", fg=typer.colors.RED)
|
|
50
|
+
return False
|
|
51
|
+
except Exception as e:
|
|
52
|
+
typer.secho(f"❌ NextJS initialization failed: {e}", fg=typer.colors.RED)
|
|
53
|
+
return False
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generators for NodeJS (Express) projects.
|
|
3
|
+
Includes boilerplate generation with TypeScript and Express.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_nodejs_boilerplate(project_name: str) -> bool:
|
|
14
|
+
"""
|
|
15
|
+
Generates a new NodeJS + Express + TypeScript project.
|
|
16
|
+
"""
|
|
17
|
+
typer.secho(f"🔄 Generating NodeJS/Express project '{project_name}'...", fg=typer.colors.CYAN)
|
|
18
|
+
safe_name = project_name.lower().replace("_", "-")
|
|
19
|
+
project_path = os.path.join(os.getcwd(), safe_name)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
os.makedirs(project_path, exist_ok=True)
|
|
23
|
+
|
|
24
|
+
# 1. Initialize package.json
|
|
25
|
+
typer.secho("Initializing package.json...", fg=typer.colors.CYAN)
|
|
26
|
+
subprocess.run(
|
|
27
|
+
["npm", "init", "-y"], cwd=project_path, check=True, stdout=subprocess.DEVNULL
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# 2. Install dependencies
|
|
31
|
+
typer.secho(
|
|
32
|
+
"Installing dependencies (express, typescript, ts-node, nodemon)...",
|
|
33
|
+
fg=typer.colors.CYAN,
|
|
34
|
+
)
|
|
35
|
+
subprocess.run(
|
|
36
|
+
["npm", "install", "express", "dotenv"],
|
|
37
|
+
cwd=project_path,
|
|
38
|
+
check=True,
|
|
39
|
+
stdout=subprocess.DEVNULL,
|
|
40
|
+
)
|
|
41
|
+
subprocess.run(
|
|
42
|
+
[
|
|
43
|
+
"npm",
|
|
44
|
+
"install",
|
|
45
|
+
"-D",
|
|
46
|
+
"typescript",
|
|
47
|
+
"@types/node",
|
|
48
|
+
"@types/express",
|
|
49
|
+
"ts-node",
|
|
50
|
+
"nodemon",
|
|
51
|
+
"rimraf",
|
|
52
|
+
],
|
|
53
|
+
cwd=project_path,
|
|
54
|
+
check=True,
|
|
55
|
+
stdout=subprocess.DEVNULL,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# 3. Initialize TypeScript
|
|
59
|
+
typer.secho("Configuring TypeScript...", fg=typer.colors.CYAN)
|
|
60
|
+
subprocess.run(
|
|
61
|
+
["npx", "tsc", "--init"], cwd=project_path, check=True, stdout=subprocess.DEVNULL
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# 4. Create folder structure
|
|
65
|
+
os.makedirs(os.path.join(project_path, "src"), exist_ok=True)
|
|
66
|
+
|
|
67
|
+
# 5. Create basic index.ts
|
|
68
|
+
index_ts = """import express, { Request, Response } from 'express';
|
|
69
|
+
import dotenv from 'dotenv';
|
|
70
|
+
|
|
71
|
+
dotenv.config();
|
|
72
|
+
|
|
73
|
+
const app = express();
|
|
74
|
+
const port = process.env.PORT || 3000;
|
|
75
|
+
|
|
76
|
+
app.use(express.json());
|
|
77
|
+
|
|
78
|
+
app.get('/', (req: Request, res: Response) => {
|
|
79
|
+
res.send('Hello from devctl NodeJS/Express!');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
app.listen(port, () => {
|
|
83
|
+
console.log(`[server]: Server is running at http://localhost:${port}`);
|
|
84
|
+
});
|
|
85
|
+
"""
|
|
86
|
+
with open(os.path.join(project_path, "src", "index.ts"), "w") as f:
|
|
87
|
+
f.write(index_ts)
|
|
88
|
+
|
|
89
|
+
# 6. Update package.json scripts
|
|
90
|
+
with open(os.path.join(project_path, "package.json"), "r") as f:
|
|
91
|
+
pkg = json.load(f)
|
|
92
|
+
|
|
93
|
+
pkg["scripts"] = {
|
|
94
|
+
"start": "node dist/index.js",
|
|
95
|
+
"build": "rimraf dist && tsc",
|
|
96
|
+
"dev": "nodemon src/index.ts",
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
with open(os.path.join(project_path, "package.json"), "w") as f:
|
|
100
|
+
json.dump(pkg, f, indent=2)
|
|
101
|
+
|
|
102
|
+
typer.secho(
|
|
103
|
+
f"NodeJS/Express project '{safe_name}' successfully generated!", fg=typer.colors.GREEN
|
|
104
|
+
)
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
typer.secho(f"Error: NodeJS/Express initialization failed: {e}", fg=typer.colors.RED)
|
|
109
|
+
return False
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generators for ReactJS projects via Vite.
|
|
3
|
+
Includes boilerplate generation and basic configuration.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generate_react_boilerplate(project_name: str) -> bool:
|
|
13
|
+
"""
|
|
14
|
+
Generates a new React + TypeScript project via Vite.
|
|
15
|
+
"""
|
|
16
|
+
typer.secho(f"Generating ReactJS frontend '{project_name}' via Vite...", fg=typer.colors.CYAN)
|
|
17
|
+
safe_name = project_name.lower().replace("_", "-")
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
typer.secho("Scaffolding React project...", fg=typer.colors.CYAN)
|
|
21
|
+
subprocess.run(
|
|
22
|
+
["npm", "create", "vite@latest", safe_name, "--", "--template", "react-ts"],
|
|
23
|
+
check=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
project_full_path = os.path.join(os.getcwd(), safe_name)
|
|
27
|
+
|
|
28
|
+
typer.secho("Installing npm dependencies...", fg=typer.colors.CYAN)
|
|
29
|
+
subprocess.run(["npm", "install"], cwd=project_full_path, check=True)
|
|
30
|
+
|
|
31
|
+
typer.secho(
|
|
32
|
+
f"ReactJS frontend '{safe_name}' successfully generated!", fg=typer.colors.GREEN
|
|
33
|
+
)
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
except subprocess.CalledProcessError as e:
|
|
37
|
+
typer.secho(
|
|
38
|
+
f"Error: React/Vite process failed with code: {e.returncode}", fg=typer.colors.RED
|
|
39
|
+
)
|
|
40
|
+
return False
|
|
41
|
+
except Exception as e:
|
|
42
|
+
typer.secho(f"Error: React/Vite initialization failed: {e}", fg=typer.colors.RED)
|
|
43
|
+
return False
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Angular resource scaffolding generator.
|
|
3
|
+
Handles the creation of features including models, services, and components.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from jinja2 import Environment, FileSystemLoader
|
|
10
|
+
|
|
11
|
+
from devctl.orchestrator.scanner import detect_environment
|
|
12
|
+
|
|
13
|
+
# Dictionary mapping CLI types to TypeScript types
|
|
14
|
+
TS_TYPE_MAP = {
|
|
15
|
+
"string": "string",
|
|
16
|
+
"int": "number",
|
|
17
|
+
"integer": "number",
|
|
18
|
+
"double": "number",
|
|
19
|
+
"float": "number",
|
|
20
|
+
"boolean": "boolean",
|
|
21
|
+
"date": "string", # In JSON, dates are typically transmitted as ISO strings
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def parse_ts_fields(fields_str: str):
|
|
26
|
+
"""Transforms terminal fields into TypeScript types."""
|
|
27
|
+
if not fields_str:
|
|
28
|
+
return []
|
|
29
|
+
parsed = []
|
|
30
|
+
for raw in [f.strip() for f in fields_str.split(",") if f.strip()]:
|
|
31
|
+
if ":" not in raw:
|
|
32
|
+
continue
|
|
33
|
+
name, field_type = raw.split(":", 1)
|
|
34
|
+
ts_type = TS_TYPE_MAP.get(field_type.lower().strip(), "string")
|
|
35
|
+
parsed.append({"name": name.strip(), "ts_type": ts_type})
|
|
36
|
+
return parsed
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def generate_angular_resource(resource_name: str, fields_str: str, root_path: str = "."):
|
|
40
|
+
"""
|
|
41
|
+
Orchestrates the creation of a complete Angular feature.
|
|
42
|
+
"""
|
|
43
|
+
env_state = detect_environment(root_path)
|
|
44
|
+
|
|
45
|
+
if not env_state["has_angular"]:
|
|
46
|
+
typer.secho("Error: No Angular project detected here.", fg=typer.colors.RED)
|
|
47
|
+
raise typer.Exit(code=1)
|
|
48
|
+
|
|
49
|
+
angular_root = env_state["angular_path"]
|
|
50
|
+
resource_lower = resource_name.lower()
|
|
51
|
+
entity_name = resource_name.capitalize()
|
|
52
|
+
|
|
53
|
+
# Feature target directory: src/app/features/resource_name
|
|
54
|
+
feature_dir = os.path.join(angular_root, "src", "app", "features", resource_lower)
|
|
55
|
+
|
|
56
|
+
# Components to generate
|
|
57
|
+
components = [
|
|
58
|
+
# Models
|
|
59
|
+
{
|
|
60
|
+
"dir": "models",
|
|
61
|
+
"suffix": "-request.model",
|
|
62
|
+
"ext": ".ts",
|
|
63
|
+
"template": "models/request.model.ts.j2",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"dir": "models",
|
|
67
|
+
"suffix": "-response.model",
|
|
68
|
+
"ext": ".ts",
|
|
69
|
+
"template": "models/response.model.ts.j2",
|
|
70
|
+
},
|
|
71
|
+
# Service
|
|
72
|
+
{
|
|
73
|
+
"dir": "services",
|
|
74
|
+
"suffix": ".service",
|
|
75
|
+
"ext": ".ts",
|
|
76
|
+
"template": "services/service.ts.j2",
|
|
77
|
+
},
|
|
78
|
+
# Routes (at feature root)
|
|
79
|
+
{"dir": "", "suffix": ".routes", "ext": ".ts", "template": "routes.ts.j2"},
|
|
80
|
+
# List Component
|
|
81
|
+
{
|
|
82
|
+
"dir": f"pages/{resource_lower}-list",
|
|
83
|
+
"suffix": "-list.component",
|
|
84
|
+
"ext": ".ts",
|
|
85
|
+
"template": "pages/list/list.component.ts.j2",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"dir": f"pages/{resource_lower}-list",
|
|
89
|
+
"suffix": "-list.component",
|
|
90
|
+
"ext": ".html",
|
|
91
|
+
"template": "pages/list/list.component.html.j2",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"dir": f"pages/{resource_lower}-list",
|
|
95
|
+
"suffix": "-list.component",
|
|
96
|
+
"ext": ".scss",
|
|
97
|
+
"template": "pages/list/list.component.scss.j2",
|
|
98
|
+
},
|
|
99
|
+
# Form Component
|
|
100
|
+
{
|
|
101
|
+
"dir": f"pages/{resource_lower}-form",
|
|
102
|
+
"suffix": "-form.component",
|
|
103
|
+
"ext": ".ts",
|
|
104
|
+
"template": "pages/form/form.component.ts.j2",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"dir": f"pages/{resource_lower}-form",
|
|
108
|
+
"suffix": "-form.component",
|
|
109
|
+
"ext": ".html",
|
|
110
|
+
"template": "pages/form/form.component.html.j2",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"dir": f"pages/{resource_lower}-form",
|
|
114
|
+
"suffix": "-form.component",
|
|
115
|
+
"ext": ".scss",
|
|
116
|
+
"template": "pages/form/form.component.scss.j2",
|
|
117
|
+
},
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
templates_dir = os.path.join(os.path.dirname(__file__), "..", "templates", "angular", "feature")
|
|
121
|
+
env = Environment(loader=FileSystemLoader(templates_dir))
|
|
122
|
+
|
|
123
|
+
typer.secho(f"Generating Angular feature '{entity_name}'...", fg=typer.colors.CYAN)
|
|
124
|
+
|
|
125
|
+
# Template data
|
|
126
|
+
context = {
|
|
127
|
+
"entity_name": entity_name,
|
|
128
|
+
"resource_name_lower": resource_lower,
|
|
129
|
+
"table_name": f"{resource_lower}s",
|
|
130
|
+
"uppercase_name": resource_name.upper(),
|
|
131
|
+
"fields": parse_ts_fields(fields_str),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for comp in components:
|
|
135
|
+
# Create target directory
|
|
136
|
+
target_dir = os.path.join(feature_dir, os.path.normpath(comp["dir"]))
|
|
137
|
+
os.makedirs(target_dir, exist_ok=True)
|
|
138
|
+
|
|
139
|
+
target_file_name = f"{resource_lower}{comp['suffix']}{comp['ext']}"
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
template = env.get_template(comp["template"])
|
|
143
|
+
content = template.render(context)
|
|
144
|
+
|
|
145
|
+
with open(os.path.join(target_dir, target_file_name), "w", encoding="utf-8") as f:
|
|
146
|
+
f.write(content)
|
|
147
|
+
|
|
148
|
+
display_dir = comp["dir"] if comp["dir"] else "feature root"
|
|
149
|
+
typer.echo(f" - Created: {display_dir}/{target_file_name}")
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
# Tip: if no .scss.j2 template exists, create an empty file automatically
|
|
153
|
+
if comp["ext"] == ".scss":
|
|
154
|
+
with open(os.path.join(target_dir, target_file_name), "w") as f:
|
|
155
|
+
f.write("")
|
|
156
|
+
typer.echo(f" - Created (empty): {comp['dir']}/{target_file_name}")
|
|
157
|
+
else:
|
|
158
|
+
typer.secho(
|
|
159
|
+
f"Warning: Failed to generate {comp['template']}: {e}",
|
|
160
|
+
fg=typer.colors.YELLOW,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
typer.secho(f"{entity_name} feature successfully generated!", fg=typer.colors.GREEN)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django resource scaffolding generator.
|
|
3
|
+
Handles the creation of models, serializers, and views.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from devctl.orchestrator.scanner import detect_environment
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_django_resource(resource_name: str, fields_str: str, root_path: str = "."):
|
|
14
|
+
"""
|
|
15
|
+
Scaffolds a Django resource.
|
|
16
|
+
"""
|
|
17
|
+
env_state = detect_environment(root_path)
|
|
18
|
+
|
|
19
|
+
if not env_state["has_django"]:
|
|
20
|
+
typer.secho("❌ Error: No Django project detected here.", fg=typer.colors.RED)
|
|
21
|
+
raise typer.Exit(code=1)
|
|
22
|
+
|
|
23
|
+
django_root = env_state["django_path"]
|
|
24
|
+
resource_name.lower()
|
|
25
|
+
entity_name = resource_name.capitalize()
|
|
26
|
+
|
|
27
|
+
# Structure: core/models.py, core/serializers.py, core/views.py
|
|
28
|
+
# For simplicity, we inject into the 'core' app created during init
|
|
29
|
+
core_dir = os.path.join(django_root, "core")
|
|
30
|
+
if not os.path.exists(core_dir):
|
|
31
|
+
os.makedirs(core_dir, exist_ok=True)
|
|
32
|
+
|
|
33
|
+
typer.secho(f"⚙️ Generating Django resource '{entity_name}'...", fg=typer.colors.CYAN)
|
|
34
|
+
|
|
35
|
+
# 1. Append Model
|
|
36
|
+
model_snippet = f"""
|
|
37
|
+
class {entity_name}(models.Model):
|
|
38
|
+
# Fields: {fields_str}
|
|
39
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
40
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
41
|
+
|
|
42
|
+
def __str__(self):
|
|
43
|
+
return f"{entity_name} {{self.id}}"
|
|
44
|
+
"""
|
|
45
|
+
with open(os.path.join(core_dir, "models.py"), "a") as f:
|
|
46
|
+
f.write(model_snippet)
|
|
47
|
+
|
|
48
|
+
# 2. Append Serializer
|
|
49
|
+
serializer_path = os.path.join(core_dir, "serializers.py")
|
|
50
|
+
if not os.path.exists(serializer_path):
|
|
51
|
+
with open(serializer_path, "w") as f:
|
|
52
|
+
f.write("from rest_framework import serializers\nfrom .models import *\n")
|
|
53
|
+
|
|
54
|
+
serializer_snippet = f"""
|
|
55
|
+
class {entity_name}Serializer(serializers.ModelSerializer):
|
|
56
|
+
class Meta:
|
|
57
|
+
model = {entity_name}
|
|
58
|
+
fields = '__all__'
|
|
59
|
+
"""
|
|
60
|
+
with open(serializer_path, "a") as f:
|
|
61
|
+
f.write(serializer_snippet)
|
|
62
|
+
|
|
63
|
+
# 3. Append View
|
|
64
|
+
view_snippet = f"""
|
|
65
|
+
from rest_framework import viewsets
|
|
66
|
+
from .models import {entity_name}
|
|
67
|
+
from .serializers import {entity_name}Serializer
|
|
68
|
+
|
|
69
|
+
class {entity_name}ViewSet(viewsets.ModelViewSet):
|
|
70
|
+
queryset = {entity_name}.objects.all()
|
|
71
|
+
serializer_class = {entity_name}Serializer
|
|
72
|
+
"""
|
|
73
|
+
with open(os.path.join(core_dir, "views.py"), "a") as f:
|
|
74
|
+
f.write(view_snippet)
|
|
75
|
+
|
|
76
|
+
typer.secho(f"✅ {entity_name} Django feature successfully generated!", fg=typer.colors.GREEN)
|
|
77
|
+
typer.echo(" - Updated: core/models.py")
|
|
78
|
+
typer.echo(" - Updated: core/serializers.py")
|
|
79
|
+
typer.echo(" - Updated: core/views.py")
|