import { exec, spawn } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import readline from "node:readline"; import { promisify } from "node:util"; const execAsync = promisify(exec); const VITE_PORT = 3000; const SERVER_PORT = 8080; /** * Read environment variable from .env file */ function getEnvVariable(filePath, key) { if (!fs.existsSync(filePath)) { return null; } const content = fs.readFileSync(filePath, "utf-8"); const lines = content.split("\n"); for (const line of lines) { const trimmed = line.trim(); if (trimmed && !trimmed.startsWith("#")) { const [envKey, ...valueParts] = trimmed.split("="); if (envKey && envKey.trim() === key) { return valueParts.join("="); } } } return null; } /** * Get the process info using a specific port */ async function getProcessOnPort(port) { try { const { stdout } = await execAsync(`lsof -ti:${port}`); const pid = stdout.trim(); if (pid) { const { stdout: psOut } = await execAsync(`ps -p ${pid} -o comm=`); const processName = psOut.trim(); return { pid: parseInt(pid), processName }; } return null; } catch { return null; } } /** * Kill a process by PID */ async function killProcess(pid) { try { await execAsync(`kill -9 ${pid}`); return true; } catch { return false; } } /** * Prompt user for confirmation */ function promptUser(question) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); return new Promise((resolve) => { rl.question(question, (answer) => { rl.close(); resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes"); }); }); } /** * Check and kill processes on ports 3000 and 8080 */ async function handlePorts() { const viteProcess = await getProcessOnPort(VITE_PORT); const serverProcess = await getProcessOnPort(SERVER_PORT); const processesToKill = []; if (viteProcess) processesToKill.push({ port: VITE_PORT, ...viteProcess }); if (serverProcess) processesToKill.push({ port: SERVER_PORT, ...serverProcess }); if (processesToKill.length === 0) { console.log("โœ… Ports 3000 and 8080 are available\n"); return; } console.log("\nโš ๏ธ Found processes on required ports:"); for (const proc of processesToKill) { console.log(` Port ${proc.port}: ${proc.processName} (PID: ${proc.pid})`); } const shouldKill = await promptUser( "\nKill these processes and continue? (y/n): ", ); if (!shouldKill) { console.log("โŒ Aborted by user"); process.exit(0); } console.log("\n๐Ÿ”จ Killing processes..."); for (const proc of processesToKill) { const killed = await killProcess(proc.pid); if (killed) { console.log(` โœ… Killed process ${proc.pid} on port ${proc.port}`); } else { console.log( ` โŒ Failed to kill process ${proc.pid} on port ${proc.port}`, ); } } // Wait for ports to be released await new Promise((r) => setTimeout(r, 500)); console.log(""); } async function startDev() { try { // Check if using remote backend (api.useautumn.com) const rootDir = path.dirname(new URL(import.meta.url).pathname); const projectRoot = path.join(rootDir, ".."); const viteEnvPath = path.join(projectRoot, "vite", ".env"); const backendUrl = process.env.VITE_BACKEND_URL || getEnvVariable(viteEnvPath, "VITE_BACKEND_URL"); const isUsingRemoteBackend = backendUrl?.includes("api.useautumn.com"); if (isUsingRemoteBackend) { console.log("\n๐ŸŒ Using remote backend (api.useautumn.com)"); console.log("โญ๏ธ Skipping port cleanup...\n"); } else { // Port cleanup disabled (detection is unreliable) console.log("โญ๏ธ Skipping port cleanup...\n"); // await handlePorts(); } // Step 1: Build shared package first (initial build) console.log("\n๐Ÿ“ฆ Building shared package...\n"); const buildShared = spawn("bun", ["run", "build"], { cwd: "shared", stdio: "inherit", shell: true, }); await new Promise((resolve, reject) => { buildShared.on("close", (code) => { if (code !== 0) { reject(new Error(`Shared package build failed with code ${code}`)); } else { resolve(); } }); buildShared.on("error", reject); }); console.log("\nโœ… Shared package built successfully!\n"); // Clear Vite cache to prevent dep optimization issues const viteCachePath = path.join( projectRoot, "vite", "node_modules", ".vite", ); if (fs.existsSync(viteCachePath)) { console.log("๐Ÿงน Clearing Vite cache...\n"); fs.rmSync(viteCachePath, { recursive: true, force: true }); } console.log("๐Ÿš€ Starting development servers in watch mode...\n"); // Step 2: Start server, workers, and vite first (they'll use the built shared package) const concurrentlyCmd = spawn( "bunx", [ "concurrently", "-n", "server,workers,vite,shared", "-c", "green,yellow,blue,cyan", `"cd server && SERVER_PORT=${SERVER_PORT} bun dev"`, `"cd server && bun workers:dev"`, `"cd vite && VITE_PORT=${VITE_PORT} bun dev"`, `"cd shared && bun run dev:watch"`, ], { stdio: "inherit", shell: true, env: { ...process.env, VITE_PORT: VITE_PORT.toString(), SERVER_PORT: SERVER_PORT.toString(), }, }, ); concurrentlyCmd.on("error", (error) => { console.error("Failed to start development servers:", error); process.exit(1); }); concurrentlyCmd.on("exit", (code) => { if (code !== 0) { console.error(`Development servers exited with code ${code}`); } process.exit(code); }); // Handle termination signals process.on("SIGINT", () => { console.log("\n\n๐Ÿ›‘ Shutting down development servers..."); concurrentlyCmd.kill("SIGINT"); }); process.on("SIGTERM", () => { console.log("\n\n๐Ÿ›‘ Shutting down development servers..."); concurrentlyCmd.kill("SIGTERM"); }); } catch (error) { console.error("Error starting development servers:", error); process.exit(1); } } startDev();