diff --git a/package.json b/package.json index 2fb725b..84e8486 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Монорепозиторий для Astro + PocketBase проекта", "main": "index.js", "scripts": { - "dev": "concurrently \"bun run dev:maildev\" \"bun run dev:frontend\" \"bun run dev:backend\"", + "dev": "bun ./scripts/dev.js", "dev:maildev": "maildev", "dev:frontend": "cd frontend && bun run dev", "dev:backend": "cd backend && ./pocketbase.exe serve", diff --git a/scripts/dev.js b/scripts/dev.js new file mode 100644 index 0000000..94f9e50 --- /dev/null +++ b/scripts/dev.js @@ -0,0 +1,175 @@ +#!/usr/bin/env bun + +import { $ } from "bun"; +import { spawn } from "child_process"; +import path from "path"; +import { promisify } from "util"; +import net from "net"; +import chalk from "chalk"; + +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); + }); +} + +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") { + 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(chalk.yellow(` 🔪 Убит процесс ${pid} на порту ${port}`)); + } + } else { + await $`lsof -ti:${port} | xargs kill -9`.quiet().nothrow(); + } + } catch (e) { + } +} + +async function cleanupPorts() { + console.log(chalk.cyan("🔍 Проверка портов...")); + const ports = [1025, 1080, 4321, 8090]; + + for (const port of ports) { + if (await isPortInUse(port)) { + console.log(chalk.yellow(` ⚠️ Порт ${port} занят, освобождаем...`)); + await killPort(port); + await sleep(500); + } + } +} + +let maildev, backend, frontend; + +async function cleanup() { + console.log(chalk.red("\n🛑 Остановка серверов...")); + + 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))); + } + + await Promise.race([ + Promise.all(kills), + sleep(3000) + ]); + + process.exit(0); +} + +async function main() { + await cleanupPorts(); + + console.log(chalk.green.bold("🚀 Запуск серверов astro-advokat...\n")); + + maildev = spawn("maildev", ["--web", "1080", "--smtp", "1025"], { + stdio: "inherit", + shell: true, + windowsHide: true, + detached: false + }); + + 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@advokat-surgut.ru" + } + }); + + 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); + + 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(chalk.gray(`\n📧 Maildev остановлен с кодом ${code}`)); + if (code !== 0 && code !== null) cleanup(); + }); + + backend.on("exit", (code) => { + console.log(chalk.gray(`\n💾 Backend остановлен с кодом ${code}`)); + if (code !== 0 && code !== null) cleanup(); + }); + + frontend.on("exit", (code) => { + console.log(chalk.gray(`\n🌐 Frontend остановлен с кодом ${code}`)); + cleanup(); + }); + + console.log(chalk.green("✅ Серверы запущены:\n")); + console.log(chalk.blue(" 📧 Maildev (SMTP): ") + chalk.underline("http://localhost:1080")); + console.log(chalk.blue(" 💾 Backend (PocketBase): ") + chalk.underline("http://localhost:8090")); + console.log(chalk.blue(" 🌐 Frontend (Astro): ") + chalk.underline("http://localhost:4321") + "\n"); + console.log(chalk.gray("Нажмите Ctrl+C для остановки\n")); +} + +main().catch(err => { + console.error(chalk.red("❌ Ошибка:"), err); + process.exit(1); +}); \ No newline at end of file