Build, test & CI
Makefile (canonical entry point)
The Makefile auto-installs pinned tool versions into ./bin/.
| Task | Command |
|---|---|
| Build the binary | make build |
| Unit tests | make test |
| A single Go test | go test ./app/<pkg>/ -run TestName (after make .gen mocks) |
| Lint | make lint |
| Merge-gate checks (parallel) | make -j check |
| Everything | make default (lint test test-integration) |
| Regenerate Wire | make .gen |
| Regenerate mocks | make mocks |
| Dev server + tester UI | make -j=2 dev.run |
| End-to-end (Robot) | make e2e |
| Build the docs site | make docs.build |
| Link-check the docs site | make docs.links |
| Serve the docs site | make docs.serve |
| Preview the built docs site | make docs.preview |
Code generation
- Wire —
wire_gen.gois generated fromwire.go+wire_bindings.go. Regenerate withmake .genwhenever providers/bindings change. - Mocks — mockery mirrors
app/interfaces intomocks/. Regenerate withmake mockswhenever an interface changes.
Testing
Unit tests live beside the code (*_test.go) and import generated mocks. The
app/pdfrender/cdp render path and the loaders are well covered; the
validate command doubles as an integration smoke test that renders every
example.
Robot Framework integration tests live under integration-tests/ and run
against the real runtime image (not through make test). Use make e2e: it
builds the binary and the fixture image with bake, then drives the suite against
it over both Chromium factory types. They cover the callback event, examples,
HTTP errors, schema, static serving, status, templates, and the tester UI.
CI & images
- GitLab CI (
.gitlab-ci.yml) runs everything on a single devenv toolchain image (pdf-server-ci, built fromdevenv.nix), so CI and local use the same tools. Since every job pays a cold image pull, the pipeline keeps the job count low and parallelizes inside a job withmake -j. Three stages:prebuildbuilds the base images (rarely, off the merge-request path); a singlecheckjob runsmake -j check(Go test/lint, cspell, editorconfig, the tester + website checks, cross-archdist) thenmake e2e(Robot against the fixture image, on a docker-in-docker service); and a singlereleasejob (master/tags) pushes the multi-arch images and, on master, builds + deploys this site. - Images are assembled with
docker buildx bake(docker-bake.hcl): a rarely-rebuiltchromium-base(Alpine + tini + Chromium) plus a thin image that copies the prebuilt binary and the static bundle onto it — no Go build happens in Docker. The binary is cross-compiled once per arch bymake dist, which also assembles the static bundle (API-doc HTML + built tester UI); the image serves the tester from disk rather than embedding it in the binary.
Documentation build
The site is Docusaurus under website/, reading the
canonical docs/ in place. make docs.build generates llms.md,
llms-full.md and runs the production build; the convention's own name,
/llms.txt, is a redirect to llms.md rather than a second copy of it,
declared in website/static/_redirects and applied by the host — so it
resolves on the deployed site, not under make docs.preview.
make docs.gallery renders the Gallery from live local
renders (website/scripts/gen-gallery.sh — reusing the prebuilt binary and
a local Chromium to render every example). CI's release job (master only)
runs the gallery + build and deploys to Cloudflare Pages.
make docs.serve runs the dev server with hot reload and make docs.preview
serves the production build over website/build; both need no setup, because
they fill in a localhost SITE_URL and a placeholder CONTACT_EMAIL when the
environment has none. Only a bare make docs.build insists on the real deploy
values, since that is the build whose output gets published. Pass DOCS_PORT to
either to move off port 3000.
make docs.links is the same production build run for its link checking alone —
Docusaurus is configured with onBrokenLinks: "throw", and a relative link only
resolves the way the rendered page will once the site is built. It is part of
check.web, so a merge request fails on a dangling link instead of the release
job discovering it on master. It substitutes placeholder values for SITE_URL
and CONTACT_EMAIL, which reach only the canonical/Open-Graph URLs and the
contact link and are deploy-only CI variables a merge-request pipeline cannot
read. It needs no browser, no server and no gallery.
Because the gallery pages are generated and gitignored, hand-written docs must
link to the gallery index (/gallery/) rather than an individual example: a
checkout that has not run make docs.gallery has no per-example route, and the
link check — correctly — fails on it.
Version control & contribution policy
The repo uses colocated jj + git; commits are kept single-purpose and every
notable change is recorded in CHANGELOG.md. See AGENTS.md for the full policy
(quality gates, preparatory-refactor rule).