Initial commit
Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Assisted-by: GLM 5.3
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
# Development
|
||||
|
||||
How to work on **Volumen**.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Go 1.27.1, the newest stable release; the `go` directive in `go.mod`
|
||||
declares exactly that version, and nothing older builds the module.
|
||||
- [just](https://github.com/casey/just) for the recipes.
|
||||
- gcc, but only for the race detector: `just race` needs cgo. The build
|
||||
itself is pure Go with `CGO_ENABLED=0`.
|
||||
- Perl for the scripted recipe lines and `scripts/notices.pl`; the base
|
||||
interpreter with builtins only is enough.
|
||||
|
||||
## Setup
|
||||
|
||||
```sh
|
||||
git clone https://sourcedock.dev/petrbalvin/volumen.git
|
||||
cd volumen
|
||||
just build
|
||||
```
|
||||
|
||||
`just run` starts the server against `./config.toml` and `./posts` on the
|
||||
configured host and port (default `[::]:9091`); `just dev` binds it to
|
||||
`127.0.0.1:9091`. `config.toml` and `users.toml` are git-ignored, because the
|
||||
config holds secrets: on a fresh clone the server runs on the built-in
|
||||
defaults and the overridden content directory. Both recipes pass
|
||||
`-buildvcs=true`, because plain `go run` does not stamp the build, so the
|
||||
reported version would be `(devel)`. Go has no built-in reload; restart the
|
||||
process after code changes. The whole binary rebuilds in seconds, which
|
||||
avoids waiting for a reload cycle.
|
||||
|
||||
## Recipes
|
||||
|
||||
Every recipe in the project's file, and what it does. Taken from the file
|
||||
itself, so the names and the list match it exactly, in the order the file
|
||||
declares them.
|
||||
|
||||
| Recipe | What it does |
|
||||
|---|---|
|
||||
| `just` | the `default` recipe, `just --list`: every recipe with its one-line summary |
|
||||
| `just build` | compiles `cmd/volumen` into `bin/volumen` with `CGO_ENABLED=0`, `-trimpath` and `-buildvcs=true`, stripped, zero errors and zero warnings |
|
||||
| `just test` | the full suite as CI runs it, with the 80 percent coverage floor |
|
||||
| `just race` | the same suite under the race detector; the expensive one |
|
||||
| `just unit ./internal/store 'TestName'` | a scoped run for iterating, cached, no race, no coverage |
|
||||
| `just fuzz <target> <package>` | a time-boxed fuzz of one target in one package, never a gate |
|
||||
| `just bench` | benchmarks with `-benchmem` and five counts, on an idle machine only |
|
||||
| `just fmt` | gofmt over the tree, in place |
|
||||
| `just fmt-check` | zero diff; prints nothing when everything is formatted |
|
||||
| `just vet` | `go vet` and `go fix -diff` |
|
||||
| `just gates` | the definition of done in one command: `build`, `fmt-check`, `vet`, `test`, `race`, in that order |
|
||||
| `just clean` | removes `bin/` and `coverage.out` |
|
||||
| `just install` | builds, then copies the binary into `~/.local/bin` |
|
||||
| `just uninstall` | removes the installed binary |
|
||||
| `just run` | runs the server against `./config.toml` and `./posts`, with the build stamped |
|
||||
| `just dev` | the same, bound to `127.0.0.1:9091` |
|
||||
|
||||
`just fmt` and `just fmt-check` are the formatting authority, `just vet` the
|
||||
static one, and the three of them plus `build`, `test` and `race` are the
|
||||
whole gate. Style rules the tools cannot see (error wrapping, `log/slog`
|
||||
only, no panics outside `main`, British English names and comments) are
|
||||
written in [CONTRIBUTING.md](../CONTRIBUTING.md).
|
||||
|
||||
## Running a single test
|
||||
|
||||
```sh
|
||||
go test -run TestName ./internal/store
|
||||
```
|
||||
|
||||
Narrow selections do not apply the coverage floor (that lives in the `test`
|
||||
recipe), so use them freely while iterating. Or stay on the recipe:
|
||||
`just unit ./internal/store 'TestName'`. Add `-v` for the sub-test names,
|
||||
`-race` when the change touches concurrency, and `-count=1` when a cached
|
||||
result looks stale; the gate itself runs with `-count=1`, so a cached pass
|
||||
never stands in for a fresh one.
|
||||
|
||||
The suite mirrors the package layout (`*_test.go` next to the code), shares
|
||||
no global fixtures, and uses `t.TempDir()` and `net/http/httptest` rather
|
||||
than a real network:
|
||||
|
||||
| Package | Covers |
|
||||
|---|---|
|
||||
| `cmd/volumen` | CLI dispatch and every subcommand against temp directories. |
|
||||
| `internal/admin` | Login, CSRF, post CRUD, settings, media, roles. |
|
||||
| `internal/app` | Server assembly, the route table, `/healthz`, `robots.txt`, media serving, rate-limit headers. |
|
||||
| `internal/audit` | The JSON-lines audit log, and a disabled or unwritable one. |
|
||||
| `internal/backup` | Archive round-trips, the restore allowlist and the decompression budget. |
|
||||
| `internal/biblio` | The `refs` frontmatter into a numbered, linked reference list. |
|
||||
| `internal/config` | Loading, merging, defaults, overrides, validation, and the example file matching the embedded template. |
|
||||
| `internal/diff` | The line-based LCS diff behind the revision comparison view. |
|
||||
| `internal/fediverse` | The `@user@host` handle rule. |
|
||||
| `internal/identifiers` | The scholarly identifier rules: DOI syntax and normalisation, ORCID shape and ISO 7064 check digit. |
|
||||
| `internal/feeds` | RSS, Atom, JSON Feed and sitemap output. |
|
||||
| `internal/frontmatter` | TOML frontmatter parsing, key order and round-trips. |
|
||||
| `internal/httpapi` | The public API over `httptest`: ETags, CORS, pagination, token writes, and the committed contract fixtures. |
|
||||
| `internal/i18n` | The admin interface catalogue: English keys, Czech translations, plural rules. |
|
||||
| `internal/imagefile` | The accepted extensions, the WebP, AVIF and SVG signatures, the detected type and the header dimensions. |
|
||||
| `internal/markdown` | Rendering, sanitisation, the mathematics and diagram passes, the table of contents, and the golden corpus in `testdata/`. |
|
||||
| `internal/password` | scrypt hashing and verification. |
|
||||
| `internal/payloads` | Payload builders, filtering and validation. |
|
||||
| `internal/post` | The Post model: parsing, metadata, rendering, cloning. |
|
||||
| `internal/preview` | The signed share links for unpublished posts. |
|
||||
| `internal/ratelimit` | The sliding-window limiter. |
|
||||
| `internal/scheduler` | Due-post selection, the publish sweep and the interval loop. |
|
||||
| `internal/session` | HMAC-signed cookies and middleware. |
|
||||
| `internal/store` | Loading, saving, deleting posts, revisions, tombstones, media. |
|
||||
| `internal/templates` | The `templates.toml` post templates. |
|
||||
| `internal/tokens` | Minting, scopes, authentication and revocation. |
|
||||
| `internal/tomlfile` | Atomic `0600` TOML writes and the shape helpers. |
|
||||
| `internal/updater` | Release checks, version comparison and the verified self-update. |
|
||||
| `internal/users` | `users.toml`, roles and last-admin safeguards. |
|
||||
| `internal/version` | The release identity from `debug.ReadBuildInfo`. |
|
||||
| `internal/web` | Middleware: gzip, the cross-origin gate, security headers, the CSP nonce, the request logger and the client IP. |
|
||||
| `internal/webhooks` | Signed deliveries, retries, payload shapes. |
|
||||
|
||||
## Coverage
|
||||
|
||||
```sh
|
||||
just test
|
||||
go tool cover -func=coverage.out
|
||||
```
|
||||
|
||||
The `total:` line is the number that matters, and it stays at 80 percent or
|
||||
more; the `test` recipe fails below the floor.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
```sh
|
||||
just bench
|
||||
```
|
||||
|
||||
The recipe is `go test -run '^$' -bench=. -benchmem -count=5 ./...` over the
|
||||
whole module. Two benchmarks live in the tree, `BenchmarkRender` in
|
||||
`internal/markdown` and `BenchmarkServer` in `internal/app`; the binding
|
||||
measurement method is in [BENCHMARKING.md](BENCHMARKING.md). Benchmark on an
|
||||
idle machine, and compare only runs made in one process against each other.
|
||||
|
||||
## Debugging the build
|
||||
|
||||
```sh
|
||||
go build -gcflags='-m' ./... # inlining decisions
|
||||
go build -gcflags='-S' ./... # what the compiler generated
|
||||
```
|
||||
|
||||
## Continuous integration
|
||||
|
||||
Workflows live in `.gitea/workflows/` and run on the project's own runners.
|
||||
They are written by hand rather than through `just`, but they enforce the same
|
||||
set of gates, so a green `just gates` locally is the fastest way to a green
|
||||
pipeline.
|
||||
|
||||
## Releases
|
||||
|
||||
Releases are cut by merging `development` into `main` and tagging `vX.Y.Z`. The
|
||||
tag drives the release workflow, which builds the assets and publishes the
|
||||
notes it extracted from `CHANGELOG.md`.
|
||||
|
||||
Dependency changes touch one more file: `NOTICE.md` in the root
|
||||
reproduces the licence of every module the binary is compiled from, and
|
||||
`perl scripts/notices.pl` writes it again after a module is added, removed or
|
||||
upgraded.
|
||||
Reference in New Issue
Block a user