diff --git a/ui/frontend/vite.config.ts b/ui/frontend/vite.config.ts index 8fd91fa..21b438f 100644 --- a/ui/frontend/vite.config.ts +++ b/ui/frontend/vite.config.ts @@ -1,5 +1,5 @@ import { sveltekit } from "@sveltejs/kit/vite"; -import { defineConfig } from "vite"; +import { defineConfig, loadEnv } from "vite"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; @@ -10,43 +10,84 @@ const pkg = JSON.parse( ), ) as { version: string }; -// Default upstream gateway addresses used by the dev proxy. Override -// by pointing `VITE_DEV_PROXY_TARGET` (REST surface) and -// `VITE_DEV_GRPC_PROXY_TARGET` (Connect-Web surface) at a different -// gateway when working with a remote stack instead of -// `tools/local-dev/`. In production the two surfaces sit behind a -// single host; the split here exists only because local-dev runs the -// REST listener on :8080 and the authenticated Connect-Web listener -// on :9090. -const DEV_PROXY_TARGET = - process.env.VITE_DEV_PROXY_TARGET ?? "http://localhost:8080"; -const DEV_GRPC_PROXY_TARGET = - process.env.VITE_DEV_GRPC_PROXY_TARGET ?? "http://localhost:9090"; +// Parse the VITE_DEV_HOST override into the shape `server.host` +// expects. `""` / `"false"` keeps Vite's safe default (loopback +// only); `"true"` / `"1"` flips to "all interfaces" (`0.0.0.0` plus +// IPv6); any other string is passed through verbatim so a developer +// can pin a single LAN address (e.g. `"192.168.1.5"`). Returning +// `undefined` lets Vite stay on its built-in default. +function parseDevHost(raw: string | undefined): string | boolean | undefined { + if (raw === undefined || raw === "") return undefined; + const normalised = raw.toLowerCase(); + if (normalised === "true" || normalised === "1" || normalised === "yes") { + return true; + } + if (normalised === "false" || normalised === "0" || normalised === "no") { + return false; + } + return raw; +} -export default defineConfig({ - plugins: [sveltekit()], - define: { - __APP_VERSION__: JSON.stringify(pkg.version), - }, - server: { - // Same-origin proxy so the browser sees only `localhost:5173` - // and never trips a cross-origin preflight against the - // gateway's REST + Connect-Web surfaces. Production deployments - // serve the UI and the gateway behind a single host, so the - // proxy is purely a dev-time convenience. - proxy: { - "/api": { - target: DEV_PROXY_TARGET, - changeOrigin: false, - }, - "/galaxy.gateway.v1.EdgeGateway": { - target: DEV_GRPC_PROXY_TARGET, - changeOrigin: false, - // Connect-Web server-streaming (`SubscribeEvents`) uses - // chunked HTTP responses; http-proxy passes them through - // transparently as long as buffering stays off, which is - // the default. +export default defineConfig(({ mode }) => { + // `loadEnv("", ...)` matches every `.env*` entry regardless of + // the customary `VITE_` prefix so the config sees the same view + // that client code sees via `import.meta.env`. Without this + // `process.env` would carry only the shell's exports, and + // per-developer files like `.env.development.local` would + // silently miss the config — every override would have to be + // passed on the `pnpm dev` command line. + const env = loadEnv(mode, process.cwd(), ""); + + // Default upstream gateway addresses used by the dev proxy. + // Override by pointing `VITE_DEV_PROXY_TARGET` (REST surface) + // and `VITE_DEV_GRPC_PROXY_TARGET` (Connect-Web surface) at a + // different gateway when working with a remote stack instead of + // `tools/local-dev/`. In production the two surfaces sit behind + // a single host; the split here exists only because local-dev + // runs the REST listener on :8080 and the authenticated + // Connect-Web listener on :9090. + const DEV_PROXY_TARGET = + env.VITE_DEV_PROXY_TARGET || "http://localhost:8080"; + const DEV_GRPC_PROXY_TARGET = + env.VITE_DEV_GRPC_PROXY_TARGET || "http://localhost:9090"; + + // `VITE_DEV_HOST` opts the dev server into wider listener + // binding. Default stays at Vite's safe loopback-only behaviour + // so an unattended `pnpm dev` on someone's laptop never exposes + // the unauthenticated dev surface to the LAN by accident. Set + // the value in `.env.development.local` (untracked) when + // reaching the server through SSH port forwarding, a VM, or a + // container needs a non-loopback bind. + const devHost = parseDevHost(env.VITE_DEV_HOST); + + return { + plugins: [sveltekit()], + define: { + __APP_VERSION__: JSON.stringify(pkg.version), + }, + server: { + ...(devHost !== undefined ? { host: devHost } : {}), + // Same-origin proxy so the browser sees only + // `localhost:5173` and never trips a cross-origin + // preflight against the gateway's REST + Connect-Web + // surfaces. Production deployments serve the UI and the + // gateway behind a single host, so the proxy is purely a + // dev-time convenience. + proxy: { + "/api": { + target: DEV_PROXY_TARGET, + changeOrigin: false, + }, + "/galaxy.gateway.v1.EdgeGateway": { + target: DEV_GRPC_PROXY_TARGET, + changeOrigin: false, + // Connect-Web server-streaming + // (`SubscribeEvents`) uses chunked HTTP + // responses; http-proxy passes them through + // transparently as long as buffering stays off, + // which is the default. + }, }, }, - }, + }; });