# Development ## Prerequisites | Tool | Version | Install | |-------------------------------|----------------------------|----------------------------------------------------------| | [Go](https://go.dev/dl/) | 1.27+ (see `go.mod`) | | | [Node.js](https://nodejs.org) | 20.19+ or 22.12+ (22 LTS recommended) | `nvm install 22` — Vite and vue-tsc run on Node | | [Bun](https://bun.sh) (package manager and script runner) | 1.x | `powershell -c "irm bun.sh/install.ps1 \| iex"` or `curl -fsSL https://bun.sh/install \| bash` | | [Task](https://taskfile.dev) | 3.43+ | `go install github.com/go-task/task/v3/cmd/task@latest` | | Git | any | | `task setup` installs the rest: [air](https://github.com/air-verse/air) for hot reload, [gremlins](https://github.com/go-gremlins/gremlins) for mutation testing, Go modules and frontend dependencies. Make sure `$(go env GOPATH)/bin` is on your `PATH`. No C compiler is needed. The SQLite driver (`glebarez/sqlite`, backed by `modernc.org/sqlite`) is pure Go, and every build runs with `CGO_ENABLED=0`. ## First run ```sh cp .env.example .env # set at least JWT_SECRET; SMTP is only needed to actually send mail task setup task dev ``` Open **http://localhost:5173**. ## How the dev setup works ``` browser ──▶ http://localhost:5173 Vite dev server (HMR) task dev:web │ /v1/*, /healthz └──proxy──▶ http://localhost:8080 Go API via air task dev:api ``` - **Always use the Vite URL (5173).** The Vite dev server proxies API calls to Go (see `server.proxy` in [`web/vite.config.ts`](../web/vite.config.ts)). The browser therefore sees a single origin, so the `jwt` cookie works and no CORS setup is needed. In the frontend, call the API with relative URLs (`/v1/...`) and never hardcode `localhost:8080`. - **air** rebuilds and restarts the API when a `.go` or `.html` file (such as a mail template) changes. Its config is in [`.air.toml`](../.air.toml). - Run the two halves separately with `task dev:api` and `task dev:web` if you want their logs in separate terminals. ### The `nofrontend` build tag The production binary embeds the built SPA from `internal/infrastructure/assets/dist/` (the `//go:embed` in `assets_embed.go`). That directory only exists after `task build:web`. To keep Go work independent of the frontend toolchain, all dev and quality tasks build with **`-tags nofrontend`**. That tag compiles `assets_stub.go` instead of the embed. The resulting binary serves the API normally and answers `/` with a notice that points to Vite. | Command | Tag | Needs the frontend built? | |------------------------------------|----------------|---------------------------| | `task dev:api`, `test`, `cover`, `mutate`, `lint:go` | `nofrontend` | no | | `task build`, `release`, `run` | *(none)* | yes (the task builds it first) | | plain `go build ./...`, `go vet ./...` | *(none)* | yes, so add `-tags nofrontend` | If your editor reports `pattern all:dist: no matching files found`, either run `task build:web` once or set the gopls build flags to `-tags=nofrontend`. In VS Code, add this to `settings.json`: `"gopls": { "buildFlags": ["-tags=nofrontend"] }`. ## Everyday commands ```sh task --list # everything available task test # Go tests; extra args pass through: task test -- -run TestCreateArticle -v task cover # coverage of the use cases → tmp/coverage.out task mutate # gremlins mutation testing → tmp/gremlins.json task lint # gofmt check, go vet, eslint --fix, vue-tsc task fmt # gofmt -w + prettier task run # build the production binary and run it (SPA embedded) on :8080 task clean # remove bin/, tmp/ and the built SPA ``` ## Tests - All Go tests live in [`test/`](../test) (package `test`, black-box). They use in-memory fakes of the domain `Repository` interfaces, so they need no database, SMTP server or network. - HTTP-level tests use `net/http/httptest` against a chi router. Call `validator.InitValidator()` before invoking a handler. - The frontend has no test runner yet. `task lint:web` runs eslint and a type check. ## Data With the default configuration, local state lives in `./data/`: `renewcms.db` and `uploads/`. The directory is gitignored. Delete it to start from scratch.