Skip to content
internal/blog/unified
On this page

Building one documentation site out of several constituent projects plus a docs-site's own cross-cutting content, each mounted under its own declared slug.

#internal/blog/unified

#internal/blog/unified

Package unified builds one documentation site out of several constituent projects plus a docs-site's own cross-cutting content.

Each constituent project is built from its own selfdoc.json; the docs-site's config supplies the orchestration through its "unified" block, which names the projects, their slugs and their navigation titles. Every project is mounted under its slug, the docs-site's own pages under "common", and the posts of every project land in the one site-level "blog/" tree they share.

[BuildUnified] is the entry point. It wipes the output directory, injects every project's posts into that project's docs tree, partitions the pages, builds one pass per docs-site version, constituent and locale, and then writes the files that belong to the site rather than to a page: the landing page listing the projects, the shared stylesheet, the auxiliary documents, the root redirect stub, the Cloudflare redirect rule, the search index over the whole tree and the compressed companions.

#The docs-site is the (N+1)th project

Its own pages are a mount like any other, which is what makes the landing page's card links and the shared stylesheet hop correct: both are computed from the landing page's own address rather than from a site-root path that would only resolve when the site is served from an origin root.

#Every write and every subprocess is declared

Nothing here touches the filesystem's mutating calls or os/exec directly: each is routed through the effects handle the caller passes in, so a --dry-run records the whole unified build instead of performing it. Reads are not effects and use the standard library.

#ResolveProjectPath

Go go
func ResolveProjectPath(projectEntry map[string]any, docsSiteDir string) (string, error)

ResolveProjectPath resolves a constituent project's absolute path from its entry in the unified config.

The entry's "path" is stated relative to the docs-site directory, e.g. "../core". A path that does not name a directory is a ConfigError: the whole unified build is described by these entries, so a typo here would otherwise surface as a missing page somewhere far downstream.

#ProjectSlug

Go go
func ProjectSlug(projectEntry map[string]any) string

ProjectSlug is the URL segment a constituent project is mounted under: the entry's explicit "slug" when it states one, else the last component of its path.

#ProjectNavTitle

Go go
func ProjectNavTitle(projectEntry map[string]any) string

ProjectNavTitle is the navigation title a constituent project carries: the entry's explicit "nav_title" when it states one, else its slug with the separators turned into spaces and every word title-cased.

#BuildUnified

Go go
func BuildUnified(

BuildUnified builds a unified documentation site from several constituent projects and returns the paths it wrote.

dirPath is the docs-site's own project root. cfg is a pre-loaded config; nil loads selfdoc.json from dirPath. theme overrides the theme the config declares, for this build only -- empty means the config decides. includeDrafts includes the draft posts of every project.

The docs-site's "unified" block names the constituents; its "versions" and "locales" arrays drive the passes. Each constituent is built from its own selfdoc.json under its own slug, the docs-site's own pages under "common", and every project's posts once into the shared site-level tree. Posts are injected into each project's docs tree before the build and removed afterwards whether the build succeeded or failed.

More tools from this site

  • claudestream Drive Claude Code from Python: run it as a subprocess and read its output as typed events, with async and sync sessions, sandbox policies, and tools you define in Python
  • claudewheel A TUI Claude Code Launcher that lets you have more than one profile, manage sessions lifecycle, pick the exact CC version, model to use (even older unlisted ones), pick which GitHub account to use, etc.
  • dirstat Fast, single-binary directory statistics CLI: every file under a tree grouped by format, with counts, sizes, and lines of code, as a colored terminal table or as JSON
  • fastware A batteries-included ASGI framework: msgspec JSON, a managed Granian server, dependency injection, SSE, WebSockets, auth, and a test client
  • go-toml-edit Zero-dep TOML editing library for Go with comment preservation
  • howmuchleft The fastest Claude Code statusline: context window, 5-hour, and weekly limit usage as three customizable gradient bars, rendering in about 6 ms
  • orxtra
  • pgdesign
  • predraw Declarative rendering pipeline: describe a scene in JSON and get SVG, PNG and WebP out, with light and dark style tokens, reusable components and text converted to path outlines
  • reposummary Turn a git repository's history into a Markdown journal: pick a time window or revision range and get a readable digest of what changed, optionally narrated by an LLM
  • rlsbl Release orchestration and project scaffolding CLI that bumps versions, validates a structured JSONL changelog, tags only the commit CI verified, and publishes to npm, PyPI, Go and more
  • safegit git wrapper CLI that gives each commit its own temporary index and retries ref updates on conflict, so concurrent agents share one repository
  • saferm Command-line replacement for rm that archives every deletion with a mandatory reason and the context it ran in, so deleted files can be listed, inspected and restored
  • strictcli
  • stricttest An always-on test-isolation floor: a pytest plugin and a Go env-hygiene module that make a test suite structurally unable to reach real credentials, the real HOME, the network, or the development repository.
  • wesktop A Python framework that turns an ASGI web app into a desktop application, serving it from a local Granian server and displaying it in a native OS window via pywebview
Search