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,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
|
devctl/generators/vue.py
ADDED
|
@@ -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,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
|