UNPKG

@laiyon/create-wasapi

Version:

CLI to create WhatsApp bot projects with Wasapi and BuilderBot

166 lines (165 loc) 6.52 kB
import { startNgrok } from "./ngrokService.js"; import { getFreePort } from "../utils/portUtils.js"; import { promises as fs } from "fs"; import chalk from "chalk"; import ora from "ora"; // Function to kill process and all its children async function killProcessTree(process) { if (!process || !process.pid) return; try { const { execSync } = await import('child_process'); if (global.process.platform === 'win32') { // Windows: Kill process tree execSync(`taskkill /pid ${process.pid} /T /F`, { stdio: 'ignore' }); } else { // Unix/Linux/Mac: Kill process group execSync(`pkill -P ${process.pid}`, { stdio: 'ignore' }); process.kill('SIGTERM'); // If still alive after 2 seconds, force kill setTimeout(() => { try { if (!process.killed) { process.kill('SIGKILL'); } } catch (e) { // Process already dead } }, 2000); } } catch (error) { // Try fallback kill try { process.kill('SIGKILL'); } catch (e) { // Process already dead } } } // Function to check if server is running on port async function waitForServer(port, maxAttempts = 10) { const net = await import('net'); for (let i = 0; i < maxAttempts; i++) { try { await new Promise((resolve, reject) => { const socket = net.connect(port, '127.0.0.1', () => { socket.end(); resolve(true); }); socket.on('error', reject); setTimeout(() => reject(new Error('timeout')), 1000); }); return true; // Server is responding } catch { // Wait 1 second before next attempt await new Promise(resolve => setTimeout(resolve, 1000)); } } return false; // Server didn't start after all attempts } // Function to update .env file with port async function updateEnvWithPort(projectName, port) { const path = await import('path'); const envPath = path.join(process.cwd(), projectName, '.env'); try { // Read existing .env content const envContent = await fs.readFile(envPath, 'utf-8'); // Check if PORT already exists const lines = envContent.split('\n'); const portLineIndex = lines.findIndex(line => line.startsWith('PORT=')); if (portLineIndex !== -1) { // Update existing PORT line lines[portLineIndex] = `PORT=${port}`; } else { // Add new PORT line lines.push(`PORT=${port}`); } // Write updated content back to .env await fs.writeFile(envPath, lines.join('\n')); } catch (error) { console.warn(`⚠️ Warning: Could not update .env with port: ${error}`); } } // Function to install packages and run server with ngrok export async function startDevServerWithNgrok(projectName, phoneNumber) { const { spawn, execSync } = await import('child_process'); const path = await import('path'); let devProcess = null; // Declare outside try block try { const projectPath = path.join(process.cwd(), projectName); console.log(""); console.log(chalk.bgBlue.white(" 🚀 AUTOMATIC SETUP ")); console.log(""); // Install packages with spinner const installSpinner = ora("📦 Installing packages...").start(); try { execSync('npm install', { cwd: projectPath, stdio: 'pipe' }); installSpinner.succeed(chalk.green("📦 Packages installed")); } catch (error) { installSpinner.fail(chalk.red("❌ Package installation failed")); throw new Error("Failed to install packages"); } // Start server with spinner const serverSpinner = ora("🚀 Starting development server...").start(); let port; try { port = await getFreePort(3000); await updateEnvWithPort(projectName, port); devProcess = spawn('npm', ['run', 'dev'], { cwd: projectPath, stdio: 'inherit', // Cambiar a 'inherit' para mostrar logs en tiempo real env: { ...process.env, PORT: port.toString() } }); const serverStarted = await waitForServer(port); if (!serverStarted) { serverSpinner.fail(chalk.red("❌ Server failed to start")); devProcess.kill(); throw new Error("Development server failed to start"); } serverSpinner.succeed(chalk.green(`🚀 Server running on port ${port}`)); } catch (error) { serverSpinner.fail(chalk.red("❌ Server startup failed")); throw error; } // Create ngrok tunnel with spinner const ngrokSpinner = ora("🌐 Creating ngrok tunnel...").start(); try { await Promise.race([ startNgrok(projectName, port, phoneNumber), new Promise((_, reject) => setTimeout(() => reject(new Error("Ngrok timeout")), 30000)) ]); ngrokSpinner.succeed(chalk.green("🌐 Ngrok tunnel active")); // ✅ SUCCESS: Ngrok connected successfully console.log(""); console.log(chalk.bgGreen.black(" 🎉 SETUP COMPLETE ")); console.log(""); console.log(chalk.green("🤖 Your bot is live and ready!")); console.log(""); console.log(chalk.yellow("💡 Keep this terminal open")); console.log(chalk.gray("📋 Press Ctrl+C to stop")); // Don't kill the server - let it run! // The CLI will exit but the server stays alive } catch (ngrokError) { ngrokSpinner.fail(chalk.red("❌ Ngrok tunnel failed")); console.error(chalk.red(` Error: ${ngrokError.message}`)); // Kill development server and all child processes await killProcessTree(devProcess); throw ngrokError; } } catch (error) { // Kill any remaining processes and all child processes await killProcessTree(devProcess); throw error; // Re-throw to let index.ts handle it } }