import { cpSync, existsSync, readdirSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { defineConfig } from 'vite'; const root = path.dirname(fileURLToPath(import.meta.url)); const excludedDirs = new Set(['.git', 'dist', 'node_modules']); const rodaDashboardUrl = 'https://dashboard.informatika.untan.ac.id/index.php?penelitian'; const rodaDashboardCacheTtlMs = 5 * 60 * 1000; const dashboardRequestHeaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36', }; let rodaDashboardHtmlCache; let rodaDashboardDevCache = { html: '', timestamp: 0 }; function collectHtmlInputs(dir) { const inputs = {}; const entries = readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { if (!excludedDirs.has(entry.name)) { Object.assign(inputs, collectHtmlInputs(fullPath)); } continue; } if (entry.isFile() && entry.name.endsWith('.html')) { const relativePath = path.relative(root, fullPath); const inputName = relativePath .replace(/\.html$/, '') .replace(/[^a-zA-Z0-9_-]+/g, '_'); inputs[inputName] = fullPath; } } return inputs; } function copyStaticAssets() { return { name: 'copy-static-assets', closeBundle() { const source = path.join(root, 'assets'); const destination = path.join(root, 'dist', 'assets'); if (existsSync(source)) { cpSync(source, destination, { recursive: true }); } }, }; } async function requestRodaDashboardHtml() { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 12000); try { const response = await fetch(rodaDashboardUrl, { signal: controller.signal, headers: dashboardRequestHeaders, }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return response.text(); } finally { clearTimeout(timeoutId); } } async function fetchRodaDashboardHtml() { if (rodaDashboardHtmlCache !== undefined) { return rodaDashboardHtmlCache; } try { rodaDashboardHtmlCache = await requestRodaDashboardHtml(); } catch (error) { const message = error instanceof Error ? error.message : String(error); console.warn(`[roda-dashboard] Build-time dashboard fetch skipped: ${message}`); rodaDashboardHtmlCache = ''; } return rodaDashboardHtmlCache; } function escapeJsonScriptContent(value) { return JSON.stringify(value) .replaceAll('<', '\\u003c') .replaceAll('>', '\\u003e') .replaceAll('&', '\\u0026') .replaceAll('\u2028', '\\u2028') .replaceAll('\u2029', '\\u2029'); } function embedRodaDashboardData() { let command = 'serve'; return { name: 'embed-roda-dashboard-data', configResolved(config) { command = config.command; }, async transformIndexHtml(html, context) { const filename = context?.filename ? path.resolve(context.filename) : ''; const rodaIndex = path.join(root, 'groups', 'RODA', 'index.html'); if (filename !== rodaIndex || command !== 'build') { return html.replace('', ''); } const dashboardHtml = await fetchRodaDashboardHtml(); const payload = ``; return html.replace('', payload); }, }; } function serveRodaDashboardApi() { return { name: 'serve-roda-dashboard-api', configureServer(server) { server.middlewares.use(async (req, res, next) => { const requestUrl = new URL(req.url || '/', 'http://localhost'); if (requestUrl.pathname !== '/api/dashboard') { next(); return; } if (req.method !== 'GET' && req.method !== 'HEAD') { res.writeHead(405, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('METHOD_NOT_ALLOWED'); return; } const now = Date.now(); try { let html = rodaDashboardDevCache.html; let cacheStatus = 'HIT'; if (!html || (now - rodaDashboardDevCache.timestamp) >= rodaDashboardCacheTtlMs) { html = await requestRodaDashboardHtml(); rodaDashboardDevCache = { html, timestamp: Date.now() }; cacheStatus = 'MISS'; } res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'X-Cache': cacheStatus, }); res.end(req.method === 'HEAD' ? '' : html); } catch (error) { if (rodaDashboardDevCache.html) { res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'X-Cache': 'STALE', }); res.end(req.method === 'HEAD' ? '' : rodaDashboardDevCache.html); return; } const message = error instanceof Error ? error.message : String(error); console.warn(`[roda-dashboard] Dev dashboard fetch failed: ${message}`); res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('DASHBOARD_FETCH_FAILED'); } }); }, }; } export default defineConfig({ plugins: [serveRodaDashboardApi(), embedRodaDashboardData(), copyStaticAssets()], build: { rollupOptions: { input: collectHtmlInputs(root), }, }, });