import { Agent } from "node:http"; import { fileURLToPath, URL } from "node:url"; import vue from "@vitejs/plugin-vue"; import { defineConfig, loadEnv, type Plugin, type ProxyOptions } from "vite"; export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ""); const nativeApiUrl = String(process.env.WORKBENCH_NATIVE_API_URL ?? env.WORKBENCH_NATIVE_API_URL ?? "").trim(); const nativeRuntimeConfig = String(process.env.WORKBENCH_WEB_RUNTIME_CONFIG ?? env.WORKBENCH_WEB_RUNTIME_CONFIG ?? "").trim(); const nativeCaseRunEnabled = (process.env.HWLAB_CASERUN_NATIVE_TEST ?? env.HWLAB_CASERUN_NATIVE_TEST) === "1"; const nativeCaseRunTarget = String(process.env.HWLAB_CASERUN_NATIVE_URL ?? env.HWLAB_CASERUN_NATIVE_URL ?? "").trim(); const nativeCaseRunAgent = new Agent({ keepAlive: true }); if (nativeApiUrl && nativeCaseRunEnabled) { throw new Error("Workbench and CaseRun native modes cannot share one Vite server"); } if (nativeCaseRunEnabled && !nativeCaseRunTarget) throw new Error("HWLAB_CASERUN_NATIVE_URL is required from the CLI-managed YAML profile in CaseRun native mode"); if (nativeApiUrl && !nativeRuntimeConfig) throw new Error("WORKBENCH_WEB_RUNTIME_CONFIG is required in Workbench native mode"); const proxy: Record | undefined = nativeCaseRunEnabled ? { "/auth": { target: nativeCaseRunTarget, changeOrigin: false, agent: nativeCaseRunAgent }, "/v1/caserun": { target: nativeCaseRunTarget, changeOrigin: false, agent: nativeCaseRunAgent } } : nativeApiUrl ? { "/v1": { target: nativeApiUrl, changeOrigin: false }, "/auth": { target: nativeApiUrl, changeOrigin: false }, "/health": { target: nativeApiUrl, changeOrigin: false } } : undefined; return { cacheDir: nativeViteCacheDir({ nativeApiUrl, nativeCaseRunEnabled }), plugins: [ ...(nativeApiUrl ? [nativeRuntimeConfigPlugin(nativeRuntimeConfig)] : []), ...(nativeCaseRunEnabled ? [nativeCaseRunRuntimeConfigPlugin()] : []), vue() ], resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) } }, server: { host: env.WORKBENCH_WEB_HOST || "127.0.0.1", port: Number(env.WORKBENCH_WEB_PORT || 5173), strictPort: true, watch: nativeCaseRunEnabled || env.WORKBENCH_VITE_USE_POLLING === "1" ? { usePolling: true, interval: nativeCaseRunEnabled ? 300 : 500 } : undefined, proxy }, build: { outDir: "dist", emptyOutDir: true, sourcemap: false, rollupOptions: { output: { entryFileNames: "app.js", chunkFileNames: "assets/[name].js", assetFileNames: "assets/[name][extname]" } } } }; }); export function nativeViteCacheDir(input: { nativeApiUrl: string; nativeCaseRunEnabled: boolean }): string { const runtime = input.nativeApiUrl ? "workbench" : input.nativeCaseRunEnabled ? "caserun" : "cloud-web"; return `.state/vite/${runtime}`; } function nativeRuntimeConfigPlugin(serialized: string): Plugin { const config = parseWorkbenchNativeRuntimeConfig(serialized); const inlineJson = JSON.stringify(config).replace(/ { if (request.url !== "/v1/web-performance" || request.method !== "POST") return next(); response.writeHead(204, { "cache-control": "no-store" }); response.end(); }); }, transformIndexHtml() { return [{ tag: "script", injectTo: "head-prepend", children: `window.HWLAB_CLOUD_WEB_CONFIG = { displayTime: { timeZone: "Asia/Shanghai", locale: "zh-CN", label: "北京时间" }, workbench: { realtimeFeatures: { liveKafkaSse: false, kafkaRefreshReplay: false, projectionRealtime: true }, debugCapabilities: { isolatedKafka: false, rawHwlabEventWindow: { enabled: false, maxEntries: 1, maxRetainedBytes: 1 } }, traceTimeline: { autoExpandRunning: false, autoCollapseTerminal: false }, runtimePolicy: {} } };` }]; } }; } export function parseWorkbenchNativeRuntimeConfig(serialized: string): Record { let value: unknown; try { value = JSON.parse(serialized); } catch { throw new Error("WORKBENCH_WEB_RUNTIME_CONFIG must be valid JSON"); } const config = record(value, "WORKBENCH_WEB_RUNTIME_CONFIG"); const displayTime = record(config.displayTime, "WORKBENCH_WEB_RUNTIME_CONFIG.displayTime"); requiredString(displayTime.timeZone, "displayTime.timeZone"); requiredString(displayTime.locale, "displayTime.locale"); requiredString(displayTime.label, "displayTime.label"); const workbench = record(config.workbench, "WORKBENCH_WEB_RUNTIME_CONFIG.workbench"); const realtime = record(workbench.realtimeFeatures, "workbench.realtimeFeatures"); requiredBoolean(realtime.liveKafkaSse, "workbench.realtimeFeatures.liveKafkaSse"); requiredBoolean(realtime.kafkaRefreshReplay, "workbench.realtimeFeatures.kafkaRefreshReplay"); requiredBoolean(realtime.projectionRealtime, "workbench.realtimeFeatures.projectionRealtime"); const debug = record(workbench.debugCapabilities, "workbench.debugCapabilities"); requiredBoolean(debug.isolatedKafka, "workbench.debugCapabilities.isolatedKafka"); const rawWindow = record(debug.rawHwlabEventWindow, "workbench.debugCapabilities.rawHwlabEventWindow"); requiredBoolean(rawWindow.enabled, "workbench.debugCapabilities.rawHwlabEventWindow.enabled"); requiredPositiveInteger(rawWindow.maxEntries, "workbench.debugCapabilities.rawHwlabEventWindow.maxEntries"); requiredPositiveInteger(rawWindow.maxRetainedBytes, "workbench.debugCapabilities.rawHwlabEventWindow.maxRetainedBytes"); const traceTimeline = record(workbench.traceTimeline, "workbench.traceTimeline"); requiredBoolean(traceTimeline.autoExpandRunning, "workbench.traceTimeline.autoExpandRunning"); requiredBoolean(traceTimeline.autoCollapseTerminal, "workbench.traceTimeline.autoCollapseTerminal"); return config; } function record(value: unknown, path: string): Record { if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${path} must be an object`); return value as Record; } function requiredString(value: unknown, path: string): void { if (typeof value !== "string" || !value.trim()) throw new Error(`${path} must be a non-empty string`); } function requiredBoolean(value: unknown, path: string): void { if (typeof value !== "boolean") throw new Error(`${path} must be a boolean`); } function requiredPositiveInteger(value: unknown, path: string): void { if (!Number.isInteger(value) || Number(value) <= 0) throw new Error(`${path} must be a positive integer`); }