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,111 @@
1
+ """
2
+ Vue.js resource scaffolding generator.
3
+ Handles the creation of features including models, services, and views.
4
+ """
5
+
6
+ import os
7
+
8
+ import typer
9
+ from jinja2 import Environment, FileSystemLoader, select_autoescape
10
+
11
+ from devctl.generators.scaffold_angular import parse_ts_fields
12
+ from devctl.orchestrator.scanner import detect_environment
13
+
14
+
15
+ def generate_vue_resource(resource_name: str, fields_str: str, root_path: str = "."):
16
+ """
17
+ Orchestrates the creation of a complete Vue 3 feature.
18
+ """
19
+ env_state = detect_environment(root_path)
20
+
21
+ if not env_state["has_vue"]:
22
+ typer.secho("Error: No Vue.js project detected here.", fg=typer.colors.RED)
23
+ raise typer.Exit(code=1)
24
+
25
+ vue_root = env_state["vue_path"]
26
+ resource_lower = resource_name.lower()
27
+ entity_name = resource_name.capitalize()
28
+
29
+ # Feature target directory: src/features/resource_name
30
+ feature_dir = os.path.join(vue_root, "src", "features", resource_lower)
31
+
32
+ # Subdirectories and files to generate
33
+ components = [
34
+ # Models
35
+ {
36
+ "dir": "models",
37
+ "suffix": "Models",
38
+ "ext": ".ts",
39
+ "template": "models.ts.j2",
40
+ },
41
+ # Service
42
+ {
43
+ "dir": "services",
44
+ "suffix": "Service",
45
+ "ext": ".ts",
46
+ "template": "service.ts.j2",
47
+ },
48
+ # Routes
49
+ {
50
+ "dir": "",
51
+ "suffix": "Routes",
52
+ "ext": ".ts",
53
+ "template": "routes.ts.j2",
54
+ },
55
+ # List Component
56
+ {
57
+ "dir": "views",
58
+ "suffix": "List",
59
+ "ext": ".vue",
60
+ "template": "List.vue.j2",
61
+ },
62
+ # Form Component
63
+ {
64
+ "dir": "views",
65
+ "suffix": "Form",
66
+ "ext": ".vue",
67
+ "template": "Form.vue.j2",
68
+ },
69
+ ]
70
+
71
+ templates_dir = os.path.join(os.path.dirname(__file__), "..", "templates", "vue", "feature")
72
+ env = Environment(
73
+ loader=FileSystemLoader(templates_dir),
74
+ autoescape=select_autoescape(["html", "xml"]),
75
+ )
76
+
77
+ typer.secho(f"Generating Vue.js feature '{entity_name}'...", fg=typer.colors.CYAN)
78
+
79
+ # Template data
80
+ context = {
81
+ "entity_name": entity_name,
82
+ "resource_name_lower": resource_lower,
83
+ "table_name": f"{resource_lower}s",
84
+ "uppercase_name": resource_name.upper(),
85
+ "fields": parse_ts_fields(fields_str),
86
+ }
87
+
88
+ for comp in components:
89
+ # Create target directory
90
+ target_dir = os.path.join(feature_dir, os.path.normpath(comp["dir"]))
91
+ os.makedirs(target_dir, exist_ok=True)
92
+
93
+ target_file_name = f"{entity_name}{comp['suffix']}{comp['ext']}"
94
+ # Adjust name for routes to be lowercase e.g. taskRoutes.ts
95
+ if comp["suffix"] == "Routes":
96
+ target_file_name = f"{resource_lower}{comp['suffix']}{comp['ext']}"
97
+
98
+ try:
99
+ template = env.get_template(comp["template"])
100
+ content = template.render(context)
101
+
102
+ with open(os.path.join(target_dir, target_file_name), "w", encoding="utf-8") as f:
103
+ f.write(content)
104
+
105
+ display_dir = comp["dir"] if comp["dir"] else "feature root"
106
+ typer.echo(f" - Created: {display_dir}/{target_file_name}")
107
+
108
+ except Exception as e:
109
+ typer.secho(f"Warning: Error on {comp['template']}: {e}", fg=typer.colors.YELLOW)
110
+
111
+ typer.secho(f"{entity_name} Vue feature successfully generated!", fg=typer.colors.GREEN)
@@ -0,0 +1,221 @@
1
+ """
2
+ Generators for Spring Boot projects.
3
+ Includes boilerplate downloading via Spring Initializr and POM patching.
4
+ """
5
+
6
+ import io
7
+ import os
8
+ import stat
9
+ import xml.etree.ElementTree as ET
10
+ import zipfile
11
+
12
+ import requests
13
+ import typer
14
+
15
+ from devctl.generators.scaffold_spring import generate_spring_security
16
+
17
+
18
+ def patch_pom_xml(project_path: str):
19
+ """
20
+ Surgically adds missing JJWT and MapStruct dependencies to pom.xml,
21
+ and configures annotation processors for Lombok + MapStruct.
22
+ """
23
+ pom_path = os.path.join(project_path, "pom.xml")
24
+ if not os.path.exists(pom_path):
25
+ return
26
+
27
+ # Register Maven namespace to avoid "ns0" prefixes
28
+ ns = "http://maven.apache.org/POM/4.0.0"
29
+ ET.register_namespace("", ns)
30
+ tree = ET.parse(pom_path)
31
+ root = tree.getroot()
32
+ ns_map = {"m": ns}
33
+
34
+ # 1. Add Dependencies
35
+ dependencies = root.find("m:dependencies", ns_map)
36
+ if dependencies is None:
37
+ dependencies = ET.SubElement(root, "{%s}dependencies" % ns)
38
+
39
+ # JJWT & MapStruct versions
40
+ jjwt_version = "0.12.5"
41
+ mapstruct_version = "1.5.5.Final"
42
+ lombok_version = "1.18.30"
43
+ lombok_mapstruct_binding_version = "0.2.0"
44
+
45
+ extra_deps = [
46
+ ("io.jsonwebtoken", "jjwt-api", jjwt_version, None),
47
+ ("io.jsonwebtoken", "jjwt-impl", jjwt_version, "runtime"),
48
+ ("io.jsonwebtoken", "jjwt-jackson", jjwt_version, "runtime"),
49
+ ("org.mapstruct", "mapstruct", mapstruct_version, None),
50
+ ]
51
+
52
+ # Helper to find if a dependency already exists
53
+ def dep_exists(gid, aid):
54
+ for dep in dependencies.findall("m:dependency", ns_map):
55
+ g = dep.find("m:groupId", ns_map)
56
+ a = dep.find("m:artifactId", ns_map)
57
+ if g is not None and a is not None and g.text == gid and a.text == aid:
58
+ return True
59
+ return False
60
+
61
+ for gid, aid, ver, scope in extra_deps:
62
+ if not dep_exists(gid, aid):
63
+ dep = ET.SubElement(dependencies, "{%s}dependency" % ns)
64
+ ET.SubElement(dep, "{%s}groupId" % ns).text = gid
65
+ ET.SubElement(dep, "{%s}artifactId" % ns).text = aid
66
+ ET.SubElement(dep, "{%s}version" % ns).text = ver
67
+ if scope:
68
+ ET.SubElement(dep, "{%s}scope" % ns).text = scope
69
+
70
+ # 2. Configure Annotation Processors
71
+ processors = [
72
+ ("org.projectlombok", "lombok", lombok_version),
73
+ ("org.projectlombok", "lombok-mapstruct-binding", lombok_mapstruct_binding_version),
74
+ ("org.mapstruct", "mapstruct-processor", mapstruct_version),
75
+ ]
76
+
77
+ def update_annotation_paths(parent_element):
78
+ ap_paths = parent_element.find("m:annotationProcessorPaths", ns_map)
79
+ if ap_paths is None:
80
+ ap_paths = ET.SubElement(parent_element, "{%s}annotationProcessorPaths" % ns)
81
+
82
+ for gid, aid, ver in processors:
83
+ # Check if this processor already exists in this block
84
+ exists = False
85
+ for path in ap_paths.findall("m:path", ns_map):
86
+ g = path.find("m:groupId", ns_map)
87
+ a = path.find("m:artifactId", ns_map)
88
+ if g is not None and a is not None and g.text == gid and a.text == aid:
89
+ # Update version if it exists
90
+ v = path.find("m:version", ns_map)
91
+ if v is not None:
92
+ v.text = ver
93
+ else:
94
+ ET.SubElement(path, "{%s}version" % ns).text = ver
95
+ exists = True
96
+ break
97
+
98
+ if not exists:
99
+ path = ET.SubElement(ap_paths, "{%s}path" % ns)
100
+ ET.SubElement(path, "{%s}groupId" % ns).text = gid
101
+ ET.SubElement(path, "{%s}artifactId" % ns).text = aid
102
+ ET.SubElement(path, "{%s}version" % ns).text = ver
103
+
104
+ # Find or create maven-compiler-plugin
105
+ compiler_plugin = None
106
+ for plugin in root.findall(".//m:plugin", ns_map):
107
+ aid = plugin.find("m:artifactId", ns_map)
108
+ if aid is not None and aid.text == "maven-compiler-plugin":
109
+ compiler_plugin = plugin
110
+ break
111
+
112
+ if compiler_plugin is None:
113
+ build = root.find("m:build", ns_map)
114
+ if build is None:
115
+ build = ET.SubElement(root, "{%s}build" % ns)
116
+ plugins = build.find("m:plugins", ns_map)
117
+ if plugins is None:
118
+ plugins = ET.SubElement(build, "{%s}plugins" % ns)
119
+
120
+ compiler_plugin = ET.SubElement(plugins, "{%s}plugin" % ns)
121
+ ET.SubElement(compiler_plugin, "{%s}groupId" % ns).text = "org.apache.maven.plugins"
122
+ ET.SubElement(compiler_plugin, "{%s}artifactId" % ns).text = "maven-compiler-plugin"
123
+ ET.SubElement(compiler_plugin, "{%s}version" % ns).text = "3.11.0"
124
+
125
+ # Update global configuration
126
+ config = compiler_plugin.find("m:configuration", ns_map)
127
+ if config is None:
128
+ config = ET.SubElement(compiler_plugin, "{%s}configuration" % ns)
129
+ update_annotation_paths(config)
130
+
131
+ # Update configurations inside executions
132
+ executions = compiler_plugin.find("m:executions", ns_map)
133
+ if executions is not None:
134
+ for execution in executions.findall("m:execution", ns_map):
135
+ exec_config = execution.find("m:configuration", ns_map)
136
+ if exec_config is not None:
137
+ update_annotation_paths(exec_config)
138
+
139
+ # Write back to file
140
+ tree.write(pom_path, encoding="utf-8", xml_declaration=True)
141
+
142
+
143
+ def download_spring_boilerplate(project_name: str, db_type: str = "postgres"):
144
+ """
145
+ Downloads and extracts a Spring Boot project via the start.spring.io API.
146
+ Automatically makes the Maven wrapper executable on Unix.
147
+ """
148
+ typer.secho(
149
+ f"Generating Spring Boot backend '{project_name}' (Driver: {db_type})...",
150
+ fg=typer.colors.CYAN,
151
+ )
152
+
153
+ # Java rule: a package name cannot contain hyphens
154
+ safe_package_name = project_name.replace("-", "").replace("_", "").lower()
155
+
156
+ # Dynamic mapping for the Spring API
157
+ db_dependency = "postgresql" if db_type == "postgres" else "mysql"
158
+ official_deps = [
159
+ "web",
160
+ "lombok",
161
+ "data-jpa",
162
+ "validation",
163
+ "security",
164
+ "devtools",
165
+ "thymeleaf",
166
+ db_dependency, # postgresql or mysql
167
+ ]
168
+ dependencies = ",".join(official_deps)
169
+
170
+ # Spring Initializr API parameters
171
+ params = {
172
+ "type": "maven-project",
173
+ "language": "java",
174
+ "baseDir": project_name,
175
+ "groupId": "com.devctl",
176
+ "artifactId": project_name,
177
+ "name": project_name,
178
+ "description": "Spring Boot project generated by devctl",
179
+ "packageName": f"com.devctl.{safe_package_name}",
180
+ "packaging": "jar",
181
+ "javaVersion": "17",
182
+ "dependencies": dependencies,
183
+ }
184
+
185
+ url = "https://start.spring.io/starter.zip"
186
+
187
+ try:
188
+ response = requests.get(url, params=params)
189
+
190
+ if response.status_code != 200:
191
+ typer.secho(f"Error: API Rejected: {response.text}", fg=typer.colors.RED)
192
+ return False
193
+
194
+ z = zipfile.ZipFile(io.BytesIO(response.content))
195
+ z.extractall(os.getcwd())
196
+
197
+ project_path = os.path.join(os.getcwd(), project_name)
198
+
199
+ # Patch the POM to add missing libraries (JJWT, MapStruct)
200
+ patch_pom_xml(project_path)
201
+
202
+ mvnw_path = os.path.join(project_path, "mvnw")
203
+ if os.path.exists(mvnw_path):
204
+ # Get current file permissions
205
+ st = os.stat(mvnw_path)
206
+ # Add execute permission (stat.S_IEXEC) for the current user
207
+ os.chmod(mvnw_path, st.st_mode | stat.S_IEXEC)
208
+
209
+ os.chdir(project_name)
210
+ generate_spring_security()
211
+ os.chdir("..")
212
+
213
+ typer.secho(
214
+ f"Backend successfully generated in folder ./{project_name}!",
215
+ fg=typer.colors.GREEN,
216
+ )
217
+ return True
218
+
219
+ except requests.exceptions.RequestException as e:
220
+ typer.secho(f"Error: Network error contacting API: {e}", fg=typer.colors.RED)
221
+ return False
@@ -0,0 +1,52 @@
1
+ """
2
+ Generators for Svelte projects.
3
+ Includes boilerplate generation via create-svelte.
4
+ """
5
+
6
+ import os
7
+ import subprocess
8
+
9
+ import typer
10
+
11
+
12
+ def generate_svelte_boilerplate(project_name: str) -> bool:
13
+ """
14
+ Generates a new SvelteKit project using create-svelte via npx.
15
+ """
16
+ typer.secho(f"Generating Svelte project '{project_name}'...", fg=typer.colors.CYAN)
17
+ safe_name = project_name.lower().replace("_", "-")
18
+
19
+ try:
20
+ typer.secho("Scaffolding SvelteKit project...", fg=typer.colors.CYAN)
21
+ # Using a non-interactive way to scaffold svelte
22
+ # We'll use the 'skeleton' template with TypeScript
23
+ subprocess.run(
24
+ [
25
+ "npx",
26
+ "sv",
27
+ "create",
28
+ safe_name,
29
+ "--template",
30
+ "skeleton",
31
+ "--types",
32
+ "typescript",
33
+ "--no-install",
34
+ "--no-git",
35
+ ],
36
+ check=True,
37
+ )
38
+
39
+ project_full_path = os.path.join(os.getcwd(), safe_name)
40
+
41
+ typer.secho("Installing npm dependencies...", fg=typer.colors.CYAN)
42
+ subprocess.run(["npm", "install"], cwd=project_full_path, check=True)
43
+
44
+ typer.secho(f"Svelte project '{safe_name}' successfully generated!", fg=typer.colors.GREEN)
45
+ return True
46
+
47
+ except subprocess.CalledProcessError as e:
48
+ typer.secho(f"Error: Svelte creation failed with code: {e.returncode}", fg=typer.colors.RED)
49
+ return False
50
+ except Exception as e:
51
+ typer.secho(f"Error: Svelte initialization failed: {e}", fg=typer.colors.RED)
52
+ return False
@@ -0,0 +1,105 @@
1
+ """
2
+ Generators for Vue.js projects via Vite.
3
+ Includes boilerplate generation, proxy setup, and router configuration.
4
+ """
5
+
6
+ import os
7
+ import subprocess
8
+
9
+ import typer
10
+ from jinja2 import Environment, FileSystemLoader
11
+
12
+
13
+ def setup_vue_proxy(project_path: str):
14
+ """
15
+ Replaces the default vite.config.ts with our version including the proxy.
16
+ """
17
+ typer.secho("Configuring Vite Proxy for Spring Boot...", fg=typer.colors.CYAN)
18
+
19
+ templates_dir = os.path.join(os.path.dirname(__file__), "..", "templates", "vue", "config")
20
+ env = Environment(loader=FileSystemLoader(templates_dir))
21
+
22
+ target_path = os.path.join(project_path, "vite.config.ts")
23
+
24
+ try:
25
+ template = env.get_template("vite.config.ts.j2")
26
+ content = template.render()
27
+ with open(target_path, "w", encoding="utf-8") as f:
28
+ f.write(content)
29
+ typer.echo(" - vite.config.ts updated with /api proxy.")
30
+ except Exception as e:
31
+ typer.secho(f"Warning: Error configuring proxy: {e}", fg=typer.colors.YELLOW)
32
+
33
+
34
+ def setup_vue_router(project_path: str):
35
+ """
36
+ Installs vue-router and configures the base architecture (main.ts, router, App.vue).
37
+ """
38
+ typer.secho("Installing and configuring vue-router...", fg=typer.colors.CYAN)
39
+
40
+ try:
41
+ # 1. NPM package installation
42
+ subprocess.run(
43
+ ["npm", "install", "vue-router@4"],
44
+ cwd=project_path,
45
+ check=True,
46
+ stdout=subprocess.DEVNULL,
47
+ )
48
+
49
+ # 2. Router directory creation
50
+ src_dir = os.path.join(project_path, "src")
51
+ router_dir = os.path.join(src_dir, "router")
52
+ os.makedirs(router_dir, exist_ok=True)
53
+
54
+ # 3. Jinja2 template rendering
55
+ templates_dir = os.path.join(os.path.dirname(__file__), "..", "templates", "vue", "config")
56
+ env = Environment(loader=FileSystemLoader(templates_dir))
57
+
58
+ files_to_generate = {
59
+ "router.ts.j2": os.path.join(router_dir, "index.ts"),
60
+ "main.ts.j2": os.path.join(src_dir, "main.ts"),
61
+ "App.vue.j2": os.path.join(src_dir, "App.vue"),
62
+ }
63
+
64
+ for tpl_name, target_path in files_to_generate.items():
65
+ template = env.get_template(tpl_name)
66
+ content = template.render()
67
+ with open(target_path, "w", encoding="utf-8") as f:
68
+ f.write(content)
69
+
70
+ typer.echo(" - Navigation architecture ready.")
71
+ except Exception as e:
72
+ typer.secho(f"Warning: Error configuring router: {e}", fg=typer.colors.YELLOW)
73
+
74
+
75
+ def generate_vue_boilerplate(project_name: str) -> bool:
76
+ """
77
+ Generates a Vue 3 + TypeScript project via Vite.
78
+ """
79
+ typer.secho(f"Generating Vue.js frontend '{project_name}' via Vite...", fg=typer.colors.CYAN)
80
+ safe_name = project_name.lower().replace("_", "-")
81
+
82
+ try:
83
+ typer.secho("Scaffolding Vite project...", fg=typer.colors.CYAN)
84
+ subprocess.run(
85
+ ["npm", "create", "vite@latest", safe_name, "--", "--template", "vue-ts"], check=True
86
+ )
87
+
88
+ project_full_path = os.path.join(os.getcwd(), safe_name)
89
+
90
+ typer.secho("Installing npm dependencies...", fg=typer.colors.CYAN)
91
+ subprocess.run(["npm", "install"], cwd=project_full_path, check=True)
92
+
93
+ # --- CALL OUR TWO CONFIGURATORS ---
94
+ setup_vue_proxy(project_full_path)
95
+ setup_vue_router(project_full_path)
96
+ # ----------------------------------------
97
+
98
+ typer.secho(f"Vue.js frontend '{safe_name}' successfully generated!", fg=typer.colors.GREEN)
99
+ return True
100
+
101
+ except subprocess.CalledProcessError as e:
102
+ typer.secho(
103
+ f"Error: Vue/Vite process failed with code: {e.returncode}", fg=typer.colors.RED
104
+ )
105
+ return False
devctl/main.py ADDED
@@ -0,0 +1,45 @@
1
+ import typer
2
+
3
+ # Import command modules
4
+ from devctl.commands import add, deploy, docker, init, run
5
+
6
+ # Create the main Typer application
7
+ app = typer.Typer(help="devctl: Local orchestrator for your Spring/Angular projects")
8
+
9
+ # Register sub-commands
10
+ app.add_typer(init.app, name="init", help="Initialize a new project with its codebase.")
11
+ app.add_typer(run.app, name="run", help="Launch the local development environment in parallel.")
12
+ app.add_typer(add.app, name="add", help="Generate code and business resources.")
13
+
14
+ app.command("dockerize", help="Scaffold Dockerfiles for supported projects.")(docker.dockerize)
15
+ app.command("deploy", help="Generate a global docker-compose-prod.yml for the entire project.")(
16
+ deploy.deploy
17
+ )
18
+
19
+
20
+ @app.callback()
21
+ def callback():
22
+ """
23
+ devctl: Local orchestrator for your projects
24
+ """
25
+ # This empty callback allows Typer to understand it handles a multi-command menu
26
+ pass
27
+
28
+
29
+ @app.command()
30
+ def ping():
31
+ """
32
+ Health check command to verify the CLI is responding.
33
+ """
34
+ typer.secho("pong! The devctl CLI is perfectly operational.", fg=typer.colors.GREEN, bold=True)
35
+
36
+
37
+ def main():
38
+ """
39
+ Entry point called by the operating system (via pyproject.toml)
40
+ """
41
+ app()
42
+
43
+
44
+ if __name__ == "__main__":
45
+ main()
@@ -0,0 +1,3 @@
1
+ """
2
+ Core orchestration logic for scanning and running the development environment.
3
+ """
@@ -0,0 +1,64 @@
1
+ """
2
+ Configuration builder for Spring Boot projects.
3
+ Generates docker-compose-db.yml and application.properties with dynamic database settings.
4
+ """
5
+
6
+ import os
7
+
8
+ import typer
9
+ from jinja2 import Environment, FileSystemLoader
10
+
11
+
12
+ def generate_config(project_name: str, db_type: str = "postgres", custom_port: int = None):
13
+ """
14
+ Generates the initial configuration (Docker and Spring properties) for a new project.
15
+ """
16
+ template_dir = os.path.join(os.path.dirname(__file__), "..", "templates", "spring")
17
+ env = Environment(loader=FileSystemLoader(template_dir))
18
+
19
+ # Intelligent default port resolution
20
+ if custom_port is None:
21
+ if db_type == "postgres":
22
+ db_port = 5432
23
+ elif db_type == "mysql":
24
+ db_port = 3306
25
+ elif db_type == "mongodb":
26
+ db_port = 27017
27
+ else:
28
+ db_port = 5432
29
+ else:
30
+ db_port = custom_port
31
+
32
+ context = {
33
+ "project_name": project_name,
34
+ "db_type": db_type,
35
+ "db_service_name": f"{project_name}-db",
36
+ "db_name": f"{project_name}_db".replace("-", "_"),
37
+ "db_user": "admin",
38
+ "db_password": "password",
39
+ "db_port": db_port,
40
+ }
41
+
42
+ project_path = os.path.join(os.getcwd(), project_name)
43
+
44
+ try:
45
+ docker_template = env.get_template("docker-compose.yml.j2")
46
+ with open(os.path.join(project_path, "docker-compose-db.yml"), "w") as f:
47
+ f.write(docker_template.render(context))
48
+
49
+ props_path = os.path.join(
50
+ project_path, "src", "main", "resources", "application.properties"
51
+ )
52
+ if os.path.exists(props_path):
53
+ props_template = env.get_template("application.properties.j2")
54
+ with open(props_path, "w") as f:
55
+ f.write(props_template.render(context))
56
+
57
+ typer.secho(
58
+ f"Dynamic configuration ({db_type} on port {db_port}) generated.",
59
+ fg=typer.colors.GREEN,
60
+ )
61
+ return True
62
+ except Exception as e:
63
+ typer.secho(f"Error: Configuration failed: {e}", fg=typer.colors.RED)
64
+ return False