The build pipeline
A build is a token passed through nine ordered stages. Each stage either enriches the build or halts it with a diagnostic naming the file at fault; there is no partial output. The whole thing is one Elixir pipeline, and you can drive it programmatically if the CLI is not enough.
What each stage does
| stage | responsibility |
|---|---|
Load |
Reads cherry.exs, content collections, portfolio, CV, and static files into one in-memory site. |
Validate |
Frontmatter against the schemas, date sanity, duplicate slugs, token overrides against the theme manifest. Fails loud, names the file. |
Transform |
Content components expand first, then markdown becomes HTML: tables, footnotes, autolinks, and class-based syntax highlighting coloured by theme tokens. |
Layout |
Resolves the theme chain, renders every page through its template (EEx or HEEx), injects the SEO head, token overrides, and custom.css. |
Feeds |
feed.xml (Atom), feed.json, sitemap.xml, canonical URLs, all derived from your configured url. |
Machine |
llms.txt plus a markdown mirror beside every page: the machine surface. |
Search |
Builds the search index. search: "cherry" needs no Node anywhere; "pagefind" shells out after Emit instead. |
Emit |
Writes the tree. The only stage that touches disk. |
Post |
Post-emit hooks: Pagefind indexing, anything that needs the finished tree. |
Determinism is a gate, not a goal
The same input tree produces the same output bytes, every time, on every platform. Cherry's own CI double-builds its fixture sites and the demo on every commit and fails if a single byte differs. What that buys you:
- Diffs mean something. If the deployed tree changed, you changed it.
- Caches work. Unchanged pages hash identically across builds.
- CI can prove drift. Build twice in your own pipeline and compare; a mismatch is a bug in Cherry, and we treat it as one.
Where the randomness went
No timestamps in output, no build IDs, no "generated at" footers, stable ordering everywhere a map could have leaked iteration order. The discipline is invisible until you need it, and then it is everything.