RustRover cargo-generate template: Axum + Vue 3 + TypeScript + Tailwind + sqlx
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import { spawnSync } from "node:child_process";
  2. import fs from "node:fs";
  3. import path from "node:path";
  4. import { fileURLToPath } from "node:url";
  5. import tailwindcss from "@tailwindcss/vite";
  6. import vue from "@vitejs/plugin-vue";
  7. import { parse } from "smol-toml";
  8. import { defineConfig, type Plugin } from "vite";
  9. const webRoot = path.dirname(fileURLToPath(import.meta.url));
  10. const workspaceRoot = path.resolve(webRoot, "../..");
  11. type TomlTable = Record<string, unknown>;
  12. function isTable(value: unknown): value is TomlTable {
  13. return typeof value === "object" && value !== null && !Array.isArray(value);
  14. }
  15. function deepMerge(base: TomlTable, overlay: TomlTable): TomlTable {
  16. const next: TomlTable = { ...base };
  17. for (const [key, value] of Object.entries(overlay)) {
  18. const existing = next[key];
  19. if (isTable(existing) && isTable(value)) {
  20. next[key] = deepMerge(existing, value);
  21. } else {
  22. next[key] = value;
  23. }
  24. }
  25. return next;
  26. }
  27. function readToml(filePath: string): TomlTable {
  28. if (!fs.existsSync(filePath)) {
  29. return {};
  30. }
  31. return parse(fs.readFileSync(filePath, "utf8")) as TomlTable;
  32. }
  33. /**
  34. * Same load order as `AppConfig::load` in the Rust server:
  35. * default.toml → {APP_ENV}.toml. Env overrides stay on the server.
  36. */
  37. function loadAppConfig(): TomlTable {
  38. const configDir = process.env.APP_CONFIG_DIR
  39. ? path.resolve(process.env.APP_CONFIG_DIR)
  40. : path.join(workspaceRoot, "config");
  41. const env = process.env.APP_ENV ?? "development";
  42. return deepMerge(
  43. readToml(path.join(configDir, "default.toml")),
  44. readToml(path.join(configDir, `${env}.toml`)),
  45. );
  46. }
  47. function section(config: TomlTable, name: string): TomlTable {
  48. const value = config[name];
  49. return isTable(value) ? value : {};
  50. }
  51. function generateApiFromSpec(): void {
  52. const script = path.join(workspaceRoot, "scripts/generate-api.mjs");
  53. const spec = path.join(workspaceRoot, "packages/contracts/openapi.json");
  54. if (!fs.existsSync(script) || !fs.existsSync(spec)) {
  55. return;
  56. }
  57. const result = spawnSync(process.execPath, [script], {
  58. cwd: workspaceRoot,
  59. stdio: "inherit",
  60. });
  61. if (result.status !== 0) {
  62. throw new Error("OpenAPI client generation failed");
  63. }
  64. }
  65. function openapiClientPlugin(): Plugin {
  66. const specPath = path.join(workspaceRoot, "packages/contracts/openapi.json");
  67. return {
  68. name: "openapi-client",
  69. buildStart() {
  70. generateApiFromSpec();
  71. this.addWatchFile(specPath);
  72. },
  73. handleHotUpdate(ctx) {
  74. if (ctx.file === specPath) {
  75. generateApiFromSpec();
  76. }
  77. },
  78. };
  79. }
  80. const appConfig = loadAppConfig();
  81. const server = section(appConfig, "server");
  82. const frontend = section(appConfig, "frontend");
  83. const backendHost = String(server.host ?? "127.0.0.1");
  84. const backendPort = Number(server.port ?? 8080);
  85. const frontendHost = String(frontend.host ?? "127.0.0.1");
  86. const frontendPort = Number(frontend.port ?? 5173);
  87. const backendOrigin = `http://${backendHost}:${backendPort}`;
  88. export default defineConfig({
  89. plugins: [vue(), tailwindcss(), openapiClientPlugin()],
  90. resolve: {
  91. alias: {
  92. "@": path.join(webRoot, "src"),
  93. },
  94. },
  95. server: {
  96. host: frontendHost,
  97. port: frontendPort,
  98. strictPort: true,
  99. proxy: {
  100. "/api": { target: backendOrigin, changeOrigin: true },
  101. "/health": { target: backendOrigin, changeOrigin: true },
  102. },
  103. },
  104. preview: {
  105. host: frontendHost,
  106. port: frontendPort,
  107. proxy: {
  108. "/api": { target: backendOrigin, changeOrigin: true },
  109. "/health": { target: backendOrigin, changeOrigin: true },
  110. },
  111. },
  112. });