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,
ipinfoToken: process.env.IPINFO_TOKEN ?? env.IPINFO_TOKEN,
};
return {
define: {
__SITE_VARIANT__: JSON.stringify(variant),
},
plugins: [siteAssetsPlugin(variant, nodeLocationOptions)],
};
});