6.3 KiB
Development
Prerequisites
| Tool | Version | Install |
|---|---|---|
| Go | 1.27+ (see go.mod) |
|
| Node.js | 20.19+ or 22.12+ (22 LTS recommended) | nvm install 22 — Vite and vue-tsc run on Node |
| Bun (package manager and script runner) | 1.x | powershell -c "irm bun.sh/install.ps1 | iex" or curl -fsSL https://bun.sh/install | bash |
| Task | 3.43+ | go install github.com/go-task/task/v3/cmd/task@latest |
| Git | any |
task setup installs the rest: air for hot reload, 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
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.proxyinweb/vite.config.ts). The browser therefore sees a single origin, so thejwtcookie works and no CORS setup is needed. In the frontend, call the API with relative URLs (/v1/...) and never hardcodelocalhost:8080. - air rebuilds and restarts the API when a
.goor.htmlfile (such as a mail template) changes. Its config is in.air.toml. - Run the two halves separately with
task dev:apiandtask dev:webif 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
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 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/(packagetest, black-box). They use in-memory fakes of the domainRepositoryinterfaces, so they need no database, SMTP server or network. - HTTP-level tests use
net/http/httptestagainst a chi router. Callvalidator.InitValidator()before invoking a handler. - SPA component tests use Vitest + Vue Test Utils (
task test:web).task lint:webruns 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. The screen is a file name from docs/design/screens (log-in, home, …), and the state is one of its headings.
- It builds the SPA into
tmp/visual/distand 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-*.pnganddiff-*.png(differing pixels in red) totmp/visual/<screen>/<state>/, and a summary totmp/visual/<screen>/report.md. web/visual/states.tssays how to reach each state in the SPA: the route, the mocked/v1responses (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|mobileand--theme=light|dark. - Lucide icons are served from the local
@iconify-json/lucideset, for both the boards and the SPA. api.iconify.design rate-limits repeated runs. - The first time on a machine, run
bunx playwright install chromiuminweb/.
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.