import puppeteer from "puppeteer"; import { createServer } from "http"; import { readFileSync, existsSync } from "fs"; import { join, dirname, extname } from "path"; import { fileURLToPath } from "url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const root = join(__dirname, ".."); const distDir = join(root, "dist"); const PORT = 5174; async function startServer() { const httpServer = createServer((req, res) => { const decodedUrl = decodeURIComponent(req.url || "/"); let filePath = join(distDir, decodedUrl === "/" ? "index.html" : decodedUrl); if (!existsSync(filePath) || extname(filePath) === "") { filePath = join(distDir, "index.html"); } if (existsSync(filePath)) { const content = readFileSync(filePath); const ext = extname(filePath); const contentType = ext === ".html" ? "text/html" : ext === ".js" ? "application/javascript" : ext === ".css" ? "text/css" : ext === ".json" ? "application/json" : ext === ".png" ? "image/png" : ext === ".jpg" || ext === ".jpeg" ? "image/jpeg" : "application/octet-stream"; res.writeHead(200, { "Content-Type": contentType }); res.end(content); } else { res.writeHead(404); res.end("Not found"); } }); await new Promise((resolve, reject) => { httpServer.listen(PORT, () => { console.log(`Preview server running on http://localhost:${PORT}`); resolve(); }); httpServer.on("error", reject); }); return httpServer; } async function generatePreview(outputPath, uri, selector) { const browser = await puppeteer.launch({ headless: true, args: ["--no-sandbox", "--disable-setuid-sandbox"] }); try { const page = await browser.newPage(); await page.setViewport({ width: 1400, height: 2000, deviceScaleFactor: 2 }); await page.goto(`http://localhost:${PORT}${uri}`, { waitUntil: "networkidle0" }); await new Promise((resolve) => setTimeout(resolve, 1000)); await page.waitForSelector(selector, { timeout: 10000 }); // Wait for images to load await page.evaluate(() => { return Promise.all( Array.from(document.images).map((img) => { if (img.complete) return Promise.resolve(); return new Promise((resolve) => { img.onload = resolve; img.onerror = resolve; setTimeout(resolve, 3000); }); }) ); }); // For tank page, ensure stage renders at full size and wait for layers if (uri === "/") { await page.evaluate(() => { const layout = document.querySelector(".layout"); if (layout) { layout.style.width = "1400px"; layout.style.minWidth = "1200px"; } const stage = document.querySelector(".stage"); if (stage) { stage.style.flex = "1 1 1200px"; stage.style.width = "1200px"; stage.style.maxWidth = "1200px"; } }); await new Promise((resolve) => setTimeout(resolve, 500)); // Wait for liquid layers to appear and have height await page.waitForFunction( () => { const layers = document.querySelectorAll(".liquid-layer"); return layers.length > 0 && Array.from(layers).some(l => l.offsetHeight > 0); }, { timeout: 10000 } ); } await new Promise((resolve) => setTimeout(resolve, 500)); const element = await page.$(selector); if (element) { await element.screenshot({ path: outputPath, type: "png" }); console.log(`✓ Generated preview: ${outputPath}`); } } finally { await browser.close(); } } export async function generateClonesPreview() { if (!existsSync(distDir) || !existsSync(join(distDir, "index.html"))) { return; } let httpServer; try { httpServer = await startServer(); await generatePreview( join(distDir, "vixiclones-preview.png"), "/vixiclones", ".clones-rows" ); await generatePreview( join(distDir, "main-preview.png"), "/", ".stage" ); } catch (error) { console.error("Failed to generate previews:", error); throw error; } finally { if (httpServer) { await new Promise((resolve) => { httpServer.close(() => resolve()); }); } } }