|
- import { spawnSync } from "node:child_process";
- import fs from "node:fs";
- import path from "node:path";
- import { fileURLToPath } from "node:url";
-
- import tailwindcss from "@tailwindcss/vite";
- import vue from "@vitejs/plugin-vue";
- import { parse } from "smol-toml";
- import { defineConfig, type Plugin } from "vite";
-
- const webRoot = path.dirname(fileURLToPath(import.meta.url));
- const workspaceRoot = path.resolve(webRoot, "../..");
-
- type TomlTable = Record<string, unknown>;
-
- function isTable(value: unknown): value is TomlTable {
- return typeof value === "object" && value !== null && !Array.isArray(value);
- }
-
- function deepMerge(base: TomlTable, overlay: TomlTable): TomlTable {
- const next: TomlTable = { ...base };
- for (const [key, value] of Object.entries(overlay)) {
- const existing = next[key];
- if (isTable(existing) && isTable(value)) {
- next[key] = deepMerge(existing, value);
- } else {
- next[key] = value;
- }
- }
- return next;
- }
-
- function readToml(filePath: string): TomlTable {
- if (!fs.existsSync(filePath)) {
- return {};
- }
- return parse(fs.readFileSync(filePath, "utf8")) as TomlTable;
- }
-
- /**
- * Same load order as `AppConfig::load` in the Rust server:
- * default.toml → {APP_ENV}.toml. Env overrides stay on the server.
- */
- function loadAppConfig(): TomlTable {
- const configDir = process.env.APP_CONFIG_DIR
- ? path.resolve(process.env.APP_CONFIG_DIR)
- : path.join(workspaceRoot, "config");
- const env = process.env.APP_ENV ?? "development";
- return deepMerge(
- readToml(path.join(configDir, "default.toml")),
- readToml(path.join(configDir, `${env}.toml`)),
- );
- }
-
- function section(config: TomlTable, name: string): TomlTable {
- const value = config[name];
- return isTable(value) ? value : {};
- }
-
- function generateApiFromSpec(): void {
- const script = path.join(workspaceRoot, "scripts/generate-api.mjs");
- const spec = path.join(workspaceRoot, "packages/contracts/openapi.json");
- if (!fs.existsSync(script) || !fs.existsSync(spec)) {
- return;
- }
- const result = spawnSync(process.execPath, [script], {
- cwd: workspaceRoot,
- stdio: "inherit",
- });
- if (result.status !== 0) {
- throw new Error("OpenAPI client generation failed");
- }
- }
-
- function openapiClientPlugin(): Plugin {
- const specPath = path.join(workspaceRoot, "packages/contracts/openapi.json");
- return {
- name: "openapi-client",
- buildStart() {
- generateApiFromSpec();
- this.addWatchFile(specPath);
- },
- handleHotUpdate(ctx) {
- if (ctx.file === specPath) {
- generateApiFromSpec();
- }
- },
- };
- }
-
- const appConfig = loadAppConfig();
- const server = section(appConfig, "server");
- const frontend = section(appConfig, "frontend");
-
- const backendHost = String(server.host ?? "127.0.0.1");
- const backendPort = Number(server.port ?? 8080);
- const frontendHost = String(frontend.host ?? "127.0.0.1");
- const frontendPort = Number(frontend.port ?? 5173);
- const backendOrigin = `http://${backendHost}:${backendPort}`;
-
- export default defineConfig({
- plugins: [vue(), tailwindcss(), openapiClientPlugin()],
- resolve: {
- alias: {
- "@": path.join(webRoot, "src"),
- },
- },
- server: {
- host: frontendHost,
- port: frontendPort,
- strictPort: true,
- proxy: {
- "/api": { target: backendOrigin, changeOrigin: true },
- "/health": { target: backendOrigin, changeOrigin: true },
- },
- },
- preview: {
- host: frontendHost,
- port: frontendPort,
- proxy: {
- "/api": { target: backendOrigin, changeOrigin: true },
- "/health": { target: backendOrigin, changeOrigin: true },
- },
- },
- });
|