File size: 6,394 Bytes
ea305ed
9f0a84b
 
5080de2
ea305ed
b8c5aec
 
21b72c8
5080de2
 
b8c5aec
 
d05a045
5080de2
 
9f37e34
 
5080de2
 
 
 
 
 
 
9f0a84b
5080de2
 
 
 
 
 
 
9f37e34
5080de2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9f37e34
5080de2
 
 
9f0a84b
5080de2
 
 
 
 
c018ea8
9f37e34
5080de2
 
 
 
 
9f0a84b
9f37e34
5080de2
9f0a84b
 
5080de2
9f0a84b
5080de2
9f37e34
5080de2
9f37e34
b8c5aec
9f37e34
5080de2
 
b8c5aec
5080de2
 
 
9f0a84b
9f37e34
5080de2
9f37e34
5080de2
 
 
 
 
9f0a84b
5080de2
 
9f37e34
5080de2
 
 
c018ea8
5080de2
d05a045
9f37e34
 
 
5080de2
 
 
 
 
 
ea305ed
5080de2
 
 
 
 
aae0687
5080de2
9f37e34
 
1b27c48
5080de2
 
 
 
 
 
 
 
 
 
 
b8c5aec
 
5080de2
 
 
 
 
 
9f37e34
b8c5aec
 
9f37e34
5080de2
b8c5aec
9f37e34
b8c5aec
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import os
import shutil
import subprocess
import uuid
import gradio as gr
from fastapi.staticfiles import StaticFiles
from fastapi import FastAPI

# --- Static Folder Setup ---
PREVIEW_PATH = os.path.abspath("hosted_sites")
if not os.path.exists(PREVIEW_PATH):
    os.makedirs(PREVIEW_PATH)

def find_index_html(start_dir):
    """
    Project mein index.html dhoondhne ke liye helper function.
    Build directories ko priority milti hai.
    """
    # Priority folders first
    for root, dirs, files in os.walk(start_dir):
        if "index.html" in files:
            parts = os.path.normpath(root).split(os.sep)
            if any(p in parts for p in ['dist', 'build', 'out', 'public']):
                return os.path.relpath(os.path.join(root, "index.html"), PREVIEW_PATH)
    
    # Fallback to any index.html
    for root, dirs, files in os.walk(start_dir):
        if "index.html" in files:
            return os.path.relpath(os.path.join(root, "index.html"), PREVIEW_PATH)
    return None

def run_command(cmd, cwd):
    """Real-time build logs capture karne ke liye generator function"""
    process = subprocess.Popen(
        cmd, 
        cwd=cwd, 
        stdout=subprocess.PIPE, 
        stderr=subprocess.STDOUT, 
        text=True, 
        shell=True
    )
    while True:
        output = process.stdout.readline()
        if output == '' and process.poll() is not None:
            break
        if output:
            yield output.strip()
    rc = process.poll()
    if rc != 0:
        raise subprocess.CalledProcessError(rc, cmd)

def deploy_project(zip_file):
    if zip_file is None:
        yield "❌ Please upload a ZIP file.", "", gr.update(visible=False), gr.update(visible=False)
        return

    # Unique deployment folder create karna
    deploy_id = f"site-{str(uuid.uuid4())[:8]}"
    deploy_dir = os.path.join(PREVIEW_PATH, deploy_id)
    os.makedirs(deploy_dir)
    
    log_accumulator = []
    def log(msg):
        log_accumulator.append(msg)
        return "\n".join(log_accumulator)

    try:
        # Extract files
        yield log("πŸ“¦ Extracting ZIP archive..."), "", gr.update(visible=False), gr.update(visible=False)
        shutil.unpack_archive(zip_file.name, deploy_dir)
        
        root_path = deploy_dir
        is_node_project = False
        
        # Check node project
        for root, dirs, files in os.walk(deploy_dir):
            if "package.json" in files:
                root_path = root
                is_node_project = True
                break

        # Build steps
        if is_node_project:
            yield log("πŸ“ Node.js project detected."), "", gr.update(visible=False), gr.update(visible=False)
            
            yield log("⚑ Running 'npm install'..."), "", gr.update(visible=False), gr.update(visible=False)
            for line in run_command("npm install", root_path):
                yield log(f"[npm] {line}"), "", gr.update(visible=False), gr.update(visible=False)
            
            yield log("πŸ—οΈ Running 'npm run build'..."), "", gr.update(visible=False), gr.update(visible=False)
            for line in run_command("npm run build", root_path):
                yield log(f"[build] {line}"), "", gr.update(visible=False), gr.update(visible=False)
        else:
            yield log("πŸ“„ Static HTML project detected. Skipping build steps."), "", gr.update(visible=False), gr.update(visible=False)

        # Locate preview entry-point
        relative_index = find_index_html(deploy_dir)
        
        if relative_index:
            preview_url = f"/preview/{relative_index.replace(os.sep, '/')}"
            success_msg = f"πŸš€ **Deployment Successful!**\n\nYour site is live at: `/preview/{relative_index}`"
            
            yield log("πŸŽ‰ Site deployed successfully!"), success_msg, gr.update(src=preview_url, visible=True), gr.update(value=f"Open App in New Tab 🌐", link=preview_url, visible=True)
        else:
            yield log("❌ Error: 'index.html' not found."), "❌ Error: index.html not found.", gr.update(visible=False), gr.update(visible=False)

    except subprocess.CalledProcessError as e:
        yield log(f"\n❌ Build Failed with exit code {e.returncode}"), "❌ Deployment Failed. Check console logs.", gr.update(visible=False), gr.update(visible=False)
    except Exception as e:
        yield log(f"\n❌ Unexpected Error: {str(e)}"), "❌ Deployment Failed.", gr.update(visible=False), gr.update(visible=False)

# --- UI Setup ---
# Removed 'css' parameter to resolve warning in Gradio 6.0
with gr.Blocks(title="Vercel Minimal Clone") as demo:
    gr.HTML("""
        <div style='text-align: center; padding: 20px; border-bottom: 1px solid #eaeaea;'>
            <h1 style='margin: 0; font-family: monospace;'>β–² VERCEL CLONE</h1>
            <p style='color: #666; margin: 5px 0 0 0;'>Instant Serverless Hosting & Preview for ZIP Deployments</p>
        </div>
    """)
    
    with gr.Row():
        with gr.Column(scale=1):
            gr.Markdown("### πŸ› οΈ New Deployment")
            zip_input = gr.File(label="Upload Project ZIP", file_types=[".zip"])
            deploy_btn = gr.Button("Deploy to Production", variant="primary")
            
            gr.Markdown("### πŸ“‹ Build Logs (Console)")
            # Fixed: Removed 'language="bash"' and used default rendering
            console_output = gr.Code(label="Terminal", value="Terminal ready...", lines=12)
            
        with gr.Column(scale=1):
            gr.Markdown("### 🌐 Live Production URL")
            status_text = gr.Markdown("No active deployment.")
            external_link = gr.Button("Open App in New Tab 🌐", visible=False)
            
            preview_iframe = gr.Iframe(
                label="Live Frame",
                src="",
                width="100%",
                height="550px",
                visible=False
            )

    deploy_btn.click(
        fn=deploy_project,
        inputs=zip_input,
        outputs=[console_output, status_text, preview_iframe, external_link]
    )

# --- FastAPI Server ---
app = FastAPI()

# Serving static preview folders
app.mount("/preview", StaticFiles(directory=PREVIEW_PATH), name="preview")

# Mounting Gradio
demo_app = gr.mount_gradio_app(app, demo, path="/")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(demo_app, host="0.0.0.0", port=7860)