From 7754b0f504edbe5392fc2bd5670cbcc354e2ffca Mon Sep 17 00:00:00 2001 From: mithral Date: Sat, 10 Oct 2026 16:32:32 -0400 Subject: [PATCH] dev: fix the Vite proxy and add a one-command dev runner Running `pnpm dev` alone gave ECONNREFUSED on every request, because nothing was listening on :7788. Two separate causes: - the backend simply was not running, and - Vite bound [::1]:5173 while the proxy target was "localhost", which Node resolves to ::1 first. The Go server binds IPv4, so even with the backend up the proxy could fail before falling back. - vite.config.ts: proxy target is now 127.0.0.1 explicitly and overridable via VELLUM_API; server.host is true so both stacks are served. - scripts/dev.sh + `make dev`: builds the backend, waits for /healthz, starts Vite, and stops the backend on exit. Ctrl-C tears down both. - README documents that running Vite alone needs the backend already up. --- Makefile | 6 ++++- README.md | 11 ++++++++- scripts/dev.sh | 61 ++++++++++++++++++++++++++++++++++++++++++++++ web/vite.config.ts | 11 +++++++-- 4 files changed, 85 insertions(+), 4 deletions(-) create mode 100755 scripts/dev.sh diff --git a/Makefile b/Makefile index a7790d2..74e90a7 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,11 @@ BINARY := vellum VERSION ?= 0.4.0-dev LDFLAGS := -s -w -X main.version=$(VERSION) -.PHONY: build web run test fmt vet tidy check docker clean +.PHONY: build web build-go dev run test fmt vet tidy check docker clean + +# Backend + Vite dev server together, with hot reload. Ctrl-C stops both. +dev: + ./scripts/dev.sh # Build the frontend first: the Go build embeds web/dist. build: web diff --git a/README.md b/README.md index 45b5677..a0033a4 100644 --- a/README.md +++ b/README.md @@ -141,9 +141,18 @@ The application never writes to `/media`. ## Development ```bash +make dev # backend + Vite dev server together, with hot reload make build # builds the frontend, then the Go binary with it embedded make build-go # Go only, when the frontend is unchanged make check # gofmt + vet + test make run # run the server on :7788 -cd web && pnpm dev # frontend dev server, proxies /api to :7788 +cd web && pnpm dev # frontend only; needs the backend running on :7788 ``` + +`make dev` is the one to reach for while developing: it builds and starts the +backend, waits for it to answer, then starts Vite. Vite proxies `/api` to the +backend so the browser sees a single origin and sessions behave exactly as in +production. Ctrl-C stops both. + +If you run Vite on its own, the backend must already be running, or every +request fails with `ECONNREFUSED` on `:7788`. diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100755 index 0000000..2ca5671 --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Run the backend and the Vite dev server together. +# +# ./scripts/dev.sh +# +# The Go server runs in the background; the Vite dev server runs in the +# foreground. Ctrl-C stops both. Vite proxies /api to the Go server, so the +# browser sees one origin and sessions behave exactly as in production. +set -euo pipefail + +cd "$(dirname "$0")/.." + +# A local preview instance, so this works on a fresh clone without touching +# your real media or your real database. +export VELLUM_DATA_DIR="${VELLUM_DATA_DIR:-$PWD/demo/data}" +export VELLUM_CONFIG_DIR="${VELLUM_CONFIG_DIR:-$PWD/demo/config}" +export VELLUM_LISTEN="${VELLUM_LISTEN:-127.0.0.1:7788}" +export VELLUM_ADMIN_USER="${VELLUM_ADMIN_USER:-admin}" +export VELLUM_ADMIN_PASSWORD="${VELLUM_ADMIN_PASSWORD:-vellumdemo}" +export VELLUM_LOG_LEVEL="${VELLUM_LOG_LEVEL:-info}" + +if ! command -v go >/dev/null 2>&1; then + export PATH="$PATH:/usr/lib/golang/bin" +fi + +echo "building backend..." +CGO_ENABLED=0 go build -trimpath -o vellum ./cmd/vellum + +echo "starting backend on ${VELLUM_LISTEN}" +./vellum & +BACKEND=$! + +cleanup() { + echo + echo "stopping backend..." + kill "$BACKEND" 2>/dev/null || true + wait "$BACKEND" 2>/dev/null || true +} +trap cleanup EXIT INT TERM + +# Wait for the API to answer before handing over to Vite, so the first page +# load does not race the backend's startup. +for _ in $(seq 1 50); do + if curl -fsS -m 1 "http://${VELLUM_LISTEN#*:}/healthz" >/dev/null 2>&1; then + break + fi + if ! kill -0 "$BACKEND" 2>/dev/null; then + echo "backend exited during startup" >&2 + exit 1 + fi + sleep 0.1 +done + +echo +echo "backend: http://${VELLUM_LISTEN} (${VELLUM_ADMIN_USER} / ${VELLUM_ADMIN_PASSWORD})" +echo "frontend: http://localhost:5173" +echo + +cd web +pnpm install --silent +pnpm dev diff --git a/web/vite.config.ts b/web/vite.config.ts index 115f7b3..8473fd9 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,6 +1,11 @@ import { defineConfig } from 'vite' import { svelte } from '@sveltejs/vite-plugin-svelte' +// The backend the dev server proxies to. 127.0.0.1 rather than localhost on +// purpose: Node resolves "localhost" to ::1 first, and the Go server binds +// IPv4, so the proxy would fail before falling back. +const apiTarget = process.env.VELLUM_API ?? 'http://127.0.0.1:7788' + // The bundle is embedded into the Go binary and served from /, so all asset // paths must be relative (base: './'). export default defineConfig({ @@ -13,9 +18,11 @@ export default defineConfig({ }, server: { port: 5173, + // Listen on both stacks so a browser using either localhost works. + host: true, proxy: { - '/api': 'http://localhost:7788', - '/healthz': 'http://localhost:7788', + '/api': apiTarget, + '/healthz': apiTarget, }, }, })