#!/usr/bin/env bun import { $ } from "bun"; import { spawn } from "child_process"; import path from "path"; import { promisify } from "util"; import net from "net"; const sleep = promisify(setTimeout); // Проверка занятости порта async function isPortInUse(port) { return new Promise((resolve) => { const server = net.createServer(); server.once("error", () => resolve(true)); server.once("listening", () => { server.close(); resolve(false); }); server.listen(port); }); } // Принудительное убийство процесса по PID (Windows) async function killProcess(pid, force = true) { try { if (process.platform === "win32") { await $`taskkill /PID ${pid} ${force ? "/F" : ""}`.quiet(); } else { process.kill(pid, force ? "SIGKILL" : "SIGTERM"); } } catch (e) { // Процесс уже мёртв — ок } } // Поиск и убийство процессов по имени порта async function killPort(port) { try { if (process.platform === "win32") { // Находим PID по порту const { stdout } = await $`netstat -ano | findstr :${port}`.quiet().nothrow(); const lines = stdout.toString().trim().split("\n"); const pids = new Set(); for (const line of lines) { const parts = line.trim().split(/\s+/); if (parts.length >= 5) { const pid = parts[4]; if (pid && !isNaN(parseInt(pid))) { pids.add(parseInt(pid)); } } } for (const pid of pids) { await killProcess(pid); console.log(` 🔪 Убит процесс ${pid} на порту ${port}`); } } else { await $`lsof -ti:${port} | xargs kill -9`.quiet().nothrow(); } } catch (e) { // Нет процессов — ок } } // Проверка и освобождение портов async function cleanupPorts() { console.log("🔍 Проверка портов..."); const ports = [1025, 1080, 4321, 8090]; for (const port of ports) { if (await isPortInUse(port)) { console.log(` ⚠️ Порт ${port} занят, освобождаем...`); await killPort(port); await sleep(500); // Даём время на освобождение } } } let maildev, backend, frontend; // Корректное завершение всех процессов async function cleanup() { console.log("\n🛑 Остановка серверов..."); // Принудительно убиваем каждый процесс с SIGKILL const kills = []; if (maildev && !maildev.killed) { maildev.kill("SIGKILL"); kills.push(new Promise(r => maildev.once("exit", r))); } if (backend && !backend.killed) { backend.kill("SIGKILL"); kills.push(new Promise(r => backend.once("exit", r))); } if (frontend && !frontend.killed) { frontend.kill("SIGKILL"); kills.push(new Promise(r => frontend.once("exit", r))); } // Ждём завершения не дольше 3 секунд await Promise.race([ Promise.all(kills), sleep(3000) ]); process.exit(0); } // Главная функция async function main() { await cleanupPorts(); console.log("🚀 Запуск серверов avtourist086...\n"); // Запуск Maildev с отдельной группой процессов maildev = spawn("maildev", ["--web", "1080", "--smtp", "1025"], { stdio: "inherit", shell: true, windowsHide: true, detached: false }); // Запуск PocketBase backend = spawn("pocketbase.exe", ["serve"], { cwd: path.join(process.cwd(), "backend"), stdio: "inherit", shell: true, windowsHide: true, detached: false, env: { ...process.env, PB_SMTP_HOST: "localhost", PB_SMTP_PORT: "1025", PB_SMTP_FROM: "noreply@avtourist-surgut.ru" } }); // Запуск Astro frontend = spawn("bun", ["dev"], { cwd: "frontend", stdio: "inherit", shell: true, windowsHide: true, detached: false }); // Обработчики сигналов process.on("SIGINT", cleanup); process.on("SIGTERM", cleanup); process.on("SIGBREAK", cleanup); // Windows-specific // Обработка закрытия консоли Windows if (process.platform === "win32") { const readline = await import("readline"); readline.createInterface({ input: process.stdin, output: process.stdout }).on("SIGINT", cleanup); } // Мониторинг процессов maildev.on("exit", (code) => { console.log(`\n📧 Maildev остановлен с кодом ${code}`); if (code !== 0 && code !== null) cleanup(); }); backend.on("exit", (code) => { console.log(`\n💾 Backend остановлен с кодом ${code}`); if (code !== 0 && code !== null) cleanup(); }); frontend.on("exit", (code) => { console.log(`\n🌐 Frontend остановлен с кодом ${code}`); cleanup(); }); console.log("✅ Серверы запущены:\n"); console.log(" 📧 Maildev (SMTP): http://localhost:1080"); console.log(" 💾 Backend (PocketBase): http://localhost:8090"); console.log(" 🌐 Frontend (Astro): http://localhost:4321\n"); console.log("Нажмите Ctrl+C для остановки\n"); } main().catch(err => { console.error("❌ Ошибка:", err); process.exit(1); });