smart-deploy 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.
File without changes
smart_deploy/cli.py ADDED
@@ -0,0 +1,83 @@
1
+ import typer
2
+ import shutil
3
+ import getpass
4
+ import subprocess
5
+ from .database import save_project
6
+ from .github_api import setup_webhook
7
+
8
+ app = typer.Typer(help="Smart Deploy: Automated CI/CD Tool for Developers")
9
+
10
+ @app.command()
11
+ def add(
12
+ repo_url: str = typer.Argument(..., help="Target GitHub Repository URL"),
13
+ path: str = typer.Argument(..., help="Absolute local path on the server"),
14
+ server_url: str = typer.Argument(..., help="Public URL or IP of this server (e.g., http://8.8.8.8:9000)"),
15
+ token: str = typer.Option(None, "--token", "-t", help="GitHub Personal Access Token (requires 'repo' permissions)"),
16
+ restart_cmd: str = typer.Option(None, "--restart", "-r", help="Custom command to run after deployment (e.g., 'pm2 restart api')")
17
+ ):
18
+ """
19
+ Register a new project and automatically configure its GitHub webhook.
20
+ """
21
+ typer.echo(f"Registering project: {repo_url} at {path}")
22
+
23
+ # ذخیره کانفیگ پروژه در سیستم جهت ارجاع هنگام دریافت درخواست وب‌هوک
24
+ repo_name = save_project(repo_url, path, token, restart_cmd)
25
+ typer.secho(f"Project '{repo_name}' successfully added to the local database.", fg=typer.colors.GREEN)
26
+
27
+ # در صورت وجود توکن، ارتباط با گیت‌هاب و ثبت وب‌هوک آغاز می‌شود
28
+ if token:
29
+ typer.echo("Initiating automated GitHub webhook configuration...")
30
+ if setup_webhook(repo_name, token, server_url):
31
+ typer.secho("GitHub Webhook configured successfully.", fg=typer.colors.GREEN)
32
+ else:
33
+ typer.secho("Failed to configure GitHub Webhook. Verify token permissions.", fg=typer.colors.RED)
34
+ else:
35
+ typer.secho("Warning: No token provided. Automated webhook configuration skipped.", fg=typer.colors.YELLOW)
36
+
37
+ @app.command()
38
+ def start(port: int = typer.Option(9000, "--port", "-p", help="Port number for the webhook listener")):
39
+ """
40
+ Start the background FastAPI server to listen for GitHub webhooks.
41
+ """
42
+ typer.secho(f"Initializing Smart Deploy Listener on port {port}...", fg=typer.colors.CYAN)
43
+
44
+ # فراخوانی Uvicorn به عنوان سرور اجرای FastAPI
45
+ subprocess.run(["uvicorn", "smart_deploy.server:app", "--host", "0.0.0.0", "--port", str(port)])
46
+
47
+ @app.command()
48
+ def generate_service(port: int = typer.Option(9000, "--port", "-p", help="Port number for the webhook listener")):
49
+ """
50
+ Generate a systemd service configuration for Linux servers to run in the background.
51
+ """
52
+ # پیدا کردن مسیر دقیق فایل اجرایی و نام کاربری لینوکس
53
+ executable = shutil.which("smart-deploy") or "/usr/local/bin/smart-deploy"
54
+ user = getpass.getuser()
55
+
56
+ service_content = f"""[Unit]
57
+ Description=Smart Deploy Webhook Listener
58
+ After=network.target
59
+
60
+ [Service]
61
+ User={user}
62
+ ExecStart={executable} start --port {port}
63
+ Restart=always
64
+ RestartSec=3
65
+
66
+ [Install]
67
+ WantedBy=multi-user.target
68
+ """
69
+
70
+ typer.secho("1. Create a new service file using nano:", fg=typer.colors.CYAN)
71
+ typer.echo("sudo nano /etc/systemd/system/smart-deploy.service\n")
72
+
73
+ typer.secho("2. Paste the following configuration into the file:\n", fg=typer.colors.CYAN)
74
+ typer.echo(service_content)
75
+
76
+ typer.secho("3. Enable and start the service by running:", fg=typer.colors.YELLOW)
77
+ typer.echo("sudo systemctl daemon-reload")
78
+ typer.echo("sudo systemctl enable smart-deploy")
79
+ typer.echo("sudo systemctl start smart-deploy")
80
+ typer.echo("sudo systemctl status smart-deploy")
81
+
82
+ if __name__ == "__main__":
83
+ app()
@@ -0,0 +1,44 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ # تعیین مسیر ذخیره‌سازی اطلاعات پروژه‌ها در دایرکتوری کاربر تا با حذف برنامه دیتابیس پاک نشود
6
+ CONFIG_DIR = Path.home() / ".smart-deploy"
7
+ DB_FILE = CONFIG_DIR / "projects.json"
8
+
9
+ def init_db():
10
+ """Initialize the database directory and file if they don't exist."""
11
+ CONFIG_DIR.mkdir(exist_ok=True)
12
+ if not DB_FILE.exists():
13
+ with open(DB_FILE, "w") as f:
14
+ json.dump({}, f)
15
+
16
+ def load_projects() -> dict:
17
+ """Load all projects from the JSON database."""
18
+ init_db()
19
+ with open(DB_FILE, "r") as f:
20
+ return json.load(f)
21
+
22
+ def save_project(repo_url: str, path: str, token: Optional[str] = None, restart_cmd: Optional[str] = None) -> str:
23
+ """Save a new project to the database and return the extracted repository name."""
24
+ projects = load_projects()
25
+
26
+ # استخراج شناسه یکتای ریپازیتوری از لینک برای استفاده به عنوان کلید اصلی
27
+ repo_name = repo_url.replace("https://github.com/", "").replace(".git", "")
28
+
29
+ projects[repo_name] = {
30
+ "repo_url": repo_url,
31
+ "path": path,
32
+ "token": token,
33
+ "restart_cmd": restart_cmd
34
+ }
35
+
36
+ with open(DB_FILE, "w") as f:
37
+ json.dump(projects, f, indent=4)
38
+
39
+ return repo_name
40
+
41
+ def get_project(repo_name: str) -> Optional[dict]:
42
+ """Retrieve project configuration by repository name."""
43
+ projects = load_projects()
44
+ return projects.get(repo_name)
@@ -0,0 +1,61 @@
1
+ import os
2
+ import subprocess
3
+ from typing import Optional
4
+
5
+ def run_command(command: str, cwd: str) -> bool:
6
+ """Execute a shell command in the specified directory and print the output."""
7
+ print(f"Executing: [{command}] in {cwd}")
8
+ try:
9
+ result = subprocess.run(
10
+ command,
11
+ shell=True,
12
+ cwd=cwd,
13
+ stdout=subprocess.PIPE,
14
+ stderr=subprocess.PIPE,
15
+ text=True
16
+ )
17
+ if result.returncode == 0:
18
+ print(f"Success:\n{result.stdout.strip()}")
19
+ return True
20
+ else:
21
+ print(f"Error:\n{result.stderr.strip()}")
22
+ return False
23
+ except Exception as e:
24
+ print(f"System Exception: {e}")
25
+ return False
26
+
27
+ def pull_repo(path: str, token: Optional[str], repo_url: str) -> bool:
28
+ """Pull the latest changes from the remote repository."""
29
+ if token:
30
+ # تزریق مستقیم توکن به آدرس ریپازیتوری برای دور زدن نیاز به احراز هویت تعاملی در سرور
31
+ url_without_protocol = repo_url.replace("https://", "")
32
+ auth_url = f"https://{token}@{url_without_protocol}"
33
+ run_command(f"git remote set-url origin {auth_url}", path)
34
+
35
+ return run_command("git pull", path)
36
+
37
+ def detect_and_deploy(path: str, restart_cmd: Optional[str] = None):
38
+ """Analyze the project structure and run appropriate build commands."""
39
+
40
+ # بررسی وجود فایل‌های پایه جاوا اسکریپت برای تشخیص فریم‌ورک‌های مبتنی بر Node
41
+ if os.path.exists(os.path.join(path, "package.json")):
42
+ print("Framework detected: Node.js")
43
+ run_command("npm install", path)
44
+
45
+ if os.path.exists(os.path.join(path, "next.config.js")) or os.path.exists(os.path.join(path, "next.config.mjs")):
46
+ print("Framework detected: Next.js")
47
+ run_command("npm run build", path)
48
+
49
+ # بررسی ساختار پروژه‌های پایتونی
50
+ elif os.path.exists(os.path.join(path, "requirements.txt")):
51
+ print("Framework detected: Python")
52
+ # اولویت استفاده از محیط مجازی در صورت وجود برای جلوگیری از تداخل پکیج‌ها در سرور
53
+ pip_cmd = "venv/bin/pip" if os.path.exists(os.path.join(path, "venv")) else "pip"
54
+ run_command(f"{pip_cmd} install -r requirements.txt", path)
55
+
56
+ # اجرای دستور ری‌استارت شخصی‌سازی شده در صورت تعریف توسط کاربر
57
+ if restart_cmd:
58
+ print(f"Executing custom restart command: {restart_cmd}")
59
+ run_command(restart_cmd, path)
60
+ else:
61
+ print("No custom restart command provided. Deployment cycle completed.")
@@ -0,0 +1,46 @@
1
+ import requests
2
+
3
+ def setup_webhook(repo_name: str, token: str, server_url: str) -> bool:
4
+ """Configure a push webhook on the target GitHub repository."""
5
+ api_url = f"https://api.github.com/repos/{repo_name}/hooks"
6
+ target_webhook_url = f"{server_url}/webhook"
7
+
8
+ headers = {
9
+ "Authorization": f"token {token}",
10
+ "Accept": "application/vnd.github.v3+json"
11
+ }
12
+
13
+ payload = {
14
+ "name": "web",
15
+ "active": True,
16
+ "events": ["push"],
17
+ "config": {
18
+ "url": target_webhook_url,
19
+ "content_type": "json",
20
+ "insecure_ssl": "1" # مجاز کردن کانکشن‌های بدون SSL برای سرورهای تستی
21
+ }
22
+ }
23
+
24
+ # بررسی وب‌هوک‌های فعلی ریپازیتوری برای جلوگیری از ثبت آدرس تکراری
25
+ try:
26
+ response = requests.get(api_url, headers=headers)
27
+ if response.status_code == 200:
28
+ for hook in response.json():
29
+ if hook.get("config", {}).get("url") == target_webhook_url:
30
+ print(f"Webhook configuration already exists for {target_webhook_url}")
31
+ return True
32
+ except Exception as e:
33
+ print(f"Failed to fetch existing webhooks: {e}")
34
+
35
+ # ثبت وب‌هوک جدید روی ریپازیتوری
36
+ try:
37
+ response = requests.post(api_url, headers=headers, json=payload)
38
+ if response.status_code == 201:
39
+ print("Webhook successfully registered on GitHub.")
40
+ return True
41
+ else:
42
+ print(f"Webhook registration failed [{response.status_code}]: {response.text}")
43
+ return False
44
+ except Exception as e:
45
+ print(f"Exception occurred during webhook registration: {e}")
46
+ return False
smart_deploy/server.py ADDED
@@ -0,0 +1,40 @@
1
+ from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
2
+ from .database import get_project
3
+ from .detector import pull_repo, detect_and_deploy
4
+
5
+ app = FastAPI(title="Smart Deploy Webhook Receiver")
6
+
7
+ def handle_deployment(project_info: dict):
8
+ """Background task to handle the deployment process."""
9
+ path = project_info.get("path")
10
+ token = project_info.get("token")
11
+ repo_url = project_info.get("repo_url")
12
+ restart_cmd = project_info.get("restart_cmd")
13
+
14
+ print(f"--- Initiating deployment pipeline for {repo_url} ---")
15
+
16
+ if pull_repo(path, token, repo_url):
17
+ detect_and_deploy(path, restart_cmd)
18
+ print("--- Deployment pipeline executed successfully ---")
19
+ else:
20
+ print("--- Deployment pipeline aborted: Pull operation failed ---")
21
+
22
+ @app.post("/webhook")
23
+ async def github_webhook(request: Request, background_tasks: BackgroundTasks):
24
+ """Endpoint for receiving GitHub webhook push events."""
25
+ payload = await request.json()
26
+
27
+ # استخراج نام کامل ریپازیتوری از دیتای ارسالی گیت‌هاب جهت تطبیق با دیتابیس محلی
28
+ try:
29
+ repo_name = payload["repository"]["full_name"]
30
+ except KeyError:
31
+ raise HTTPException(status_code=400, detail="Malformed GitHub payload structure.")
32
+
33
+ project_info = get_project(repo_name)
34
+ if not project_info:
35
+ raise HTTPException(status_code=404, detail=f"Repository '{repo_name}' is not registered in Smart Deploy.")
36
+
37
+ # اجرای پروسه در پس‌زمینه تا سرور گیت‌هاب با خطای Timeout به دلیل طولانی شدن زمان بیلد مواجه نشود
38
+ background_tasks.add_task(handle_deployment, project_info)
39
+
40
+ return {"status": "Deployment job queued successfully", "repository": repo_name}
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: smart-deploy
3
+ Version: 0.1.0
4
+ Requires-Dist: typer
5
+ Requires-Dist: fastapi
6
+ Requires-Dist: uvicorn
7
+ Requires-Dist: requests
8
+ Requires-Dist: pydantic
9
+ Dynamic: requires-dist
@@ -0,0 +1,11 @@
1
+ smart_deploy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ smart_deploy/cli.py,sha256=bVqqEtWDgXcD9m3rXZEACytl7Kb_SS5WNArG3m7GV-Q,3699
3
+ smart_deploy/database.py,sha256=vM_ART7axKwCBL13tzjudmmtbv9MbyA7DSWGzyniAYw,1641
4
+ smart_deploy/detector.py,sha256=1Sung6XqUB_YjisDZo9oa3JVhdRpkOknwJAEhnyCMv8,2860
5
+ smart_deploy/github_api.py,sha256=olakUiBiAAajSHfceD4fbgkVq98XIYatUkYKHW4Eg8s,1859
6
+ smart_deploy/server.py,sha256=6JLFPvDswqCiOZB-oVsMwEnuU8hqV0CgdeJAxahV6_s,1873
7
+ smart_deploy-0.1.0.dist-info/METADATA,sha256=AJwXzo54uq14OTVDEKPReTiwH97APoVW2sJq04TTLW8,203
8
+ smart_deploy-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
9
+ smart_deploy-0.1.0.dist-info/entry_points.txt,sha256=YydznVUAJwYJEqY1jzACsyvKOyRj1zpNdl2Is9d8P4U,54
10
+ smart_deploy-0.1.0.dist-info/top_level.txt,sha256=uYK3d01eDZrsxOK-Mn0tzkkoOQKxbEus41DfvzeQooU,13
11
+ smart_deploy-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ smart-deploy = smart_deploy.cli:app
@@ -0,0 +1 @@
1
+ smart_deploy