Skip to content

zensical-pages

Builds a Zensical microsite and deploys it to GitLab Pages. This very site is built by this component — see its self-include in this repo's root .gitlab-ci.yml.

Two jobs:

  • zensical-build — runs the zensical CLI (pre-installed in the docs-tools image in its own venv — no runtime install, no per-repo lockfile), builds the site, and moves the produced site/ to $CI_PROJECT_DIR/public (Zensical's CLI has no --site-dir flag and reads zensical.toml from the working directory). Runs everywhere via an explicit when: on_success rule — a rule-less job is skipped on merge-request pipelines.
  • pages — consumes the build artifact, deploys on deploy_branch only.

Change-detected by default — both jobs run only when a matching file changes, so a code-only MR doesn't rebuild the docs site. Build and deploy share the same changes filter, so they skip or run together (a deploy never runs without its build artifact).

Jobs

Job What it runs
zensical-build Removes [project] exclude_docs matches from the docs tree, installs the estate's structured-data main.html into the site's custom_dir (adding the key when the site has none), runs zensical build --clean (CLI baked into the image), writes site/llms.txt, moves site/ → public/.
pages Publishes the public/ artifact via GitLab Pages (pages: true).

Inputs

Input Type Default Description
image_version string "v0.1.1" docs-tools image tag (provides the baked zensical toolchain).
stage string pages GitLab CI stage for both jobs.
working_directory string "." Directory the build runs from. Must contain zensical.toml.
docs_path string "docs" Path to the Zensical docs source, relative to working_directory. Echoed in logs; Zensical reads zensical.toml for the canonical path.
deploy_branch string "main" Branch whose pipelines deploy Pages. Other branches still build, for visibility.
changes array ["docs/**/*", "zensical.toml", ".gitlab-ci.yml"] Change-detection paths shared by both jobs. Set ["**/*"] to always run.

exclude_docs

Zensical accepts MkDocs' exclude_docs key and builds the matching files anyway (checked at 0.0.55, the image's version, and 0.0.62, the latest release, on 2026-09-16), which is how four estate sites came to publish their page scaffold. The component honours the key itself, removing the matches from the docs tree before the build so neither the site, the sitemap nor the search index carries them. One pattern per line: a bare name matches at any depth (_template.md), a slash anchors it to the docs root (drafts/*.md), a trailing slash names a directory. Negation and anchored patterns are not supported. A pattern that matches nothing is logged and ignored. See spec 0090.

Derived configuration

Two zensical.toml values are filled in by the build when the site leaves them unset, from what the checkout already knows (spec 0092): [project] edit_uri becomes -/edit/$CI_DEFAULT_BRANCH/<docs path from the repo root>/ for a gitlab.com repo_url, so "edit this page" links stop going to edit/master/; and [project.extra] language is read off go.mod, Cargo.toml, a *.tf file or pyproject.toml for the structured data's programmingLanguage. Each insert is re-parsed before it is trusted and skipped with a log line if it would not parse. A site that sets either keeps its own value.

Structured data

Every built page carries one JSON-LD graph: WebSite, SoftwareSourceCode and a single Person (the author, with sameAs to the profiles listed in [[project.extra.social]]), plus a TechArticle on every page but the home. All of it is derived from zensical.toml and the page's front matter (title, description, date, tags, authors); nothing is typed into the template. programmingLanguage comes from [project.extra] language when set, or from a .go., .rust. or .iac. host. The component achieves this by writing a main.html that fills the theme's extrahead block into the site's custom_dir: into the directory the site declares (its own main.html, if it has one, is left alone and wins), or into .zensical-overrides with the key added to zensical.toml in the job workspace and the result re-parsed before it is trusted. The package's own template cannot be used: /opt/zensical is root-owned and the job runs as ci. Canonical copy: scripts/zensical-structured-data/main.html, byte-diffed by the self-test. See spec 0091.

The llms.txt map

Every site built by this component carries /llms.txt: an H1, the site description as a blockquote, then every page under its section as - [title](url): description, closing with the repository's releases, the estate changelog filtered to the project, the estate map at phpboyscout.uk/llms.txt, and the sitemap. Assistants and answer engines fetch one page at a time, and this is the file they look for at the root to learn what the other pages are.

It is derived, not authored. Sections follow [project] nav when the site declares one (nested groups flatten into their top-level entry; pages the nav omits land under More), and the Diátaxis directory order otherwise (getting-started, tutorials, how-to, reference, explanation, about, then anything else, then development). A page's line uses its front-matter title and description, falling back to its first heading. Directories and files starting with _ are skipped, so _template/ scaffolding never appears. site_url is required; the build fails without it rather than writing relative links an assistant cannot follow.

The engine is embedded in the template and mirrored at scripts/zensical-llms-txt/llms.py, where its unit tests live; the self-test byte-diffs the two. Standard library only, because the docs-tools image carries nothing else.

Usage

include:
  - component: gitlab.com/phpboyscout/cicd/[email protected]

See also