229 lines
8.5 KiB
Markdown
229 lines
8.5 KiB
Markdown
<!-- go-doc:generated:begin source=go-bananas:docs/development.md signature=7d8b696ba971c1cbd2e19854d26de304 generated=2026-10-05 18:14 UTC -->
|
|
Imported from docs/development.md in the go-bananas repository.
|
|
|
|
---
|
|
|
|
# Development
|
|
|
|
Working on go-bananas.
|
|
|
|
## Requirements
|
|
|
|
| Tool | Version | Why |
|
|
| --- | --- | --- |
|
|
| Go | 1.27 or later | Static binaries, `net/http` routing primitives |
|
|
| Node.js | 22 or later | Vite 8 requires it |
|
|
| Docker | 24 or later | BuildKit for the multi-stage image |
|
|
| Helm | 3.14 or later | Chart validation and `helm lint --strict` |
|
|
|
|
Optional, installed by `make tools`: `air` for hot reload, `golangci-lint` for
|
|
linting, `sqlc` if you add a query.
|
|
|
|
## Setup
|
|
|
|
```bash
|
|
git clone https://github.com/urpi/go-bananas
|
|
cd go-bananas
|
|
make install # Go modules, admin UI dependencies, SDK dependencies
|
|
make tools # air, golangci-lint, sqlc
|
|
```
|
|
|
|
## Running
|
|
|
|
```bash
|
|
make dev-all # API on :8080, admin UI on :5173 with a proxy
|
|
```
|
|
|
|
Open <http://localhost:5173>. The Vite dev server proxies `/api`, `/rt` and `/auth`
|
|
to the Go process, so the session cookie works exactly as it will in production.
|
|
Same-origin matters: a cross-origin setup would not authenticate at all, and the
|
|
problem would look like a bug rather than a topology difference.
|
|
|
|
Other entry points:
|
|
|
|
```bash
|
|
make dev-api # the API alone, with hot reload
|
|
make dev-web # the admin UI alone
|
|
make run # build everything, run the real binary
|
|
```
|
|
|
|
### Trying the theme preference locally
|
|
|
|
The theme switcher in the header writes to the account, not just to the browser, so the
|
|
selection follows you to another browser and survives a re-login. To see that end to
|
|
end without a second browser:
|
|
|
|
```bash
|
|
# 1. Start the stack and sign in. `make dev-all` defaults to AUTH_MODE=dev, so the
|
|
# dev-user query parameter is enough to be somebody:
|
|
# http://localhost:5173/?dev-user=designer@example.test
|
|
|
|
# 2. Pick a published theme and a scheme in the header switcher. That is a PUT to
|
|
# /api/v1/me/preferences.
|
|
|
|
# 3. Read back what the server decided, which is the authoritative value:
|
|
curl -s localhost:8080/api/v1/me/preferences \
|
|
-H 'Cookie: go-bananas-session=...' | jq
|
|
|
|
# 4. Confirm it arrives with the session, which is how the UI gets it on first paint:
|
|
curl -s localhost:8080/api/v1/auth/me \
|
|
-H 'Cookie: go-bananas-session=...' | jq .preferences
|
|
|
|
# 5. Prove it is account state rather than cookie state by clearing local storage,
|
|
# or by asking in a second browser profile: the theme you chose is still selected.
|
|
```
|
|
|
|
Two things are deliberately not local state:
|
|
|
|
- **The theme is a preference, not a session claim.** The token comes from the identity
|
|
provider, so this service cannot put anything in it; `/api/v1/me/preferences` is the
|
|
contract instead.
|
|
- **`localStorage` is still read on the first render.** A server round trip cannot
|
|
happen before paint, and waiting for one flashes the wrong theme on every load. The
|
|
stored preference is adopted as soon as the session arrives, and a change writes both
|
|
places, so the next paint is correct even offline.
|
|
|
|
Pointing it at a draft theme returns 400, and pointing it at a theme that has since been
|
|
archived reads back as no choice. Both are covered in `docs/api.md`.
|
|
|
|
## Layout
|
|
|
|
```
|
|
cmd/go-bananas/ main(): signals, exit codes, nothing else
|
|
internal/
|
|
app/ dependency injection and the process lifecycle
|
|
auth/ OIDC, sessions, roles
|
|
config/ environment parsing and validation
|
|
domain/ entities, invariants, sentinel errors
|
|
httpapi/ routing, middleware, handlers
|
|
logging/ slog configuration
|
|
seed/ the built-in themes
|
|
store/ SQLite: migrations and repositories
|
|
tokenengine/ pure logic: colour, validation, rendering
|
|
store/migrations/ embedded SQL, applied at start-up
|
|
web/ the administration UI (React, Vite, Tailwind)
|
|
sdk/ the runtime SDKs, served from /rt/v1
|
|
deploy/helm/go-bananas/ the chart
|
|
docs/ this, plus the ADRs
|
|
```
|
|
|
|
See [architecture.md](architecture.md) for the dependency rule and
|
|
[standards.md](standards.md) for the conventions. Read both before a substantial
|
|
change.
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
make test # everything
|
|
make test-go # Go, with the race detector
|
|
make test-web # the admin UI
|
|
make test-sdk # the runtime SDK
|
|
make test-cover # Go, with a coverage profile
|
|
make bench # the token engine benchmarks
|
|
```
|
|
|
|
Every test run in CI is `go test -race`. A data race in a cache or a store is a
|
|
production incident waiting for load.
|
|
|
|
## The pre-merge gate
|
|
|
|
```bash
|
|
make verify # check + helm lint
|
|
```
|
|
|
|
This is exactly what CI runs. A pull request that does not pass it will not merge.
|
|
|
|
## Things that will surprise you
|
|
|
|
**The admin UI consumes the runtime.** It loads themes from `/rt/v1` and maps the
|
|
variables onto Tailwind's namespaces. Switching the UI to the "Light" or "Dark" mode
|
|
in the top bar loads a published theme's real stylesheet. A change to the token engine
|
|
that breaks delivery breaks the editor, immediately.
|
|
|
|
**Publishing bumps the version, and the version is the cache key.** Every mutation
|
|
does, not only publication. If you change `store.Update`, check whether the version
|
|
needs to move.
|
|
|
|
**History is append-only.** A rollback restores a snapshot as a *new* version. There
|
|
is no code path that removes a version, by design: an application deployed against
|
|
version 7 must never have version 7 taken away.
|
|
|
|
**The built-in themes are reconciled, not overwritten.** `internal/seed` compares the
|
|
stored version with the seed version. An edited built-in theme is recognised by its
|
|
higher version and left alone.
|
|
|
|
**Validation gates publication, not editing.** A draft can be incomplete. That is
|
|
what makes building a theme up incrementally practical.
|
|
|
|
## Common changes
|
|
|
|
### Adding a token group
|
|
|
|
1. Add a `GroupSpec` to `tokenengine.Groups`.
|
|
2. Add the tokens to the built-in themes in `internal/seed`.
|
|
3. Run `make test-go`. The seed tests validate every built-in theme and check WCAG
|
|
contrast for body text on every surface, so an inaccessible colour fails the build.
|
|
|
|
### Adding an API endpoint
|
|
|
|
1. The handler in `internal/httpapi`. No business logic; call the store, encode the
|
|
result.
|
|
2. The route in `internal/httpapi/router.go`, with the role it requires.
|
|
3. The type in `web/src/lib/types.ts` and the client function in `web/src/lib/api.ts`.
|
|
4. The documentation in `docs/api.md`.
|
|
5. Tests for each branch and each authorisation decision.
|
|
|
|
### Adding a configuration variable
|
|
|
|
1. The field and default in `internal/config`.
|
|
2. The parsing in `loadFrom`.
|
|
3. A validation rule in `Validate` if it can be wrong.
|
|
4. The environment variable in the chart's `configmap.yaml` and `values.yaml`, and in
|
|
`values.schema.json`.
|
|
5. The table in `docs/deployment.md`.
|
|
|
|
A variable with no validation is a variable that will be set to something wrong.
|
|
|
|
### Changing the runtime contract
|
|
|
|
Add, do not change. `/rt/v1` is a contract with applications that have no way to
|
|
upgrade in step with you. Adding an optional query parameter or a response field is
|
|
safe. Changing the meaning of an existing one is not.
|
|
|
|
## Conventions
|
|
|
|
Enforced, not suggested. `make check` is the same command CI runs.
|
|
|
|
| | |
|
|
| --- | --- |
|
|
| Go formatting | `gofmt`, `make fmt-check` |
|
|
| Go lint | `golangci-lint`, `make lint-go` |
|
|
| Go imports | `gci`, configured in `.golangci.yml` |
|
|
| TypeScript | `tsc --build`, strict |
|
|
| Frontend lint | `eslint --max-warnings 0` |
|
|
| Frontend format | `prettier`, including the Tailwind plugin |
|
|
| Chart | `helm lint --strict` plus `values.schema.json` |
|
|
|
|
Read [standards.md](standards.md) before contributing. It is the difference between a
|
|
change that fits and one that has to be rewritten.
|
|
|
|
## Troubleshooting
|
|
|
|
**Port 8080 in use.** `make dev-api` with `BANANAS_HTTP_ADDR=:8081`, or stop whatever
|
|
holds the port.
|
|
|
|
**The admin UI cannot reach the API.** The Vite proxy targets
|
|
`http://localhost:8080`. If the API is elsewhere, edit `server.proxy` in
|
|
`vite.config.ts`.
|
|
|
|
**`npm ci` fails.** A stale `node_modules`. `make clean-cache`.
|
|
|
|
**`go build` fails after a dependency change.** `make tidy`, then `make install`.
|
|
|
|
**A test is flaky.** Do not re-run it and hope. Find the ordering dependency. The most
|
|
common cause here is module-level state in a frontend test: use `vi.resetModules()`
|
|
and a fresh module instance.
|
|
|
|
**The chart will not render.** Read the error. Every chart failure names the value to
|
|
change — that is deliberate, and the alternative is a failure at run time instead.
|
|
<!-- go-doc:generated:end -->
|