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,219 @@
1
+ """
2
+ Local development environment runner.
3
+ Handles parallel process management for multi-tier applications with log prefixing.
4
+ """
5
+
6
+ import os
7
+ import signal
8
+ import subprocess
9
+ import sys
10
+ import threading
11
+ import time
12
+ from pathlib import Path
13
+ from typing import List
14
+
15
+ import typer
16
+ from rich.console import Console
17
+ from rich.markup import escape
18
+
19
+ from devctl.generators.docker_scaffold import DockerProject
20
+ from devctl.utils.env_loader import get_project_env
21
+
22
+ console = Console()
23
+
24
+ # Global list to track processes for cleanup
25
+ active_processes = []
26
+ active_threads = []
27
+
28
+
29
+ def is_docker_running():
30
+ """Checks if the Docker daemon is active on the system."""
31
+ try:
32
+ subprocess.run(["docker", "info"], capture_output=True, check=True)
33
+ return True
34
+ except (subprocess.CalledProcessError, FileNotFoundError):
35
+ return False
36
+
37
+
38
+ def stream_logs(name: str, process: subprocess.Popen, color: str):
39
+ """Streams logs from a process with a colored prefix."""
40
+ try:
41
+ # Use line-buffered reading
42
+ for line in iter(process.stdout.readline, b""):
43
+ if line:
44
+ decoded_line = line.decode("utf-8", errors="ignore").rstrip()
45
+ console.print(f"[{color}]{name:>15} |[/{color}] {escape(decoded_line)}")
46
+ except Exception as e:
47
+ console.print(f"[red]Error streaming logs for {name}: {escape(str(e))}[/red]")
48
+
49
+
50
+ def _launch_process(p: DockerProject, cmd: List[str], color: str, label: str):
51
+ """Helper to launch a process with log streaming and .env loading."""
52
+ global active_processes, active_threads
53
+
54
+ typer.secho(f"Starting {label}: {p.name}...", fg=getattr(typer.colors, color.upper()))
55
+
56
+ # Load environment variables including .env if present
57
+ env = get_project_env(p.path)
58
+
59
+ proc = subprocess.Popen(
60
+ cmd,
61
+ cwd=str(p.path),
62
+ stdout=subprocess.PIPE,
63
+ stderr=subprocess.STDOUT,
64
+ bufsize=1,
65
+ env=env,
66
+ )
67
+ active_processes.append((p.name, proc))
68
+
69
+ t = threading.Thread(target=stream_logs, args=(p.name, proc, color), daemon=True)
70
+ t.start()
71
+ active_threads.append(t)
72
+
73
+
74
+ def launch_dev_environment(projects: List[DockerProject], docker_composes: List[Path]):
75
+ """
76
+ Launches the necessary processes in parallel with structured startup and log streaming.
77
+ """
78
+ global active_processes
79
+
80
+ def signal_handler(_sig, _frame):
81
+ typer.echo("\nShutdown requested. Cleaning up...")
82
+ cleanup_and_exit(docker_composes)
83
+
84
+ signal.signal(signal.SIGINT, signal_handler)
85
+ signal.signal(signal.SIGTERM, signal_handler)
86
+
87
+ try:
88
+ # 1. Start Databases
89
+ if docker_composes:
90
+ if not is_docker_running():
91
+ typer.secho("Error: Docker service is not running.", fg=typer.colors.RED)
92
+ sys.exit(1)
93
+
94
+ for compose_path in docker_composes:
95
+ typer.secho(
96
+ f"Starting Docker Compose DB in {compose_path}...",
97
+ fg=typer.colors.CYAN,
98
+ )
99
+ subprocess.run(
100
+ ["docker", "compose", "-f", "docker-compose-db.yml", "up", "-d"],
101
+ cwd=str(compose_path),
102
+ check=True,
103
+ )
104
+
105
+ typer.echo("Waiting 5s for databases to initialize...")
106
+ time.sleep(5)
107
+
108
+ # 2. Start Projects
109
+ for p in projects:
110
+ if p.kind == "spring":
111
+ _launch_process(p, ["./mvnw", "spring-boot:run"], "green", "Spring Boot")
112
+
113
+ elif p.kind == "angular":
114
+ _launch_process(p, ["npx", "ng", "serve"], "cyan", "Angular")
115
+
116
+ elif p.kind == "vue":
117
+ _launch_process(p, ["npm", "run", "dev"], "magenta", "Vue")
118
+
119
+ elif p.kind == "react":
120
+ _launch_process(p, ["npm", "run", "dev"], "blue", "React")
121
+
122
+ elif p.kind == "nextjs":
123
+ _launch_process(p, ["npm", "run", "dev"], "yellow", "NextJS")
124
+
125
+ elif p.kind == "svelte":
126
+ _launch_process(p, ["npm", "run", "dev"], "red", "Svelte")
127
+
128
+ elif p.kind == "nest":
129
+ _launch_process(p, ["npm", "run", "start:dev"], "magenta", "NestJS")
130
+
131
+ elif p.kind == "nodejs":
132
+ _launch_process(p, ["npm", "run", "dev"], "green", "NodeJS")
133
+
134
+ elif p.kind == "fastapi":
135
+ venv_python = os.path.join(str(p.path), ".venv", "bin", "python3")
136
+ if not os.path.exists(venv_python):
137
+ venv_python = "python3"
138
+ _launch_process(
139
+ p, [venv_python, "-m", "uvicorn", "main:app", "--reload"], "cyan", "FastAPI"
140
+ )
141
+
142
+ elif p.kind == "django":
143
+ venv_python = os.path.join(str(p.path), ".venv", "bin", "python3")
144
+ if not os.path.exists(venv_python):
145
+ venv_python = "python3"
146
+ _launch_process(p, [venv_python, "manage.py", "runserver"], "green", "Django")
147
+
148
+ elif p.kind == "go":
149
+ _launch_process(p, ["go", "run", "."], "cyan", "Go")
150
+
151
+ if not active_processes and not docker_composes:
152
+ typer.secho(
153
+ "Warning: No projects or databases detected to run.",
154
+ fg=typer.colors.YELLOW,
155
+ )
156
+ return
157
+
158
+ typer.secho(
159
+ "\nDevelopment environment active! Press Ctrl+C to stop everything gracefully.\n",
160
+ fg=typer.colors.GREEN,
161
+ bold=True,
162
+ )
163
+ # Keep the main thread alive
164
+ while True:
165
+ # Monitor process health
166
+ for name, proc in active_processes:
167
+ exit_code = proc.poll()
168
+ if exit_code is not None:
169
+ typer.secho(
170
+ f"\nError: {name} process terminated unexpectedly "
171
+ f"(Exit code: {exit_code}).",
172
+ fg=typer.colors.RED,
173
+ bold=True,
174
+ )
175
+ # Trigger shutdown logic
176
+ raise KeyboardInterrupt
177
+
178
+ time.sleep(1)
179
+ # Check if any process has died unexpectedly
180
+ # This is a bit redundant with the monitor above but kept for compatibility
181
+ for name, proc in active_processes:
182
+ if proc.poll() is not None:
183
+ typer.secho(
184
+ f"Warning: Process {name} exited with code {proc.returncode}",
185
+ fg=typer.colors.RED,
186
+ )
187
+ active_processes.remove((name, proc))
188
+
189
+ except Exception as e:
190
+ typer.secho(f"Error: A system error occurred: {e}", fg=typer.colors.RED)
191
+ cleanup_and_exit(docker_composes)
192
+
193
+
194
+ def cleanup_and_exit(docker_composes: List[Path]):
195
+ """Stops all active processes and docker containers."""
196
+ for name, proc in active_processes:
197
+ typer.echo(f"Closing {name}...")
198
+ proc.terminate()
199
+ try:
200
+ proc.wait(timeout=5)
201
+ except subprocess.TimeoutExpired:
202
+ typer.echo(f"Force killing {name}...")
203
+ proc.kill()
204
+
205
+ for compose_path in docker_composes:
206
+ typer.echo(f"Stopping Docker Compose DB in {compose_path}...")
207
+ try:
208
+ subprocess.run(
209
+ ["docker", "compose", "-f", "docker-compose-db.yml", "down", "-v"],
210
+ cwd=str(compose_path),
211
+ check=True,
212
+ stdout=subprocess.DEVNULL,
213
+ stderr=subprocess.DEVNULL,
214
+ )
215
+ except Exception:
216
+ typer.secho(f"Warning: Docker cleanup failed for {compose_path}", fg=typer.colors.RED)
217
+
218
+ typer.secho("Cleanup finished. Environment is clean.", fg=typer.colors.GREEN)
219
+ sys.exit(0)
@@ -0,0 +1,155 @@
1
+ """
2
+ Project scanner and environment detector.
3
+ Identifies Spring Boot, Angular, Vue.js, React, NextJS, NestJS, NodeJS, FastAPI,
4
+ Django, Svelte, Go, and Docker components in a directory tree.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+
11
+ # Directories to ignore during scanning
12
+ IGNORED_DIRECTORIES = {
13
+ "node_modules",
14
+ "target",
15
+ ".git",
16
+ ".angular",
17
+ "dist",
18
+ "build",
19
+ "venv",
20
+ ".venv",
21
+ "__pycache__",
22
+ ".next",
23
+ ".svelte-kit",
24
+ }
25
+
26
+
27
+ def detect_environment(root_path: str = "."):
28
+ """
29
+ Scans the directory tree and its subfolders to identify components.
30
+ Returns the state and absolute paths of each component.
31
+ """
32
+ root = Path(root_path).resolve()
33
+ env_state = {
34
+ "has_docker_compose": False,
35
+ "docker_path": None,
36
+ "has_spring": False,
37
+ "spring_path": None,
38
+ "has_angular": False,
39
+ "angular_path": None,
40
+ "has_vue": False,
41
+ "vue_path": None,
42
+ "has_react": False,
43
+ "react_path": None,
44
+ "has_nextjs": False,
45
+ "nextjs_path": None,
46
+ "has_nest": False,
47
+ "nest_path": None,
48
+ "has_nodejs": False,
49
+ "nodejs_path": None,
50
+ "has_fastapi": False,
51
+ "fastapi_path": None,
52
+ "has_django": False,
53
+ "django_path": None,
54
+ "has_svelte": False,
55
+ "svelte_path": None,
56
+ "has_go": False,
57
+ "go_path": None,
58
+ "project_root": str(root),
59
+ }
60
+
61
+ for dirpath, dirnames, filenames in os.walk(root):
62
+ # In-place modification of dirnames to prune the traversal
63
+ dirnames[:] = [d for d in dirnames if d not in IGNORED_DIRECTORIES]
64
+
65
+ current_path = Path(dirpath)
66
+ filename_set = set(filenames)
67
+
68
+ # 1. Docker Compose detection
69
+ if "docker-compose-db.yml" in filename_set and not env_state["has_docker_compose"]:
70
+ env_state["has_docker_compose"] = True
71
+ env_state["docker_path"] = str(current_path)
72
+
73
+ # 2. Spring Boot detection
74
+ if ("pom.xml" in filename_set or "mvnw" in filename_set) and not env_state["has_spring"]:
75
+ env_state["has_spring"] = True
76
+ env_state["spring_path"] = str(current_path)
77
+
78
+ # 3. Angular detection
79
+ if "angular.json" in filename_set and not env_state["has_angular"]:
80
+ env_state["has_angular"] = True
81
+ env_state["angular_path"] = str(current_path)
82
+
83
+ # 4. Vite-based detection (Vue/React)
84
+ vue_markers = {"vite.config.ts", "vite.config.js"}
85
+ if (vue_markers & filename_set) and not any([env_state["has_vue"], env_state["has_react"]]):
86
+ # Distinguish by package.json
87
+ pkg_path = current_path / "package.json"
88
+ if pkg_path.exists():
89
+ try:
90
+ pkg = json.loads(pkg_path.read_text(encoding="utf-8"))
91
+ deps = pkg.get("dependencies", {})
92
+ dev_deps = pkg.get("devDependencies", {})
93
+ all_deps = {**deps, **dev_deps}
94
+ if "react" in all_deps:
95
+ env_state["has_react"] = True
96
+ env_state["react_path"] = str(current_path)
97
+ else:
98
+ env_state["has_vue"] = True
99
+ env_state["vue_path"] = str(current_path)
100
+ except Exception:
101
+ env_state["has_vue"] = True
102
+ env_state["vue_path"] = str(current_path)
103
+ else:
104
+ env_state["has_vue"] = True
105
+ env_state["vue_path"] = str(current_path)
106
+
107
+ # 5. NestJS detection
108
+ if "nest-cli.json" in filename_set and not env_state["has_nest"]:
109
+ env_state["has_nest"] = True
110
+ env_state["nest_path"] = str(current_path)
111
+
112
+ # 6. NextJS detection
113
+ if any(f.startswith("next.config.") for f in filename_set) and not env_state["has_nextjs"]:
114
+ env_state["has_nextjs"] = True
115
+ env_state["nextjs_path"] = str(current_path)
116
+
117
+ # 7. Generic NodeJS detection (if not already caught)
118
+ if "package.json" in filename_set and not any(
119
+ [
120
+ env_state["has_angular"],
121
+ env_state["has_vue"],
122
+ env_state["has_react"],
123
+ env_state["has_nest"],
124
+ env_state["has_nextjs"],
125
+ ]
126
+ ):
127
+ env_state["has_nodejs"] = True
128
+ env_state["nodejs_path"] = str(current_path)
129
+
130
+ # 8. Python detection (FastAPI/Django)
131
+ if "requirements.txt" in filename_set:
132
+ req_path = current_path / "requirements.txt"
133
+ try:
134
+ req_content = req_path.read_text(encoding="utf-8").lower()
135
+ if "fastapi" in req_content and not env_state["has_fastapi"]:
136
+ env_state["has_fastapi"] = True
137
+ env_state["fastapi_path"] = str(current_path)
138
+ if "django" in req_content and not env_state["has_django"]:
139
+ env_state["has_django"] = True
140
+ env_state["django_path"] = str(current_path)
141
+ except (OSError, UnicodeDecodeError):
142
+ # Ignore unreadable requirements files during environment scanning
143
+ pass
144
+
145
+ # 9. Svelte detection
146
+ if "svelte.config.js" in filename_set and not env_state["has_svelte"]:
147
+ env_state["has_svelte"] = True
148
+ env_state["svelte_path"] = str(current_path)
149
+
150
+ # 10. Go detection
151
+ if "go.mod" in filename_set and not env_state["has_go"]:
152
+ env_state["has_go"] = True
153
+ env_state["go_path"] = str(current_path)
154
+
155
+ return env_state
@@ -0,0 +1,4 @@
1
+ export const environment = {
2
+ production: false,
3
+ apiUrl: '/api'
4
+ };
@@ -0,0 +1,5 @@
1
+ export const environment = {
2
+ production: true,
3
+ // TODO: Remplacer par l'URL de production de ton backend
4
+ apiUrl: 'https://api.ton-domaine.com/api'
5
+ };
@@ -0,0 +1,8 @@
1
+ {
2
+ "/api": {
3
+ "target": "http://localhost:8080",
4
+ "secure": false,
5
+ "changeOrigin": true,
6
+ "logLevel": "debug"
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ export interface {{ entity_name }}Request {
2
+ {% for field in fields %}
3
+ {{ field.name }}: {{ field.ts_type }};
4
+ {% endfor %}
5
+ }
@@ -0,0 +1,6 @@
1
+ export interface {{ entity_name }}Response {
2
+ id: number;
3
+ {% for field in fields %}
4
+ {{ field.name }}: {{ field.ts_type }};
5
+ {% endfor %}
6
+ }
@@ -0,0 +1,21 @@
1
+ <div class="form-container">
2
+ <h2>{{ '{{' }} isEditMode ? 'Modifier' : 'Nouveau' {{ '}}' }} {{ entity_name }}</h2>
3
+
4
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
5
+ {% for field in fields %}
6
+ <div class="form-group">
7
+ <label for="{{ field.name }}">{{ field.name | capitalize }}</label>
8
+ <input
9
+ id="{{ field.name }}"
10
+ type="{% if field.ts_type == 'number' %}number{% else %}text{% endif %}"
11
+ formControlName="{{ field.name }}"
12
+ />
13
+ </div>
14
+ {% endfor %}
15
+
16
+ <div class="form-actions">
17
+ <button type="submit" [disabled]="form.invalid">Enregistrer</button>
18
+ <a routerLink="/{{ resource_name_lower }}s" class="btn-cancel">Annuler</a>
19
+ </div>
20
+ </form>
21
+ </div>
@@ -0,0 +1,63 @@
1
+ import { Component, inject, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
4
+ import { ActivatedRoute, Router, RouterModule } from '@angular/router';
5
+ import { {{ entity_name }}Service } from '../../services/{{ resource_name_lower }}.service';
6
+ import { {{ entity_name }}Request } from '../../models/{{ resource_name_lower }}-request.model';
7
+
8
+ @Component({
9
+ selector: 'app-{{ resource_name_lower }}-form',
10
+ standalone: true,
11
+ imports: [CommonModule, ReactiveFormsModule, RouterModule],
12
+ templateUrl: './{{ resource_name_lower }}-form.component.html',
13
+ styleUrls: ['./{{ resource_name_lower }}-form.component.scss']
14
+ })
15
+ export class {{ entity_name }}FormComponent implements OnInit {
16
+ private fb = inject(FormBuilder);
17
+ private service = inject({{ entity_name }}Service);
18
+ private router = inject(Router);
19
+ private route = inject(ActivatedRoute);
20
+
21
+ form!: FormGroup;
22
+ isEditMode = false;
23
+ currentId?: number;
24
+
25
+ ngOnInit(): void {
26
+ this.initForm();
27
+ this.checkEditMode();
28
+ }
29
+
30
+ private initForm(): void {
31
+ this.form = this.fb.group({
32
+ {% for field in fields %}
33
+ {{ field.name }}: ['', Validators.required],
34
+ {% endfor %}
35
+ });
36
+ }
37
+
38
+ private checkEditMode(): void {
39
+ const id = this.route.snapshot.paramMap.get('id');
40
+ if (id) {
41
+ this.isEditMode = true;
42
+ this.currentId = +id;
43
+ this.service.getById(this.currentId).subscribe(data => {
44
+ this.form.patchValue(data);
45
+ });
46
+ }
47
+ }
48
+
49
+ onSubmit(): void {
50
+ if (this.form.invalid) return;
51
+
52
+ const request: {{ entity_name }}Request = this.form.value;
53
+ const action$ = this.isEditMode && this.currentId
54
+ ? this.service.update(this.currentId, request)
55
+ : this.service.create(request);
56
+
57
+ action$.subscribe({
58
+ // Redirige vers la liste après succès
59
+ next: () => this.router.navigate(['/{{ resource_name_lower }}s']),
60
+ error: (err) => console.error('Erreur de sauvegarde', err)
61
+ });
62
+ }
63
+ }
@@ -0,0 +1,28 @@
1
+ <div class="header-actions">
2
+ <h2>Liste des {{ entity_name }}s</h2>
3
+ <a routerLink="new" class="btn-primary">Nouveau {{ entity_name }}</a>
4
+ </div>
5
+
6
+ <table>
7
+ <thead>
8
+ <tr>
9
+ <th>ID</th>
10
+ {% for field in fields %}
11
+ <th>{{ field.name | capitalize }}</th>
12
+ {% endfor %}
13
+ <th>Actions</th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <tr *ngFor="let item of items">
18
+ <td>{{ '{{' }} item.id {{ '}}' }}</td>
19
+ {% for field in fields %}
20
+ <td>{{ '{{' }} item.{{ field.name }} {{ '}}' }}</td>
21
+ {% endfor %}
22
+ <td>
23
+ <a [routerLink]="['edit', item.id]" class="btn-edit">Modifier</a>
24
+ <button (click)="delete(item.id)" class="btn-delete">Supprimer</button>
25
+ </td>
26
+ </tr>
27
+ </tbody>
28
+ </table>
@@ -0,0 +1,34 @@
1
+ import { Component, inject, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { RouterModule } from '@angular/router';
4
+ import { {{ entity_name }}Service } from '../../services/{{ resource_name_lower }}.service';
5
+ import { {{ entity_name }}Response } from '../../models/{{ resource_name_lower }}-response.model';
6
+
7
+ @Component({
8
+ selector: 'app-{{ resource_name_lower }}-list',
9
+ standalone: true,
10
+ imports: [CommonModule, RouterModule],
11
+ templateUrl: './{{ resource_name_lower }}-list.component.html',
12
+ styleUrls: ['./{{ resource_name_lower }}-list.component.scss']
13
+ })
14
+ export class {{ entity_name }}ListComponent implements OnInit {
15
+ private service = inject({{ entity_name }}Service);
16
+ items: {{ entity_name }}Response[] = [];
17
+
18
+ ngOnInit(): void {
19
+ this.loadItems();
20
+ }
21
+
22
+ loadItems(): void {
23
+ this.service.getAll().subscribe({
24
+ next: (data) => this.items = data,
25
+ error: (err) => console.error('Erreur lors du chargement', err)
26
+ });
27
+ }
28
+
29
+ delete(id: number): void {
30
+ if (confirm('Voulez-vous vraiment supprimer cet élément ?')) {
31
+ this.service.delete(id).subscribe(() => this.loadItems());
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,9 @@
1
+ import { Routes } from '@angular/router';
2
+ import { {{ entity_name }}ListComponent } from './pages/{{ resource_name_lower }}-list/{{ resource_name_lower }}-list.component';
3
+ import { {{ entity_name }}FormComponent } from './pages/{{ resource_name_lower }}-form/{{ resource_name_lower }}-form.component';
4
+
5
+ export const {{ uppercase_name }}_ROUTES: Routes = [
6
+ { path: '', component: {{ entity_name }}ListComponent },
7
+ { path: 'new', component: {{ entity_name }}FormComponent },
8
+ { path: 'edit/:id', component: {{ entity_name }}FormComponent }
9
+ ];
@@ -0,0 +1,34 @@
1
+ import { inject, Injectable } from '@angular/core';
2
+ import { HttpClient } from '@angular/common/http';
3
+ import { Observable } from 'rxjs';
4
+ import { environment } from '../../../../environments/environment';
5
+ import { {{ entity_name }}Request } from '../models/{{ resource_name_lower }}-request.model';
6
+ import { {{ entity_name }}Response } from '../models/{{ resource_name_lower }}-response.model';
7
+
8
+ @Injectable({
9
+ providedIn: 'root'
10
+ })
11
+ export class {{ entity_name }}Service {
12
+ private http = inject(HttpClient);
13
+ private apiUrl = `${environment.apiUrl}/{{ table_name }}`; // URL Spring Boot
14
+
15
+ getAll(): Observable<{{ entity_name }}Response[]> {
16
+ return this.http.get<{{ entity_name }}Response[]>(this.apiUrl);
17
+ }
18
+
19
+ getById(id: number): Observable<{{ entity_name }}Response> {
20
+ return this.http.get<{{ entity_name }}Response>(`${this.apiUrl}/${id}`);
21
+ }
22
+
23
+ create(request: {{ entity_name }}Request): Observable<{{ entity_name }}Response> {
24
+ return this.http.post<{{ entity_name }}Response>(this.apiUrl, request);
25
+ }
26
+
27
+ update(id: number, request: {{ entity_name }}Request): Observable<{{ entity_name }}Response> {
28
+ return this.http.put<{{ entity_name }}Response>(`${this.apiUrl}/${id}`, request);
29
+ }
30
+
31
+ delete(id: number): Observable<void> {
32
+ return this.http.delete<void>(`${this.apiUrl}/${id}`);
33
+ }
34
+ }
@@ -0,0 +1,37 @@
1
+ services:
2
+ {% for service in services %}
3
+ {{ service.service_name }}:
4
+ build:
5
+ context: {{ service.relative_context }}
6
+ dockerfile: Dockerfile
7
+ {% if service.kind == 'spring' and service.db %}
8
+ depends_on:
9
+ - {{ service.db.service_name }}
10
+ environment:
11
+ - SPRING_DATASOURCE_URL=jdbc:{{ service.db.type }}://{{ service.db.service_name }}:{{ service.db.internal_port }}/{{ service.db.name }}
12
+ - SPRING_DATASOURCE_USERNAME={{ service.db.user }}
13
+ - SPRING_DATASOURCE_PASSWORD={{ service.db.password }}
14
+ {% endif %}
15
+ {% if service.kind == 'angular' or service.kind == 'vue' %}
16
+ ports:
17
+ - "80:80"
18
+ {% endif %}
19
+
20
+ {% endfor %}
21
+ {% for db in databases %}
22
+ {{ db.service_name }}:
23
+ image: {{ db.image }}
24
+ environment:
25
+ {% for key, value in db.env.items() %}
26
+ {{ key }}: {{ value }}
27
+ {% endfor %}
28
+ ports:
29
+ - "{{ db.port }}:{{ db.internal_port }}"
30
+ volumes:
31
+ - {{ db.volume_name }}:{{ db.volume_path }}
32
+
33
+ {% endfor %}
34
+ volumes:
35
+ {% for db in databases %}
36
+ {{ db.volume_name }}:
37
+ {% endfor %}
@@ -0,0 +1,21 @@
1
+ # Production Dockerfile for Django
2
+ # Generated by devctl
3
+
4
+ FROM python:3.11-slim
5
+
6
+ WORKDIR /app
7
+
8
+ # Install system dependencies
9
+ RUN apt-get update && apk add --no-cache libpq-dev gcc
10
+
11
+ COPY requirements.txt .
12
+ RUN pip install --no-cache-dir -r requirements.txt
13
+
14
+ COPY . .
15
+
16
+ EXPOSE 8000
17
+
18
+ # Using gunicorn for production
19
+ RUN pip install gunicorn
20
+
21
+ CMD ["gunicorn", "--bind", "0.0.0.0:8000", "core.wsgi:application"]