cicd-cli-tool 0.1.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.
- cicd_cli_tool-0.1.0.dist-info/METADATA +41 -0
- cicd_cli_tool-0.1.0.dist-info/RECORD +49 -0
- cicd_cli_tool-0.1.0.dist-info/WHEEL +5 -0
- cicd_cli_tool-0.1.0.dist-info/entry_points.txt +2 -0
- cicd_cli_tool-0.1.0.dist-info/top_level.txt +3 -0
- cli/main.py +384 -0
- core/__init__.py +3 -0
- core/base_detector.py +113 -0
- core/base_generator.py +190 -0
- frameworks/__init__.py +39 -0
- frameworks/dotnet/__init__.py +7 -0
- frameworks/dotnet/detector.py +146 -0
- frameworks/dotnet/generator.py +157 -0
- frameworks/dotnet/templates/Dockerfile.j2 +29 -0
- frameworks/dotnet/templates/Jenkinsfile.j2 +8 -0
- frameworks/dotnet/templates/github-actions.yml.j2 +77 -0
- frameworks/dotnet/templates/gitlab-ci.yml.j2 +68 -0
- frameworks/gradle/__init__.py +7 -0
- frameworks/gradle/detector.py +115 -0
- frameworks/gradle/generator.py +149 -0
- frameworks/gradle/templates/Dockerfile.j2 +31 -0
- frameworks/gradle/templates/Jenkinsfile.j2 +8 -0
- frameworks/gradle/templates/github-actions.yml.j2 +79 -0
- frameworks/gradle/templates/gitlab-ci.yml.j2 +63 -0
- frameworks/java/__init__.py +7 -0
- frameworks/java/detector.py +29 -0
- frameworks/java/generator.py +29 -0
- frameworks/maven/__init__.py +7 -0
- frameworks/maven/detector.py +104 -0
- frameworks/maven/generator.py +155 -0
- frameworks/maven/templates/Dockerfile.j2 +35 -0
- frameworks/maven/templates/Jenkinsfile.j2 +8 -0
- frameworks/maven/templates/github-actions.yml.j2 +28 -0
- frameworks/maven/templates/gitlab-ci.yml.j2 +73 -0
- frameworks/node/__init__.py +3 -0
- frameworks/node/detector.py +110 -0
- frameworks/node/generator.py +149 -0
- frameworks/node/templates/Dockerfile.j2 +31 -0
- frameworks/node/templates/Jenkinsfile.j2 +8 -0
- frameworks/node/templates/github-actions.yml.j2 +191 -0
- frameworks/node/templates/gitlab-ci.yml.j2 +74 -0
- frameworks/python/__init__.py +5 -0
- frameworks/python/detector.py +189 -0
- frameworks/python/generator.py +254 -0
- frameworks/python/templates/Dockerfile.j2 +34 -0
- frameworks/python/templates/Jenkinsfile.j2 +8 -0
- frameworks/python/templates/README.md.j2 +195 -0
- frameworks/python/templates/github-actions.yml.j2 +91 -0
- frameworks/python/templates/gitlab-ci.yml.j2 +64 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cicd-cli-tool
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Auto-detect project frameworks and generate CI/CD pipeline files
|
|
5
|
+
Author: Your Name
|
|
6
|
+
Author-email: Ayoub Jedidi <ayoub.ejedidi@gmail.com>
|
|
7
|
+
Keywords: ci,cd,jenkins,gitlab,github,pipeline,devops,cicd
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: typer[all]>=0.9.0
|
|
20
|
+
Requires-Dist: jinja2>=3.1.2
|
|
21
|
+
Requires-Dist: rich>=13.7.0
|
|
22
|
+
Requires-Dist: pydantic<2
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
26
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
27
|
+
Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
28
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
29
|
+
Dynamic: author
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
|
|
32
|
+
# CLI Client
|
|
33
|
+
this cli provides every setup you need for these frameworks (nodejs,pytohn,java,gradle,maven,dotnet)
|
|
34
|
+
after installing the cli with pip install -e
|
|
35
|
+
you'll use these commands :
|
|
36
|
+
cicd--help : to see all commands you can use with cicd-cli
|
|
37
|
+
cicd detect : detects everything is related to your project dependencies , version , framewokr, language etc ...
|
|
38
|
+
you should provide the folder your project to work
|
|
39
|
+
cicd init : includes cicd detect , it will detect auto and will generate jenkinsfile and dockerfile for your project
|
|
40
|
+
this all you need to know about the cli
|
|
41
|
+
if you need more example you find the tests folder ive used to test , simple example running
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
cli/main.py,sha256=PyX1gxNic6LZ96yx1rTHFG9rub8i5s3Y3oPH8REtCAU,13870
|
|
2
|
+
core/__init__.py,sha256=4k9y0t_moGgaw2bzmbC-maaPJ5tM2mFlTnJj_NW7Cs4,126
|
|
3
|
+
core/base_detector.py,sha256=yIYgr1WRJNNZ-Fef5YlB7KDyNM3bpp9PuK5F3x2wS9E,3608
|
|
4
|
+
core/base_generator.py,sha256=U20eIAd9DSOrBltHSrHYVW-4nh77Z16BfwnAfXDYAEM,6445
|
|
5
|
+
frameworks/__init__.py,sha256=D3rVWmuKN7te5qn41dcMD6GhdLEQv0OdlVx28K4izds,1051
|
|
6
|
+
frameworks/dotnet/__init__.py,sha256=Nj8Fh8Fd_sOXrCl2rU0JW-Y_AIIeFyGIkGq70hdnzck,170
|
|
7
|
+
frameworks/dotnet/detector.py,sha256=uuwpcbNQqz7YOJ7QhVvGE4bptcOWSCXXnbPT9boSFuU,4874
|
|
8
|
+
frameworks/dotnet/generator.py,sha256=ScrCMg-aER9EtJb_32q383Xs7SiiinBSjUUGRoZw9vg,5754
|
|
9
|
+
frameworks/dotnet/templates/Dockerfile.j2,sha256=YK7IaZfsAaB7mfNt1G2979upEaHEw7TXpD5OwqXT9as,542
|
|
10
|
+
frameworks/dotnet/templates/Jenkinsfile.j2,sha256=j4ac3a5OkISiBLlruPy9F13_QMe310Tb4c1pk2iXIWU,163
|
|
11
|
+
frameworks/dotnet/templates/github-actions.yml.j2,sha256=s8ytAfZK6Y6zb6K1d_wf6JcxBQaiVRtgCRvSUSXHj7c,1968
|
|
12
|
+
frameworks/dotnet/templates/gitlab-ci.yml.j2,sha256=e2qcByOH6pPzQ29I8HoPAEu5mh1b7iBmFuo1-zXVto4,1347
|
|
13
|
+
frameworks/gradle/__init__.py,sha256=uHycl82gkj4zjy_fFZZw4MEhett7PpJli1NoMxqNX30,172
|
|
14
|
+
frameworks/gradle/detector.py,sha256=OI7yi_BOi3EDlC9m1SGycJjodeFj9M90EUrIpblcb1k,3969
|
|
15
|
+
frameworks/gradle/generator.py,sha256=1c7nRj_H7aAtcp8ZrZ4wXJDavDa4WMcXrGlUjIjXYfA,5388
|
|
16
|
+
frameworks/gradle/templates/Dockerfile.j2,sha256=qS4VZv98OyzALMDTgfCO_eshgM8AHIQqMwCworzzAdk,634
|
|
17
|
+
frameworks/gradle/templates/Jenkinsfile.j2,sha256=Rr7AcYjf2l33SZVaDWW4D6YSbtqSViweIYhxtXG4f44,163
|
|
18
|
+
frameworks/gradle/templates/github-actions.yml.j2,sha256=Vgfi8-REzplZv9DM1K34x7bEIp3STj3JenaEo-4uFZM,1966
|
|
19
|
+
frameworks/gradle/templates/gitlab-ci.yml.j2,sha256=Fy2Z_zmkjptbZ89AH14LluEyRTsP60Bv3iLSPH2u9rw,1055
|
|
20
|
+
frameworks/java/__init__.py,sha256=MhBeJxNdQyZKeDp5Lz4LEgCtSoCeO8zLLU4j-VXQkxk,158
|
|
21
|
+
frameworks/java/detector.py,sha256=BD_Whan0twNB-kv3pnDqcPDwkUi39QGf55hjsN6t9oI,923
|
|
22
|
+
frameworks/java/generator.py,sha256=sASHMg1WxTAszkTJroQv5mMBpJ9wNXEkTL9qfRqVBt0,929
|
|
23
|
+
frameworks/maven/__init__.py,sha256=BflBSotd-iLNN1MDIqmQodZroAprzNnFF5PQXGHkrZ0,167
|
|
24
|
+
frameworks/maven/detector.py,sha256=vFqWLhLFTxzMsj6i4cV6DT4aanpoa-MwuIAdfW-2K6U,3397
|
|
25
|
+
frameworks/maven/generator.py,sha256=Pt8TDmioX_xd9--vFGxsCeirZ7mk0gvb-fpFIkN88kY,5617
|
|
26
|
+
frameworks/maven/templates/Dockerfile.j2,sha256=0rSTYspIUZnSBPF0a01SwW2UcNgYbvicUVJ_hltH3-4,836
|
|
27
|
+
frameworks/maven/templates/Jenkinsfile.j2,sha256=cXiCJmf_tSlbvfwJtAXj7JalHVAMdFWATTaquLLIV4o,162
|
|
28
|
+
frameworks/maven/templates/github-actions.yml.j2,sha256=0bvN6ROsrRTByBAjcHPoBpXlSMdDgwh6kRMTQp6sjlI,564
|
|
29
|
+
frameworks/maven/templates/gitlab-ci.yml.j2,sha256=1Dd8APErz-R-7k4Za8fPMtMsAvZy66fn5UQswAY9Cpg,1161
|
|
30
|
+
frameworks/node/__init__.py,sha256=88s2aFa7FGELa2j7tlkmUWhkLfu8IrPjk0bwWyFDMTU,116
|
|
31
|
+
frameworks/node/detector.py,sha256=GhxfkRuSWaznPnVbJNkPvrOQDwo44qv2yaElzWFEqi8,3258
|
|
32
|
+
frameworks/node/generator.py,sha256=b-_wQL13hjhJKxwSkbtL1VNN3wloerP-rkZRViRVbfA,5393
|
|
33
|
+
frameworks/node/templates/Dockerfile.j2,sha256=91z9TxWQOey0-XAbZW4cnx0Yl0rcSIdNrZVmLSLbjAM,636
|
|
34
|
+
frameworks/node/templates/Jenkinsfile.j2,sha256=G6UG9KS6JIbExmsCT-HlZAkD6fGfZZE0YTff41_lOmA,160
|
|
35
|
+
frameworks/node/templates/github-actions.yml.j2,sha256=TznC_ki8RYwudbRdB0LMB7OvuFBDXKRXszGg9v4jQic,6619
|
|
36
|
+
frameworks/node/templates/gitlab-ci.yml.j2,sha256=rApavtOLnA5LdvnhQ4EW8kcqVYfS3Tudzacw6h3nl58,1334
|
|
37
|
+
frameworks/python/__init__.py,sha256=Kl3eOjpv_tyNbBN1E9IABQFN0hjUOKspwTrdYxTnCkw,147
|
|
38
|
+
frameworks/python/detector.py,sha256=67NL1VtGFqQ1wOdF0BcOdtlxzP3hU_Q0r5HyY005z-c,6187
|
|
39
|
+
frameworks/python/generator.py,sha256=EgBcaOTCQxTMd7uXq7d0P1qsK3x8wOjc73jKM5-ORw4,9512
|
|
40
|
+
frameworks/python/templates/Dockerfile.j2,sha256=fppi68xMCE9cBcHiaCrmX0mRPe1w_m7LRk-PSCbCwt0,817
|
|
41
|
+
frameworks/python/templates/Jenkinsfile.j2,sha256=bfoTBiHVXLyGVCG5Ias-EwzJtC1gG3nrtreecQGz7Ak,163
|
|
42
|
+
frameworks/python/templates/README.md.j2,sha256=ScPwDQYq2XkrHmvhaU27bvK3XJbi7sNgqBBc1tkkCfY,5493
|
|
43
|
+
frameworks/python/templates/github-actions.yml.j2,sha256=tOFL7VTi90r-hDdm8cjYCkMFyekBsGyKjfBiOr7g4eE,2339
|
|
44
|
+
frameworks/python/templates/gitlab-ci.yml.j2,sha256=5gvlepYPUuK6ZAALZ96OsRBCHzWA48WF0CXNx8uL4r0,1343
|
|
45
|
+
cicd_cli_tool-0.1.0.dist-info/METADATA,sha256=XvcexTUZsnXJuXj-Ujqxq7KeBTD9ethGYL7DfCmUGT4,1838
|
|
46
|
+
cicd_cli_tool-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
47
|
+
cicd_cli_tool-0.1.0.dist-info/entry_points.txt,sha256=WuJa44_vrSvNg8LaRDOBEeB2Q5g1_VSozoVZfMb0Wac,39
|
|
48
|
+
cicd_cli_tool-0.1.0.dist-info/top_level.txt,sha256=iTFuqReac6OeWK02FqIhPrTzd4t9xBxk270SYfFrp4g,20
|
|
49
|
+
cicd_cli_tool-0.1.0.dist-info/RECORD,,
|
cli/main.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI for cicd-framework tool
|
|
3
|
+
Auto-detects project framework and generates CI/CD files
|
|
4
|
+
"""
|
|
5
|
+
from typing import Optional, List
|
|
6
|
+
import typer
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
from frameworks import AVAILABLE_FRAMEWORKS
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(
|
|
13
|
+
help="CI/CD Framework Generator - Auto-detect and generate pipeline files",
|
|
14
|
+
add_completion=False
|
|
15
|
+
)
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.command()
|
|
20
|
+
def init(
|
|
21
|
+
path: Path = typer.Argument(
|
|
22
|
+
Path("."),
|
|
23
|
+
help="Project path to analyze",
|
|
24
|
+
exists=True,
|
|
25
|
+
file_okay=False,
|
|
26
|
+
dir_okay=True,
|
|
27
|
+
resolve_path=True
|
|
28
|
+
),
|
|
29
|
+
framework: Optional[str] = typer.Option(
|
|
30
|
+
None,
|
|
31
|
+
"--framework",
|
|
32
|
+
"-f",
|
|
33
|
+
help="Force specific framework (python, node, java, etc.)"
|
|
34
|
+
),
|
|
35
|
+
platforms: str = typer.Option(
|
|
36
|
+
"jenkins",
|
|
37
|
+
"--platforms",
|
|
38
|
+
"-p",
|
|
39
|
+
help="CI/CD platforms to generate (comma-separated). Options: jenkins | gitlab | github"
|
|
40
|
+
),
|
|
41
|
+
deployment_type: str = typer.Option(
|
|
42
|
+
"webapp",
|
|
43
|
+
"--deployment-type",
|
|
44
|
+
"-dt",
|
|
45
|
+
help="Type of deployment. Options: webapp | instance"
|
|
46
|
+
),
|
|
47
|
+
cloud_provider: str = typer.Option(
|
|
48
|
+
"local",
|
|
49
|
+
"--cloud-provider",
|
|
50
|
+
"-cp",
|
|
51
|
+
help="Cloud provider for deployment. Options: local | aws | azure | gcp"
|
|
52
|
+
),
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
Initialize CI/CD pipeline for a project.
|
|
56
|
+
|
|
57
|
+
Auto-detects project framework and generates pipeline files.
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
|
|
61
|
+
# Generate Jenkins pipeline (default)
|
|
62
|
+
$ cicd-framework init
|
|
63
|
+
|
|
64
|
+
# Generate GitLab CI only
|
|
65
|
+
$ cicd-framework init --platforms gitlab
|
|
66
|
+
|
|
67
|
+
# Generate all platforms
|
|
68
|
+
$ cicd-framework init --platforms jenkins,gitlab,github
|
|
69
|
+
|
|
70
|
+
# Force Python framework with multiple platforms
|
|
71
|
+
$ cicd-framework init --framework python --platforms gitlab,github
|
|
72
|
+
|
|
73
|
+
# Deploy to AWS as a webapp
|
|
74
|
+
$ cicd-framework init --cloud-provider aws --deployment-type webapp
|
|
75
|
+
|
|
76
|
+
# Deploy to Azure as a VM instance
|
|
77
|
+
$ cicd-framework init --cloud-provider azure --deployment-type instance
|
|
78
|
+
"""
|
|
79
|
+
console.print("🔍 Detecting project framework...\n")
|
|
80
|
+
|
|
81
|
+
project_path = Path(path).resolve()
|
|
82
|
+
|
|
83
|
+
# Parse and validate platforms
|
|
84
|
+
requested_platforms = [p.strip().lower() for p in platforms.split(',')]
|
|
85
|
+
valid_platforms = ['jenkins', 'gitlab', 'github']
|
|
86
|
+
invalid = [p for p in requested_platforms if p not in valid_platforms]
|
|
87
|
+
|
|
88
|
+
if invalid:
|
|
89
|
+
console.print(f"[red]✗ Invalid platforms: {', '.join(invalid)}[/red]")
|
|
90
|
+
console.print(f"[yellow]Valid platforms: {', '.join(valid_platforms)}[/yellow]")
|
|
91
|
+
console.print("\n[dim]Tip: Use comma-separated values like --platforms jenkins,gitlab,github[/dim]")
|
|
92
|
+
raise typer.Exit(code=1)
|
|
93
|
+
|
|
94
|
+
# Validate deployment type
|
|
95
|
+
deployment_type = deployment_type.lower()
|
|
96
|
+
valid_deployment_types = ['webapp', 'instance']
|
|
97
|
+
if deployment_type not in valid_deployment_types:
|
|
98
|
+
console.print(f"[red]✗ Invalid deployment type: {deployment_type}[/red]")
|
|
99
|
+
console.print(f"[yellow]Valid deployment types: {', '.join(valid_deployment_types)}[/yellow]")
|
|
100
|
+
raise typer.Exit(code=1)
|
|
101
|
+
|
|
102
|
+
# Validate cloud provider
|
|
103
|
+
cloud_provider = cloud_provider.lower()
|
|
104
|
+
valid_cloud_providers = ['local', 'aws', 'azure', 'gcp']
|
|
105
|
+
if cloud_provider not in valid_cloud_providers:
|
|
106
|
+
console.print(f"[red]✗ Invalid cloud provider: {cloud_provider}[/red]")
|
|
107
|
+
console.print(f"[yellow]Valid cloud providers: {', '.join(valid_cloud_providers)}[/yellow]")
|
|
108
|
+
raise typer.Exit(code=1)
|
|
109
|
+
|
|
110
|
+
# If user specified framework, use only that
|
|
111
|
+
if framework:
|
|
112
|
+
if framework not in AVAILABLE_FRAMEWORKS:
|
|
113
|
+
console.print(f"[red]✗ Unknown framework: {framework}[/red]")
|
|
114
|
+
console.print(f"[yellow]Available frameworks: {', '.join(AVAILABLE_FRAMEWORKS.keys())}[/yellow]")
|
|
115
|
+
raise typer.Exit(code=1)
|
|
116
|
+
|
|
117
|
+
frameworks_to_try = [framework]
|
|
118
|
+
else:
|
|
119
|
+
# Try all available frameworks
|
|
120
|
+
frameworks_to_try = list(AVAILABLE_FRAMEWORKS.keys())
|
|
121
|
+
|
|
122
|
+
# Try each framework detector
|
|
123
|
+
detection_result = None
|
|
124
|
+
detected_framework = None
|
|
125
|
+
|
|
126
|
+
for fw_name in frameworks_to_try:
|
|
127
|
+
fw_config = AVAILABLE_FRAMEWORKS[fw_name]
|
|
128
|
+
DetectorClass = fw_config['detector']
|
|
129
|
+
|
|
130
|
+
console.print(f" Trying {fw_name}...", style="dim")
|
|
131
|
+
|
|
132
|
+
# Initialize detector and try detection
|
|
133
|
+
detector = DetectorClass(project_path)
|
|
134
|
+
result = detector.detect()
|
|
135
|
+
|
|
136
|
+
if result is not None:
|
|
137
|
+
detection_result = result
|
|
138
|
+
detected_framework = fw_name
|
|
139
|
+
console.print(f"[green]✓ Detected: {fw_name}[/green]")
|
|
140
|
+
break
|
|
141
|
+
|
|
142
|
+
# Check if detection failed
|
|
143
|
+
if detection_result is None:
|
|
144
|
+
console.print("[red]✗ No supported framework detected![/red]")
|
|
145
|
+
console.print(f"[yellow]Supported frameworks: {', '.join(AVAILABLE_FRAMEWORKS.keys())}[/yellow]")
|
|
146
|
+
console.print("\n[dim]Tip: Use --framework to force a specific framework[/dim]")
|
|
147
|
+
raise typer.Exit(code=1)
|
|
148
|
+
|
|
149
|
+
# Add project path to result
|
|
150
|
+
detection_result['project_path'] = str(project_path)
|
|
151
|
+
|
|
152
|
+
# Add deployment configuration
|
|
153
|
+
detection_result['deployment_type'] = deployment_type
|
|
154
|
+
detection_result['cloud_provider'] = cloud_provider
|
|
155
|
+
|
|
156
|
+
# Display detection results
|
|
157
|
+
console.print()
|
|
158
|
+
table = Table(title="📊 Detection Results", show_header=True, header_style="bold cyan")
|
|
159
|
+
table.add_column("Property", style="cyan", no_wrap=True)
|
|
160
|
+
table.add_column("Value", style="green")
|
|
161
|
+
|
|
162
|
+
table.add_row("Framework", detected_framework)
|
|
163
|
+
table.add_row("Language", detection_result.get("language", "N/A"))
|
|
164
|
+
table.add_row("Cloud Provider", cloud_provider.upper() if cloud_provider != "local" else "Local Docker")
|
|
165
|
+
table.add_row("Deployment Type", deployment_type.capitalize())
|
|
166
|
+
|
|
167
|
+
# Add framework-specific details
|
|
168
|
+
if detected_framework == 'python':
|
|
169
|
+
table.add_row("Python Version", detection_result.get("python_version", "N/A"))
|
|
170
|
+
table.add_row("Package Manager", detection_result.get("package_manager", "N/A"))
|
|
171
|
+
table.add_row("Test Framework", detection_result.get("test_framework", "N/A"))
|
|
172
|
+
if detection_result.get("framework"):
|
|
173
|
+
table.add_row("Web Framework", detection_result.get("framework"))
|
|
174
|
+
elif detected_framework == 'node':
|
|
175
|
+
table.add_row("Node Version", detection_result.get("node_version", "N/A"))
|
|
176
|
+
table.add_row("Package Manager", detection_result.get("package_manager", "N/A"))
|
|
177
|
+
if detection_result.get("framework"):
|
|
178
|
+
table.add_row("Framework", detection_result.get("framework"))
|
|
179
|
+
|
|
180
|
+
console.print(table)
|
|
181
|
+
console.print()
|
|
182
|
+
|
|
183
|
+
# Display platforms to generate
|
|
184
|
+
platforms_display = ", ".join([f"[cyan]{p}[/cyan]" for p in requested_platforms])
|
|
185
|
+
console.print(f"[bold]📦 Generating CI/CD files for: {platforms_display}[/bold]\n")
|
|
186
|
+
|
|
187
|
+
# Get the appropriate generator
|
|
188
|
+
GeneratorClass = AVAILABLE_FRAMEWORKS[detected_framework]['generator']
|
|
189
|
+
generator = GeneratorClass()
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
files = generator.generate(detection_result, project_path, requested_platforms)
|
|
193
|
+
|
|
194
|
+
console.print("[green]✅ Generation successful![/green]\n")
|
|
195
|
+
|
|
196
|
+
# Display generated files
|
|
197
|
+
files_table = Table(title="📄 Generated Files", show_header=True, header_style="bold green")
|
|
198
|
+
files_table.add_column("Platform", style="cyan", no_wrap=True)
|
|
199
|
+
files_table.add_column("File Path", style="yellow")
|
|
200
|
+
|
|
201
|
+
for platform, file_path in files['generated_files'].items():
|
|
202
|
+
# Make path relative to project for cleaner display
|
|
203
|
+
try:
|
|
204
|
+
rel_path = Path(file_path).relative_to(project_path)
|
|
205
|
+
files_table.add_row(platform.upper(), str(rel_path))
|
|
206
|
+
except ValueError:
|
|
207
|
+
files_table.add_row(platform.upper(), str(file_path))
|
|
208
|
+
|
|
209
|
+
console.print(files_table)
|
|
210
|
+
console.print()
|
|
211
|
+
|
|
212
|
+
return files
|
|
213
|
+
except Exception as e:
|
|
214
|
+
console.print(f"[red]✗ Generation failed: {e}[/red]")
|
|
215
|
+
import traceback
|
|
216
|
+
console.print("\n[dim]Stack trace:[/dim]")
|
|
217
|
+
traceback.print_exc()
|
|
218
|
+
raise typer.Exit(code=1)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@app.command()
|
|
222
|
+
def detect(
|
|
223
|
+
path: Path = typer.Argument(
|
|
224
|
+
Path("."),
|
|
225
|
+
help="Project path to analyze",
|
|
226
|
+
exists=True,
|
|
227
|
+
file_okay=False,
|
|
228
|
+
dir_okay=True,
|
|
229
|
+
resolve_path=True
|
|
230
|
+
),
|
|
231
|
+
framework: Optional[str] = typer.Option(
|
|
232
|
+
None,
|
|
233
|
+
"--framework",
|
|
234
|
+
"-f",
|
|
235
|
+
help="Force specific framework detection"
|
|
236
|
+
),
|
|
237
|
+
):
|
|
238
|
+
"""
|
|
239
|
+
Detect project configuration without generating files.
|
|
240
|
+
|
|
241
|
+
Useful for testing framework detection.
|
|
242
|
+
|
|
243
|
+
Examples:
|
|
244
|
+
|
|
245
|
+
# Auto-detect framework
|
|
246
|
+
$ cicd-framework detect
|
|
247
|
+
|
|
248
|
+
# Force Python detection
|
|
249
|
+
$ cicd-framework detect --framework python
|
|
250
|
+
"""
|
|
251
|
+
project_path = Path(path).resolve()
|
|
252
|
+
|
|
253
|
+
console.print("🔍 Detecting project framework...\n")
|
|
254
|
+
|
|
255
|
+
# If user specified framework, use only that
|
|
256
|
+
if framework:
|
|
257
|
+
if framework not in AVAILABLE_FRAMEWORKS:
|
|
258
|
+
console.print(f"[red]✗ Unknown framework: {framework}[/red]")
|
|
259
|
+
console.print(f"[yellow]Available frameworks: {', '.join(AVAILABLE_FRAMEWORKS.keys())}[/yellow]")
|
|
260
|
+
raise typer.Exit(code=1)
|
|
261
|
+
frameworks_to_try = [framework]
|
|
262
|
+
else:
|
|
263
|
+
frameworks_to_try = list(AVAILABLE_FRAMEWORKS.keys())
|
|
264
|
+
|
|
265
|
+
# Try each framework
|
|
266
|
+
for fw_name in frameworks_to_try:
|
|
267
|
+
fw_config = AVAILABLE_FRAMEWORKS[fw_name]
|
|
268
|
+
DetectorClass = fw_config['detector']
|
|
269
|
+
|
|
270
|
+
console.print(f" Trying {fw_name}...", style="dim")
|
|
271
|
+
detector = DetectorClass(project_path)
|
|
272
|
+
result = detector.detect()
|
|
273
|
+
|
|
274
|
+
if result is not None:
|
|
275
|
+
result['project_path'] = str(project_path)
|
|
276
|
+
result['detected_framework'] = fw_name
|
|
277
|
+
console.print(f"\n[green]✓ Detected: {fw_name}[/green]\n")
|
|
278
|
+
console.print_json(data=result)
|
|
279
|
+
return result
|
|
280
|
+
|
|
281
|
+
console.print("[red]✗ No supported framework detected![/red]")
|
|
282
|
+
console.print(f"[yellow]Supported frameworks: {', '.join(AVAILABLE_FRAMEWORKS.keys())}[/yellow]")
|
|
283
|
+
raise typer.Exit(code=1)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@app.command(name="list")
|
|
287
|
+
def list_frameworks():
|
|
288
|
+
"""
|
|
289
|
+
List all supported frameworks and platforms.
|
|
290
|
+
|
|
291
|
+
Shows available framework detectors and generators.
|
|
292
|
+
"""
|
|
293
|
+
console.print("\n[bold cyan]🚀 CI/CD Framework Generator[/bold cyan]\n")
|
|
294
|
+
|
|
295
|
+
# Frameworks table
|
|
296
|
+
frameworks_table = Table(title="Supported Frameworks", show_header=True, header_style="bold cyan")
|
|
297
|
+
frameworks_table.add_column("Framework", style="cyan", no_wrap=True)
|
|
298
|
+
frameworks_table.add_column("Detector", style="yellow")
|
|
299
|
+
frameworks_table.add_column("Generator", style="green")
|
|
300
|
+
|
|
301
|
+
for fw_name, fw_config in AVAILABLE_FRAMEWORKS.items():
|
|
302
|
+
frameworks_table.add_row(
|
|
303
|
+
fw_name.upper(),
|
|
304
|
+
fw_config['detector'].__name__,
|
|
305
|
+
fw_config['generator'].__name__
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
console.print(frameworks_table)
|
|
309
|
+
console.print()
|
|
310
|
+
|
|
311
|
+
# Platforms table
|
|
312
|
+
platforms_table = Table(title="Supported CI/CD Platforms", show_header=True, header_style="bold green")
|
|
313
|
+
platforms_table.add_column("Platform", style="green", no_wrap=True)
|
|
314
|
+
platforms_table.add_column("File Generated", style="yellow")
|
|
315
|
+
platforms_table.add_column("Description", style="dim")
|
|
316
|
+
|
|
317
|
+
platforms_table.add_row("Jenkins", "Jenkinsfile", "Declarative pipeline for Jenkins")
|
|
318
|
+
platforms_table.add_row("GitLab CI", ".gitlab-ci.yml", "GitLab CI/CD configuration")
|
|
319
|
+
platforms_table.add_row("GitHub Actions", ".github/workflows/ci.yml", "GitHub Actions workflow")
|
|
320
|
+
|
|
321
|
+
console.print(platforms_table)
|
|
322
|
+
console.print()
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@app.command()
|
|
326
|
+
def version():
|
|
327
|
+
"""
|
|
328
|
+
Show version information.
|
|
329
|
+
"""
|
|
330
|
+
console.print("\n[bold cyan]CI/CD Framework Generator[/bold cyan]")
|
|
331
|
+
console.print("[dim]Version:[/dim] [green]1.0.0[/green]")
|
|
332
|
+
console.print("[dim]Author:[/dim] [yellow]Ayoub Jedidi[/yellow]")
|
|
333
|
+
console.print()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@app.command()
|
|
337
|
+
def options():
|
|
338
|
+
"""
|
|
339
|
+
Show all available deployment options and their values.
|
|
340
|
+
"""
|
|
341
|
+
console.print("\n[bold cyan]🚀 Available Deployment Options[/bold cyan]\n")
|
|
342
|
+
|
|
343
|
+
# Cloud Providers table
|
|
344
|
+
providers_table = Table(title="Cloud Providers", show_header=True, header_style="bold cyan")
|
|
345
|
+
providers_table.add_column("Value", style="green", no_wrap=True)
|
|
346
|
+
providers_table.add_column("Description", style="yellow")
|
|
347
|
+
|
|
348
|
+
providers_table.add_row("local", "Local Docker Registry (default)")
|
|
349
|
+
providers_table.add_row("aws", "Amazon Web Services")
|
|
350
|
+
providers_table.add_row("azure", "Microsoft Azure")
|
|
351
|
+
providers_table.add_row("gcp", "Google Cloud Platform")
|
|
352
|
+
|
|
353
|
+
console.print(providers_table)
|
|
354
|
+
console.print()
|
|
355
|
+
|
|
356
|
+
# Deployment Types table
|
|
357
|
+
types_table = Table(title="Deployment Types", show_header=True, header_style="bold green")
|
|
358
|
+
types_table.add_column("Value", style="green", no_wrap=True)
|
|
359
|
+
types_table.add_column("Description", style="yellow")
|
|
360
|
+
|
|
361
|
+
types_table.add_row("webapp", "Web Application/Service (default)")
|
|
362
|
+
types_table.add_row("instance", "VM/Container Instance")
|
|
363
|
+
|
|
364
|
+
console.print(types_table)
|
|
365
|
+
console.print()
|
|
366
|
+
|
|
367
|
+
# Usage examples
|
|
368
|
+
console.print("[bold]Usage Examples:[/bold]\n")
|
|
369
|
+
console.print(" [dim]#[/dim] Deploy to AWS as a web application:")
|
|
370
|
+
console.print(" [cyan]cicd init --cloud-provider aws --deployment-type webapp[/cyan]\n")
|
|
371
|
+
console.print(" [dim]#[/dim] Deploy to Azure as a VM instance:")
|
|
372
|
+
console.print(" [cyan]cicd init --cloud-provider azure --deployment-type instance[/cyan]\n")
|
|
373
|
+
console.print(" [dim]#[/dim] Local Docker deployment:")
|
|
374
|
+
console.print(" [cyan]cicd init --cloud-provider local[/cyan]\n")
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def main():
|
|
378
|
+
"""Entry point for the CLI"""
|
|
379
|
+
app()
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
if __name__ == "__main__":
|
|
383
|
+
main()
|
|
384
|
+
|
core/__init__.py
ADDED
core/base_detector.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Detector class for all framework detectors
|
|
3
|
+
Provides common utilities for file checking, reading, and dependency parsing
|
|
4
|
+
"""
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional, Set, Dict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseDetector:
|
|
11
|
+
"""Base class for framework-specific detectors"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, project_path: Path):
|
|
14
|
+
"""
|
|
15
|
+
Initialize detector with project path
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
project_path: Path to the project to detect
|
|
19
|
+
"""
|
|
20
|
+
self.project_path = Path(project_path)
|
|
21
|
+
|
|
22
|
+
def detect(self) -> Optional[Dict]:
|
|
23
|
+
"""
|
|
24
|
+
Main detection method - must be overridden by child classes
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Dict with detection results or None if not detected
|
|
28
|
+
"""
|
|
29
|
+
raise NotImplementedError("Subclasses must implement detect()")
|
|
30
|
+
|
|
31
|
+
# ============ Shared File Utilities ============
|
|
32
|
+
|
|
33
|
+
def file_exists(self, filename: str) -> bool:
|
|
34
|
+
"""Check if a file exists in project"""
|
|
35
|
+
return (self.project_path / filename).exists()
|
|
36
|
+
|
|
37
|
+
def read_file(self, filename: str) -> str:
|
|
38
|
+
"""Read file content safely"""
|
|
39
|
+
try:
|
|
40
|
+
file_path = self.project_path / filename
|
|
41
|
+
if file_path.exists():
|
|
42
|
+
return file_path.read_text()
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
return ""
|
|
46
|
+
|
|
47
|
+
def has_files_with_extension(self, extension: str) -> bool:
|
|
48
|
+
"""Check if project has files with given extension"""
|
|
49
|
+
pattern = f"*.{extension}" if not extension.startswith('*') else extension
|
|
50
|
+
return bool(list(self.project_path.rglob(pattern)))
|
|
51
|
+
|
|
52
|
+
# ============ Shared Version Detection ============
|
|
53
|
+
|
|
54
|
+
def normalize_version(self, version: str) -> str:
|
|
55
|
+
"""
|
|
56
|
+
Normalize version to X.Y format
|
|
57
|
+
Example: 3.11.2 -> 3.11
|
|
58
|
+
"""
|
|
59
|
+
parts = version.split('.')
|
|
60
|
+
if len(parts) >= 2:
|
|
61
|
+
return f"{parts[0]}.{parts[1]}"
|
|
62
|
+
return version
|
|
63
|
+
|
|
64
|
+
def extract_version_from_content(self, content: str, pattern: str) -> Optional[str]:
|
|
65
|
+
"""
|
|
66
|
+
Extract version using regex pattern
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
content: File content to search
|
|
70
|
+
pattern: Regex pattern with one capture group for version
|
|
71
|
+
"""
|
|
72
|
+
match = re.search(pattern, content)
|
|
73
|
+
if match:
|
|
74
|
+
return self.normalize_version(match.group(1))
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
# ============ Shared Dependency Parsing ============
|
|
78
|
+
|
|
79
|
+
def parse_dependencies_from_file(self, filename: str) -> Set[str]:
|
|
80
|
+
"""
|
|
81
|
+
Parse dependencies from a requirements-style file
|
|
82
|
+
Returns set of lowercase package names
|
|
83
|
+
"""
|
|
84
|
+
deps = set()
|
|
85
|
+
content = self.read_file(filename)
|
|
86
|
+
|
|
87
|
+
for line in content.split('\n'):
|
|
88
|
+
line = line.strip().lower()
|
|
89
|
+
# Skip comments and empty lines
|
|
90
|
+
if not line or line.startswith('#') or line.startswith('-r'):
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
# Extract package name (before ==, >=, etc.)
|
|
94
|
+
pkg = re.split(r'[=<>~!]', line)[0].strip()
|
|
95
|
+
if pkg:
|
|
96
|
+
deps.add(pkg)
|
|
97
|
+
|
|
98
|
+
return deps
|
|
99
|
+
|
|
100
|
+
def check_dependency_in_files(self, dependency: str, *filenames) -> bool:
|
|
101
|
+
"""
|
|
102
|
+
Check if a dependency exists in any of the given files
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
dependency: Package name to look for (case-insensitive)
|
|
106
|
+
*filenames: Variable number of filenames to check
|
|
107
|
+
"""
|
|
108
|
+
dependency = dependency.lower()
|
|
109
|
+
for filename in filenames:
|
|
110
|
+
deps = self.parse_dependencies_from_file(filename)
|
|
111
|
+
if dependency in deps:
|
|
112
|
+
return True
|
|
113
|
+
return False
|