AI Update: دیپلوی با اف تی پی باید به این صورت انجام شود نسخه ی موجود د
This commit is contained in:
+48
-14
@@ -401,9 +401,12 @@ async def build_ftp_menu(chat_id: int) -> tuple[str, InlineKeyboardMarkup]:
|
||||
if is_fa:
|
||||
keyboard = [
|
||||
[
|
||||
InlineKeyboardButton("🚀 دیپلوی روی FTP (Clean Deploy)", callback_data="btn_ftp_deploy"),
|
||||
InlineKeyboardButton("🚀 دیپلوی نسخه پروداکشن روی FTP", callback_data="btn_ftp_deploy"),
|
||||
InlineKeyboardButton("🔍 تست اتصال FTP", callback_data="btn_ftp_test"),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton("🌿 انتقال از dev به پروداکشن (Publish)", callback_data="btn_git_publish"),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton("🌐 ویرایش هاست/پورت", callback_data="ftp_set:host"),
|
||||
InlineKeyboardButton("👤 ویرایش نام کاربری", callback_data="ftp_set:user"),
|
||||
@@ -424,9 +427,12 @@ async def build_ftp_menu(chat_id: int) -> tuple[str, InlineKeyboardMarkup]:
|
||||
else:
|
||||
keyboard = [
|
||||
[
|
||||
InlineKeyboardButton("🚀 Deploy via FTP (Clean)", callback_data="btn_ftp_deploy"),
|
||||
InlineKeyboardButton("🚀 Deploy Production to FTP", callback_data="btn_ftp_deploy"),
|
||||
InlineKeyboardButton("🔍 Test FTP Connection", callback_data="btn_ftp_test"),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton("🌿 Publish dev to Production", callback_data="btn_git_publish"),
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton("🌐 Edit Host/Port", callback_data="ftp_set:host"),
|
||||
InlineKeyboardButton("👤 Edit Username", callback_data="ftp_set:user"),
|
||||
@@ -4711,11 +4717,11 @@ async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
await query.message.reply_html(text, reply_markup=markup, disable_web_page_preview=True)
|
||||
|
||||
elif data == "btn_git_publish":
|
||||
await query.answer("🚀 در حال انتشار به پروداکشن..." if is_fa else "🚀 Publishing to production...")
|
||||
await query.answer("🚀 در حال انتقال تغییرات از dev به production..." if is_fa else "🚀 Publishing to production...")
|
||||
if curr_proj:
|
||||
ok, res_msg, _ = await git_manager.git_publish(curr_proj.workspace, message="Publish via bot button", repo_name=curr_proj.name)
|
||||
ok, res_msg, _ = await git_manager.git_publish(curr_proj.workspace, message="Publish dev to production via bot button", repo_name=curr_proj.name)
|
||||
try:
|
||||
await query.answer("✅ انتشار به پروداکشن با موفقیت انجام شد!" if ok else "⚠️ خطا در انتشار", show_alert=not ok)
|
||||
await query.message.reply_html(res_msg, disable_web_page_preview=True)
|
||||
except Exception:
|
||||
pass
|
||||
text, markup = await build_git_menu(chat_id)
|
||||
@@ -4864,14 +4870,10 @@ async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
await query.message.reply_html(text, reply_markup=markup, disable_web_page_preview=True)
|
||||
|
||||
elif data == "btn_ftp_deploy":
|
||||
await query.answer("🚀 در حال استقرار هوشمند روی FTP..." if is_fa else "🚀 Deploying to FTP...")
|
||||
await query.answer("🚀 در حال استقرار نسخه پروداکشن روی FTP..." if is_fa else "🚀 Deploying production to FTP...")
|
||||
if curr_proj and curr_proj.ftp_host:
|
||||
# Auto publish before deploy
|
||||
try:
|
||||
await git_manager.git_publish(curr_proj.workspace, message="Auto-publish before UI FTP deploy", repo_name=curr_proj.name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
status_msg = await query.message.reply_html("🚀 <i>در حال آمادهسازی و ارسال نسخه شاخه production به سرور FTP...</i>" if is_fa else "🚀 <i>Preparing and uploading production branch files to FTP...</i>")
|
||||
|
||||
res = await ftp_manager.deploy_project(
|
||||
workspace_path=curr_proj.workspace,
|
||||
host=curr_proj.ftp_host,
|
||||
@@ -4880,18 +4882,50 @@ async def callback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
password=curr_proj.ftp_password or "",
|
||||
remote_path=curr_proj.ftp_path or "/",
|
||||
tls=curr_proj.ftp_tls,
|
||||
branch="production",
|
||||
)
|
||||
|
||||
if res.get("success"):
|
||||
files_up = res.get("files_uploaded", 0)
|
||||
files_skip = res.get("files_skipped", 0)
|
||||
dur = res.get("duration", 0)
|
||||
b_trans = res.get("bytes_transferred", 0)
|
||||
size_mb = f"{b_trans / (1024 * 1024):.2f} MB" if b_trans > 1024 * 1024 else f"{b_trans / 1024:.1f} KB"
|
||||
|
||||
report = (
|
||||
f"✅ <b>استقرار روی FTP با موفقیت انجام شد!</b>\n\n"
|
||||
f"• 📁 <b>پروژه:</b> <code>{escape_html(curr_proj.name)}</code>\n"
|
||||
f"• 🌿 <b>شاخه سورس دیپلوی:</b> <code>production</code>\n"
|
||||
f"• 🌐 <b>سرور مقصد:</b> <code>{escape_html(curr_proj.ftp_host)}:{curr_proj.ftp_port}</code>\n"
|
||||
f"• 📂 <b>مسیر هاست:</b> <code>{escape_html(curr_proj.ftp_path or '/')}</code>\n"
|
||||
f"• 📦 <b>تعداد فایلهای آپلود شده:</b> {files_up}\n"
|
||||
f"• 🧹 <b>فایلهای زائد و فیلتر شده:</b> {files_skip}\n"
|
||||
f"• 📊 <b>حجم کل منتقل شده:</b> {size_mb}\n"
|
||||
f"• ⏱️ <b>مدت زمان دیپلوی:</b> {dur} ثانیه"
|
||||
) if is_fa else (
|
||||
f"✅ <b>FTP Deployment Completed Successfully!</b>\n\n"
|
||||
f"• 📁 <b>Project:</b> <code>{escape_html(curr_proj.name)}</code>\n"
|
||||
f"• 🌿 <b>Source Branch:</b> <code>production</code>\n"
|
||||
f"• 🌐 <b>Target Server:</b> <code>{escape_html(curr_proj.ftp_host)}:{curr_proj.ftp_port}</code>\n"
|
||||
f"• 📂 <b>Remote Path:</b> <code>{escape_html(curr_proj.ftp_path or '/')}</code>\n"
|
||||
f"• 📦 <b>Files Uploaded:</b> {files_up}\n"
|
||||
f"• 🧹 <b>Excluded/Skipped:</b> {files_skip}\n"
|
||||
f"• 📊 <b>Transferred:</b> {size_mb}\n"
|
||||
f"• ⏱️ <b>Duration:</b> {dur}s"
|
||||
)
|
||||
try:
|
||||
await query.answer(f"✅ دیپلوی با موفقیت انجام شد ({files_up} فایل در {dur} ثانیه)", show_alert=True)
|
||||
await status_msg.edit_text(report, parse_mode=constants.ParseMode.HTML)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
err = res.get("error", "Error")
|
||||
err_report = (
|
||||
f"❌ <b>خطا در استقرار روی FTP:</b>\n\n"
|
||||
f"• ⚠️ <b>پیام خطا:</b> {escape_html(str(err))}\n"
|
||||
f"• 💡 <i>پیشنهاد: ابتدا دکمه تست اتصال را بزنید یا مشخصات هاست و مسیر را بررسی کنید.</i>"
|
||||
) if is_fa else f"❌ <b>FTP Deployment Error:</b>\n{escape_html(str(err))}"
|
||||
try:
|
||||
await query.answer(f"❌ خطا در دیپلوی: {err}"[:200], show_alert=True)
|
||||
await status_msg.edit_text(err_report, parse_mode=constants.ParseMode.HTML)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
|
||||
@@ -8,6 +8,9 @@ from pathlib import Path
|
||||
from typing import Optional, Dict, Any, List, Tuple, Set
|
||||
import ftplib
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
logger = logging.getLogger("AGYFTPManager")
|
||||
|
||||
@@ -227,11 +230,16 @@ class FTPManager:
|
||||
tls: bool = False,
|
||||
custom_excludes: Optional[List[str]] = None,
|
||||
dry_run: bool = False,
|
||||
branch: str = "production",
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Deploys files from workspace_path to the remote FTP server cleanly.
|
||||
Deploys files from the specified git branch (default: production) to the remote FTP server cleanly.
|
||||
Extracts files directly from the git branch to ensure exact production code is deployed.
|
||||
Skips all temporary, git, session, and junk files.
|
||||
"""
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
ws = Path(workspace_path).expanduser().resolve()
|
||||
if not ws.exists() or not ws.is_dir():
|
||||
return {
|
||||
@@ -249,8 +257,42 @@ class FTPManager:
|
||||
uploaded_files: List[str] = []
|
||||
skipped_files: List[str] = []
|
||||
total_bytes = 0
|
||||
temp_export_dir = None
|
||||
|
||||
try:
|
||||
# 1. Determine source directory: if git exists, export the exact production branch
|
||||
source_dir = ws
|
||||
if (ws / ".git").exists():
|
||||
try:
|
||||
# Check if branch exists
|
||||
branches_proc = subprocess.run(
|
||||
["git", "branch", "--list", branch],
|
||||
cwd=str(ws),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
target_branch = branch if branch in branches_proc.stdout else "HEAD"
|
||||
|
||||
temp_export_dir = Path(tempfile.mkdtemp(prefix="ftp_deploy_prod_"))
|
||||
# Use git archive to cleanly extract files from the target branch
|
||||
archive_proc = subprocess.Popen(
|
||||
["git", "archive", target_branch],
|
||||
cwd=str(ws),
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
tar_proc = subprocess.Popen(
|
||||
["tar", "-x", "-C", str(temp_export_dir)],
|
||||
stdin=archive_proc.stdout,
|
||||
)
|
||||
archive_proc.stdout.close()
|
||||
tar_proc.communicate()
|
||||
|
||||
if tar_proc.returncode == 0:
|
||||
source_dir = temp_export_dir
|
||||
except Exception as ge:
|
||||
logger.warning(f"Git archive export failed for {branch} (falling back to workspace): {ge}")
|
||||
source_dir = ws
|
||||
|
||||
ftp = self._create_ftp_client(host.strip(), int(port), user.strip(), password, tls, timeout=20)
|
||||
|
||||
# Navigate or create remote root path
|
||||
@@ -280,11 +322,11 @@ class FTPManager:
|
||||
|
||||
ensure_remote_dir(target_root)
|
||||
|
||||
# Scan workspace
|
||||
# Scan source directory
|
||||
all_files_to_upload: List[Tuple[Path, str]] = [] # (local_path, rel_path)
|
||||
|
||||
for root, dirs, files in os.walk(str(ws)):
|
||||
rel_dir = os.path.relpath(root, str(ws))
|
||||
for root, dirs, files in os.walk(str(source_dir)):
|
||||
rel_dir = os.path.relpath(root, str(source_dir))
|
||||
if rel_dir == ".":
|
||||
rel_dir = ""
|
||||
|
||||
@@ -359,6 +401,12 @@ class FTPManager:
|
||||
"duration": round(time.time() - start_time, 2),
|
||||
"uploaded_list": uploaded_files,
|
||||
}
|
||||
finally:
|
||||
if temp_export_dir and Path(temp_export_dir).exists():
|
||||
try:
|
||||
shutil.rmtree(temp_export_dir, ignore_errors=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return await asyncio.to_thread(_run_deploy)
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user