Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 29 additions & 4 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
66 changes: 54 additions & 12 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -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<string, string | undefined>, 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;
}
Loading