import { jsxLocPlugin } from "@builder.io/vite-plugin-jsx-loc"; import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; import fs from "node:fs"; import path from "node:path"; import { defineConfig, type Plugin, type ViteDevServer } from "vite"; import { vitePluginManusRuntime } from "vite-plugin-manus-runtime"; // ============================================================================= // Manus Debug Collector - Vite Plugin // Writes browser logs directly to files, trimmed when exceeding size limit // ============================================================================= const PROJECT_ROOT = import.meta.dirname; const LOG_DIR = path.join(PROJECT_ROOT, ".manus-logs"); const MAX_LOG_SIZE_BYTES = 1 * 1024 * 1024; // 1MB per log file const TRIM_TARGET_BYTES = Math.floor(MAX_LOG_SIZE_BYTES * 0.6); // Trim to 60% to avoid constant re-trimming type LogSource = "browserConsole" | "networkRequests" | "sessionReplay"; function ensureLogDir() { if (!fs.existsSync(LOG_DIR)) { fs.mkdirSync(LOG_DIR, { recursive: true }); } } function trimLogFile(logPath: string, maxSize: number) { try { if (!fs.existsSync(logPath) || fs.statSync(logPath).size <= maxSize) { return; } const lines = fs.readFileSync(logPath, "utf-8").split("\n"); const keptLines: string[] = []; let keptBytes = 0; // Keep newest lines (from end) that fit within 60% of maxSize const targetSize = TRIM_TARGET_BYTES; for (let i = lines.length - 1; i >= 0; i--) { const lineBytes = Buffer.byteLength(`${lines[i]}\n`, "utf-8"); if (keptBytes + lineBytes > targetSize) break; keptLines.unshift(lines[i]); keptBytes += lineBytes; } fs.writeFileSync(logPath, keptLines.join("\n"), "utf-8"); } catch { /* ignore trim errors */ } } function writeToLogFile(source: LogSource, entries: unknown[]) { if (entries.length === 0) return; ensureLogDir(); const logPath = path.join(LOG_DIR, `${source}.log`); // Format entries with timestamps const lines = entries.map((entry) => { const ts = new Date().toISOString(); return `[${ts}] ${JSON.stringify(entry)}`; }); // Append to log file fs.appendFileSync(logPath, `${lines.join("\n")}\n`, "utf-8"); // Trim if exceeds max size trimLogFile(logPath, MAX_LOG_SIZE_BYTES); } /** * Vite plugin to collect browser debug logs * - POST /__manus__/logs: Browser sends logs, written directly to files * - Files: browserConsole.log, networkRequests.log, sessionReplay.log * - Auto-trimmed when exceeding 1MB (keeps newest entries) */ function vitePluginManusDebugCollector(): Plugin { return { name: "manus-debug-collector", transformIndexHtml(html) { if (process.env.NODE_ENV === "production") { return html; } return { html, tags: [ { tag: "script", attrs: { src: "/__manus__/debug-collector.js", defer: true, }, injectTo: "head", }, ], }; }, configureServer(server: ViteDevServer) { // POST /__manus__/logs: Browser sends logs (written directly to files) server.middlewares.use("/__manus__/logs", (req, res, next) => { if (req.method !== "POST") { return next(); } const handlePayload = (payload: any) => { // Write logs directly to files if (payload.consoleLogs?.length > 0) { writeToLogFile("browserConsole", payload.consoleLogs); } if (payload.networkRequests?.length > 0) { writeToLogFile("networkRequests", payload.networkRequests); } if (payload.sessionEvents?.length > 0) { writeToLogFile("sessionReplay", payload.sessionEvents); } res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: true })); }; const reqBody = (req as { body?: unknown }).body; if (reqBody && typeof reqBody === "object") { try { handlePayload(reqBody); } catch (e) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: false, error: String(e) })); } return; } let body = ""; req.on("data", (chunk) => { body += chunk.toString(); }); req.on("end", () => { try { const payload = JSON.parse(body); handlePayload(payload); } catch (e) { res.writeHead(400, { "Content-Type": "application/json" }); res.end(JSON.stringify({ success: false, error: String(e) })); } }); }); }, }; } const plugins = [react(), tailwindcss(), jsxLocPlugin(), vitePluginManusRuntime(), vitePluginManusDebugCollector()]; export default defineConfig({ plugins, resolve: { alias: { "@": path.resolve(import.meta.dirname, "src"), "@shared": path.resolve(import.meta.dirname, "shared"), }, }, build: { outDir: path.resolve(import.meta.dirname, "dist"), emptyOutDir: true, }, server: { host: true, allowedHosts: [ ".manuspre.computer", ".manus.computer", ".manus-asia.computer", ".manuscomputer.ai", ".manusvm.computer", "localhost", "127.0.0.1", ], fs: { strict: true, deny: ["**/.*"], }, proxy: { // Proxy API requests to Django backend '/api': { target: 'http://localhost:8000', changeOrigin: true, }, // Proxy media files '/media': { target: 'http://localhost:8000', changeOrigin: true, }, // Proxy Stripe webhooks '/webhooks': { target: 'http://localhost:8000', changeOrigin: true, }, }, }, });