Skip to main content

Build, test & CI

Makefile (canonical entry point)

The Makefile auto-installs pinned tool versions into ./bin/.

TaskCommand
Build the binarymake build
Unit testsmake test
A single Go testgo test ./app/<pkg>/ -run TestName (after make .gen mocks)
Lintmake lint
Merge-gate checks (parallel)make -j check
Everythingmake default (lint test test-integration)
Regenerate Wiremake .gen
Regenerate mocksmake mocks
Dev server + tester UImake -j=2 dev.run
End-to-end (Robot)make e2e
Build the docs sitemake docs.build
Link-check the docs sitemake docs.links
Serve the docs sitemake docs.serve
Preview the built docs sitemake docs.preview

Code generation

  • Wirewire_gen.go is generated from wire.go + wire_bindings.go. Regenerate with make .gen whenever providers/bindings change.
  • Mocks — mockery mirrors app/ interfaces into mocks/. Regenerate with make mocks whenever 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 from devenv.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 with make -j. Three stages: prebuild builds the base images (rarely, off the merge-request path); a single check job runs make -j check (Go test/lint, cspell, editorconfig, the tester + website checks, cross-arch dist) then make e2e (Robot against the fixture image, on a docker-in-docker service); and a single release job (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-rebuilt chromium-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 by make 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).