From 4b3b83a66413ebea226a0f1089e6566ee2c7aee1 Mon Sep 17 00:00:00 2001 From: Andrii Furmanets Date: Tue, 5 May 2026 15:52:35 +0300 Subject: [PATCH] Configure web and API ports --- .env.example | 3 ++- CONTRIBUTING.md | 3 ++- README.md | 10 +++++-- src/server/index.ts | 33 ++++++++++++++++++++--- vite.config.ts | 66 ++++++++++++++++++++++++++++++++++++--------- 5 files changed, 95 insertions(+), 20 deletions(-) diff --git a/.env.example b/.env.example index e3b2d8a..7c4c034 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ -PORT=4141 +WEB_PORT=3000 +API_PORT=3001 BOARD_DB_PATH=./data/board.db # Optional API-key fallback. OpenAI account auth can be connected in Settings. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9722bc2..7473426 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,8 @@ npm install npm run dev ``` -Open `http://localhost:5173`. The API runs on `http://127.0.0.1:4141`. +Open `http://localhost:5173`. The API runs on `http://127.0.0.1:4141` by +default. Set `WEB_PORT` and `API_PORT` in `.env` to use another pair. ## Before Opening a PR diff --git a/README.md b/README.md index b25ba67..d6dcb7b 100644 --- a/README.md +++ b/README.md @@ -86,15 +86,21 @@ Copy `.env.example` to `.env` when you need local overrides: cp .env.example .env ``` -Supported environment variables: +Example local override: ```bash -PORT=4141 +WEB_PORT=3000 +API_PORT=3001 BOARD_DB_PATH=./data/board.db OPENAI_API_KEY= OPENAI_BASE_URL=https://api.openai.com/v1 ``` +`WEB_PORT` controls the Vite dev and preview server. `API_PORT` controls the +Fastify API and the packaged `npx draftmora` server. Without these variables, +the dev web server uses `5173` and the API uses `4141`. The older `PORT` +variable still works as an API port fallback when `API_PORT` is not set. + Open Settings in the app to choose an auth mode: - OpenAI account auth: connect an eligible OpenAI account/subscription from the diff --git a/src/server/index.ts b/src/server/index.ts index e3e8016..d03a222 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -5,8 +5,10 @@ import { fileURLToPath } from "node:url"; import fastifyStatic from "@fastify/static"; import { buildServer } from "./routes"; -const port = Number(process.env.PORT ?? 4141); -const host = process.env.HOST ?? "127.0.0.1"; +const port = readPort(["API_PORT", "PORT"], 4141); +const host = process.env.API_HOST ?? process.env.HOST ?? "127.0.0.1"; +const webPort = readPort(["WEB_PORT"], 5173); +const webHost = process.env.WEB_HOST ?? "localhost"; const app = buildServer(); const staticDir = resolveStaticDir(); @@ -39,9 +41,13 @@ if (staticDir) { try { await app.listen({ port, host }); - console.log(`Draftmora running at http://${host}:${port}`); + console.log( + staticDir + ? `Draftmora running at http://${host}:${port}` + : `Draftmora API running at http://${host}:${port}`, + ); if (!staticDir) { - console.log("Web app runs with Vite at http://localhost:5173"); + console.log(`Web app runs with Vite at http://${displayHost(webHost)}:${webPort}`); } } catch (error) { app.log.error(error); @@ -59,3 +65,22 @@ function resolveStaticDir(): string | null { ].filter(Boolean) as string[]; return candidates.find((candidate) => existsSync(path.join(candidate, "index.html"))) ?? null; } + +function readPort(names: string[], fallback: number) { + for (const name of names) { + const raw = process.env[name]?.trim(); + if (!raw) { + continue; + } + const port = Number(raw); + if (Number.isInteger(port) && port >= 1 && port <= 65535) { + return port; + } + throw new Error(`${name} must be a port number between 1 and 65535.`); + } + return fallback; +} + +function displayHost(host: string) { + return host === "0.0.0.0" || host === "::" ? "localhost" : host; +} diff --git a/vite.config.ts b/vite.config.ts index 3b82c2a..1ee5680 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,19 +1,61 @@ import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; import path from "node:path"; -import { defineConfig } from "vite"; +import { defineConfig, loadEnv } from "vite"; -export default defineConfig({ - plugins: [react(), tailwindcss()], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), +export default defineConfig(({ mode }) => { + const env = { ...loadEnv(mode, process.cwd(), ""), ...process.env }; + const webPort = readPort(env, ["WEB_PORT"], 5173); + const apiPort = readPort(env, ["API_PORT", "PORT"], 4141); + const webHost = env.WEB_HOST ?? "0.0.0.0"; + const apiHost = normalizeProxyHost(env.API_HOST ?? env.HOST ?? "127.0.0.1"); + const apiTarget = `http://${formatHttpHost(apiHost)}:${apiPort}`; + + return { + plugins: [react(), tailwindcss()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, + server: { + host: webHost, + port: webPort, + strictPort: true, + proxy: { + "/api": apiTarget, + }, }, - }, - server: { - port: 5173, - proxy: { - "/api": "http://127.0.0.1:4141", + preview: { + host: webHost, + port: webPort, + strictPort: true, }, - }, + }; }); + +function readPort(env: Record, names: string[], fallback: number) { + for (const name of names) { + const raw = env[name]?.trim(); + if (!raw) { + continue; + } + const port = Number(raw); + if (Number.isInteger(port) && port >= 1 && port <= 65535) { + return port; + } + throw new Error(`${name} must be a port number between 1 and 65535.`); + } + return fallback; +} + +function normalizeProxyHost(host: string) { + return host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host; +} + +function formatHttpHost(host: string) { + if (host.startsWith("[") && host.endsWith("]")) { + return host; + } + return host.includes(":") ? `[${host}]` : host; +}