import { rm, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; import { defineConfig, loadEnv, type Plugin, type ResolvedConfig } from "vite"; import { generateNodeLocations, type NodeLocationBuildOptions } from "./build/node-locations.ts"; type SiteVariant = "com" | "cn"; const SITE_CONFIG = { com: { origin: "https://www.zhouhaoze.com", lang: "en", locale: "en_US", alternateLocale: "zh_CN", title: "ZaneChow | Computer Science Student & Developer", description: "ZaneChow is a Computer Science undergraduate at Universiti Malaya building software, self-hosted infrastructure, and open-source projects.", imageAlt: "ZaneChow personal website", }, cn: { origin: "https://www.zhouhaoze.cn", lang: "zh-CN", locale: "zh_CN", alternateLocale: "en_US", title: "周昊泽 | 计算机科学学生与开发者", description: "周昊泽是马来亚大学计算机科学本科生,专注于软件开发、自建基础设施和开源项目。", imageAlt: "周昊泽个人网站", }, } as const; function resolveVariant(value: string | undefined): SiteVariant { const variant = value?.toLowerCase() || "com"; if (variant !== "com" && variant !== "cn") { throw new Error(`SITE_VARIANT must be "com" or "cn"; received "${value}".`); } return variant; } function siteAssetsPlugin(variant: SiteVariant, nodeLocationOptions: NodeLocationBuildOptions): Plugin { const site = SITE_CONFIG[variant]; const siteUrl = `${site.origin}/`; let resolvedConfig: ResolvedConfig; const robots = `User-agent: *\nAllow: /\n\nSitemap: ${siteUrl}sitemap.xml\n`; const sitemap = ` ${siteUrl} 2026-08-20 `; return { name: "site-variant-assets", configResolved(config) { resolvedConfig = config; }, transformIndexHtml(html) { const replacements: Record = { SITE_LANG: site.lang, SITE_TITLE: site.title, SITE_DESCRIPTION: site.description, SITE_URL: siteUrl, SITE_LOCALE: site.locale, SITE_ALTERNATE_LOCALE: site.alternateLocale, SITE_IMAGE_ALT: site.imageAlt, }; return Object.entries(replacements).reduce( (result, [key, value]) => result.replaceAll(`%${key}%`, value), html, ); }, configureServer(server) { server.middlewares.use((request, response, next) => { if (request.url === "/robots.txt") { response.setHeader("Content-Type", "text/plain; charset=utf-8"); response.end(robots); return; } if (request.url === "/sitemap.xml") { response.setHeader("Content-Type", "application/xml; charset=utf-8"); response.end(sitemap); return; } next(); }); }, async closeBundle() { const outDir = resolve(resolvedConfig.root, resolvedConfig.build.outDir); let nodeLocations; try { nodeLocations = await generateNodeLocations(nodeLocationOptions); console.info( `[node-locations] Resolved ${nodeLocations.locations.length}/${nodeLocations.nodes.length} precise node locations.`, ); } catch (error) { console.warn(`[node-locations] ${error instanceof Error ? error.message : String(error)}`); nodeLocations = { version: 1, generatedAt: null, nodes: [], locations: [] } as const; } await Promise.all([ writeFile(resolve(outDir, "robots.txt"), robots, "utf8"), writeFile(resolve(outDir, "sitemap.xml"), sitemap, "utf8"), rm(resolve(outDir, "sitemap-cn.xml"), { force: true }), writeFile(resolve(outDir, "node-locations.json"), `${JSON.stringify(nodeLocations)}\n`, "utf8"), ]); }, }; } export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ""); const variant = resolveVariant(process.env.SITE_VARIANT ?? env.SITE_VARIANT); const nodeLocationOptions = { komariBaseUrl: process.env.KOMARI_BASE_URL ?? env.KOMARI_BASE_URL ?? "https://ops.zhouhaoze.top", komariApiKey: process.env.KOMARI_API_KEY ?? env.KOMARI_API_KEY, }; return { define: { __SITE_VARIANT__: JSON.stringify(variant), }, plugins: [siteAssetsPlugin(variant, nodeLocationOptions)], }; });