#!/usr/bin/env npx tsx /** * Sync Server * * Local HTTP server for executing sync commands from the Dashboard UI. * Runs on localhost:3001 with optional token authentication. * * Usage: * npm run sync-server * * Security: * - Binds to localhost only (not accessible from network) * - Optional token auth via SYNC_TOKEN env var * - Whitelisted commands only */ import { spawn } from "child_process"; import http from "http"; import dotenv from "dotenv"; // Load environment variables dotenv.config({ path: ".env.local" }); dotenv.config(); const PORT = 3001; const HOST = "127.0.0.1"; // Localhost only for security // Optional token for authentication (set in .env.local) const SYNC_TOKEN = process.env.SYNC_TOKEN || ""; // Whitelist of allowed sync commands const ALLOWED_COMMANDS: Record }> = { "sync": { script: "sync" }, "sync:prod": { script: "sync:prod" }, "sync:discovery": { script: "sync:discovery" }, "sync:discovery:prod": { script: "sync:discovery:prod" }, "sync:all": { script: "sync:all" }, "sync:all:prod": { script: "sync:all:prod" }, }; // CORS headers for local development function setCorsHeaders(res: http.ServerResponse) { res.setHeader("Access-Control-Allow-Origin", "http://localhost:5173"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Sync-Token"); } // Verify authentication token function verifyAuth(req: http.IncomingMessage): boolean { // If no token configured, allow all requests (dev mode) if (!SYNC_TOKEN) { return true; } const token = req.headers["x-sync-token"]; return token === SYNC_TOKEN; } // Parse JSON body from request async function parseBody(req: http.IncomingMessage): Promise> { return new Promise((resolve, reject) => { let body = ""; req.on("data", (chunk) => { body += chunk.toString(); }); req.on("end", () => { try { resolve(body ? JSON.parse(body) : {}); } catch { reject(new Error("Invalid JSON")); } }); req.on("error", reject); }); } // Execute npm script and stream output function executeScript( scriptName: string, res: http.ServerResponse, ): void { const config = ALLOWED_COMMANDS[scriptName]; if (!config) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: `Unknown command: ${scriptName}` })); return; } // Set headers for streaming response res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Transfer-Encoding": "chunked", "Cache-Control": "no-cache", "Connection": "keep-alive", }); // Write initial message res.write(`> npm run ${config.script}\n\n`); // Spawn the npm process const child = spawn("npm", ["run", config.script], { cwd: process.cwd(), env: { ...process.env, ...config.env }, shell: true, }); // Stream stdout child.stdout.on("data", (data: Buffer) => { res.write(data.toString()); }); // Stream stderr child.stderr.on("data", (data: Buffer) => { res.write(data.toString()); }); // Handle process completion child.on("close", (code) => { if (code === 0) { res.write(`\n[Done] Command completed successfully.\n`); } else { res.write(`\n[Error] Command exited with code ${code}.\n`); } res.end(); }); // Handle process errors child.on("error", (err) => { res.write(`\n[Error] Failed to execute command: ${err.message}\n`); res.end(); }); } // Main request handler async function handleRequest( req: http.IncomingMessage, res: http.ServerResponse, ): Promise { setCorsHeaders(res); // Handle preflight OPTIONS request if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; } const url = new URL(req.url || "/", `http://${HOST}:${PORT}`); const path = url.pathname; // Health check endpoint if (path === "/health" && req.method === "GET") { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ status: "ok", commands: Object.keys(ALLOWED_COMMANDS) })); return; } // Execute sync command if (path === "/api/sync" && req.method === "POST") { // Verify authentication if (!verifyAuth(req)) { res.writeHead(401, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Unauthorized" })); return; } try { const body = await parseBody(req); const command = body.command as string; if (!command || !ALLOWED_COMMANDS[command]) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Invalid command", allowed: Object.keys(ALLOWED_COMMANDS) })); return; } executeScript(command, res); } catch (error) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Invalid request body" })); } return; } // 404 for other routes res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Not found" })); } // Create and start server const server = http.createServer((req, res) => { handleRequest(req, res).catch((error) => { console.error("Request error:", error); if (!res.headersSent) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Internal server error" })); } }); }); server.listen(PORT, HOST, () => { console.log(`\n Sync Server running at http://${HOST}:${PORT}`); console.log(`\n Available commands:`); Object.keys(ALLOWED_COMMANDS).forEach((cmd) => { console.log(` - ${cmd}`); }); if (SYNC_TOKEN) { console.log(`\n Token authentication: enabled`); } else { console.log(`\n Token authentication: disabled (set SYNC_TOKEN in .env.local to enable)`); } console.log(`\n Use with Dashboard at http://localhost:5173/dashboard\n`); }); // Handle graceful shutdown process.on("SIGINT", () => { console.log("\n Shutting down sync server..."); server.close(() => { process.exit(0); }); });