Files
RenewCMS/docs/development.md
T

95 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 test:mysql # MySQL tests against real servers in Docker (mysql:8.4, then mariadb:11)
task test:postgres # PostgreSQL tests against real servers in Docker (postgres:16, then postgres:17)
task cover # coverage of the use cases → tmp/coverage.out
task mutate # gremlins mutation testing → tmp/gremlins.json
task test:web # SPA component tests (Vitest)
task visual:check -- log-in # screenshot design vs SPA for a screen → tmp/visual/log-in/
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.
- SPA component tests use Vitest + Vue Test Utils (`task test:web`). `task lint:web` runs eslint and a type check.
## Visual check against the design
`task visual:check -- <screen> [state]` compares the SPA with the Claude Design boards in [`docs/design/source`](design/source). The screen is a file name from [`docs/design/screens`](design/screens) (`log-in`, `home`, …), and the state is one of its headings.
- It builds the SPA into `tmp/visual/dist` and serves both the build and the boards locally, so no API or dev server is needed.
- For every state it screenshots:
- the board's frame for that state;
- the SPA in the same state, at 1440×900 and 390×844, in light and dark.
- It saves `design-*.png`, `app-*.png` and `diff-*.png` (differing pixels in red) to `tmp/visual/<screen>/<state>/`, and a summary to `tmp/visual/<screen>/report.md`.
- [`web/visual/states.ts`](../web/visual/states.ts) says how to reach each state in the SPA: the route, the mocked `/v1` responses (keyed `'METHOD /v1/path'`) and any user steps. A state without an entry is captured on the design side only, and marked "design only" in the report.
- Narrow a run with `--viewport=desktop|mobile` and `--theme=light|dark`.
- Lucide icons are served from the local `@iconify-json/lucide` set, for both the boards and the SPA. api.iconify.design rate-limits repeated runs.
- The first time on a machine, run `bunx playwright install chromium` in `web/`.
The pixel percentage only points at where to look, because font anti-aliasing always differs a little. Judge by comparing the image pairs.
## Data
With the default configuration, local state lives in `./data/`: `renewcms.db` and `uploads/`. The directory is gitignored. Delete it to start from scratch.