ru-mkdocs-publish¶
Build, check, audit and publish a HardFOC documentation site with the shared theme, versioned or not.
ru-mkdocs-publish.yml builds a HardFOC documentation site with the shared theme, checks it, audits it and publishes it to gh-pages as
a versioned site with mike, with the Doxygen API reference under api/.
Migrating to MkDocs moves a repository onto it; the style guide
says what its pages contain.
Where the theme comes from¶
The theme, the plugin, figkit and the pinned toolchain are the hardfoc-mkdocs-theme package in
packages/hardfoc-mkdocs-theme. Every job installs it with pip from
the commit this workflow runs from (the job context's workflow_repository and workflow_sha), so a caller
pinned to @v1 builds with the theme released in v1, and a branch under review builds with that branch's theme.
theme_ref overrides the ref. A repository's own requirements-docs.txt, if it has one, is installed afterwards
for extra plugins.
What runs when¶
| Event | Build + checks | Lighthouse + screenshots | Publish |
|---|---|---|---|
| Pull request | Yes | Yes | Never, whatever deploy says |
| Push to the default branch | Yes | Yes | Version dev (when deploy is true); dev also holds latest until the first release |
Push of a vX.Y.Z tag |
Yes | Yes | Version vX.Y.Z (release_version: minor: X.Y), and latest moves to it |
With versioning: false (the organisation portal) there are no versions: see
Unversioned sites.
The site root redirects to latest. Aliases are symlinks, so the API reference added to a version folder is
reachable through them too. Material's version selector in the header lists every version.
Jobs and permissions¶
The caller grants contents: write, even one that never publishes (pull requests, deploy: false): GitHub checks
the permissions of every job of a called workflow before the run starts, whatever the job's if.
| Job | Permissions | Steps |
|---|---|---|
build |
contents: read |
Check out (plus submodule_paths), install the theme, run check_commands (hardfoc-docs check), mkdocs build --strict, Doxygen into api/ (with the HardFOC API stylesheet when the Doxyfile has none), lychee --offline --include-fragments on the merged site, upload docs-site |
quality |
contents: read |
Lighthouse CI (median of 3 runs, mobile emulation) on the home page and the first navigation pages against lighthouse_budgets, upload lighthouse-report; Playwright screenshots at 375 and 1280 px, light and dark, upload docs-screenshots |
deploy |
contents: write |
Versioned: prune the pre-MkDocs site from gh-pages (prune_legacy), mike deploy, add the API reference to the version folder, mike set-default latest, root 404.html, push (never force). Unversioned: hardfoc-docs publish-root commits the built site as the whole branch, push (never force) |
Every action is pinned to a commit SHA with its version in a comment.
The first publish¶
A repository with an older (Jekyll / Doxygen) site on gh-pages: everything at the top of the branch that is not
a mike version or alias is removed in one commit before mike writes (kept: versions.json, index.html once
mike owns it, 404.html, .nojekyll, CNAME, robots.txt). A repository without gh-pages: mike creates the
branch. In both cases the root gets mike's redirect to latest/ and a 404.html that sends old addresses to the
same page under latest/ (/docs/x/, /development/x/ to latest/x/; /doxs/, /html/ to latest/api/).
Inputs¶
| Input | Type | Default | Description |
|---|---|---|---|
python_version |
string | 3.12 |
Python used to build the site. |
requirements |
string | requirements-docs.txt |
Optional pip requirements file installed after the theme package (extra MkDocs plugins). Skipped when the file does not exist; the theme package already pins mkdocs, mkdocs-material, awesome-nav, pymdown and mike. |
theme_ref |
string | empty | Ref of hardfoc/hf-general-ci-tools to install the theme package from. Empty: the commit this workflow runs from, so the theme always matches the workflow. |
mkdocs_config |
string | mkdocs.yml |
Path of mkdocs.yml. |
submodules |
string | false |
Submodules to check out with the repository: 'false', 'true' (top level) or 'recursive'. |
submodule_paths |
string | empty | Space-separated submodule paths to initialise (shallow) after checkout, nested ones included: each parent submodule on the way is initialised without recursion, e.g. 'lib/core/drivers/internal/pincfg'. Use it instead of 'recursive' when the checks need a few nested submodules. |
check_commands |
string | if [ -f tools/figkit/build.py ]; then python3 tools/figkit/build.py --check; fi if [ -f tools/docs/gen.py ]; then python3 tools/docs/gen.py --check; fi if [ ! -f tools/figkit/build.py ] && [ ! -f tools/docs/gen.py ]; then hardfoc-docs check; fi |
Shell lines run before the build, with GH_TOKEN set to the job's read-only token (for commands that read the GitHub API); any failure fails the job. The default runs the figure drift check and the generated-region check (hardfoc-docs check), or a repository's own tools/figkit and tools/docs/gen.py when it still carries them. |
doxygen_config |
string | empty | Doxyfile for the API reference, relative to doxygen_working_directory. Empty: no API. |
doxygen_working_directory |
string | . |
Directory Doxygen runs in; relative paths in the Doxyfile resolve against it. |
doxygen_html_dir |
string | empty | Directory of the generated HTML, relative to doxygen_working_directory. Empty: read OUTPUT_DIRECTORY and HTML_OUTPUT from the Doxyfile. |
api_dir |
string | api |
Folder of the site that receives the Doxygen HTML (match extra.api_dir in mkdocs.yml). |
link_check |
boolean | true |
Run lychee offline on the built site (internal links, anchors and images, API included). |
link_check_args |
string | empty | Extra lychee arguments, for example --exclude-path patterns. |
lighthouse |
boolean | true |
Run Lighthouse on the built site (home page and the first navigation pages) against the budgets. |
lighthouse_budgets |
string | performance=0.8 accessibility=0.95 best-practices=0.9 seo=0.9 |
Minimum Lighthouse category scores (0-1, median of three runs, mobile emulation), space-separated category=score pairs. A score below its minimum fails the job. |
screenshots |
boolean | true |
Capture the home page and the first navigation pages at 375 and 1280 px, light and dark (artifact). |
versioning |
boolean | true |
true: a versioned site, published with mike (default branch -> dev_version, v* tag -> that version, with latest_alias moved to it). false: one unversioned site (the organisation portal), published from the default branch as the whole content of deployment_branch at its root (a custom domain only from a CNAME file in the site); tags publish nothing. The build tells the theme plugin which (HARDFOC_DOCS_VERSIONED), so site_url and the version selector match. |
deploy |
boolean | false |
Publish to deployment_branch. Never happens on a pull_request event, whatever this says; callers usually pass a repository-variable gate here. |
deployment_branch |
string | gh-pages |
Branch GitHub Pages serves from. |
dev_version |
string | dev |
mike version name a push to the default branch publishes to. |
latest_alias |
string | latest |
mike alias moved to each release (v* tag). |
release_version |
string | tag |
Version name a v* tag publishes: 'tag' (the tag itself, vX.Y.Z) or 'minor' (X.Y, a patch release replaces the docs of its minor release). |
prune_legacy |
boolean | true |
On publish, remove every top-level entry of the deployment branch that is not a mike version or alias (the old Jekyll / Doxygen site: development/, doxs/, html/, version_selector.html, ...), in the publish commit. Kept: versions.json, index.html, 404.html, .nojekyll, CNAME, robots.txt. |
Example¶
name: 📚 Docs
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: docs-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
docs:
uses: hardfoc/hf-general-ci-tools/.github/workflows/ru-mkdocs-publish.yml@v1
permissions:
contents: write
with:
doxygen_config: Doxyfile
deploy: true
Unversioned sites¶
The organisation portal (hardfoc.github.io) has one current site, not versions. With versioning: false:
| Piece | Versioned (default) | Unversioned |
|---|---|---|
site_url |
https://hardfoc.github.io/<repo>/latest/ |
https://hardfoc.github.io/ (or /<repo>/) |
| Version selector, older-version banner | Yes | No |
| Push to the default branch | Publishes dev |
Replaces the whole deployment branch with the built site, in one ordinary commit (.nojekyll written; a CNAME only when the site has one, docs/CNAME; no commit when nothing changed) |
Push of a v* tag |
Publishes vX.Y.Z |
Nothing (a notice) |
| Pull request | Build, checks, Lighthouse, screenshots | The same |
The build job tells the theme plugin through HARDFOC_DOCS_VERSIONED, so the input decides both the build and the
publish. A local build of the portal needs nothing: the plugin treats <org>.github.io as unversioned on its own,
and plugins: [{hardfoc: {versioned: false}}] does the same for any other site.
jobs:
site:
uses: hardfoc/hf-general-ci-tools/.github/workflows/ru-mkdocs-publish.yml@v1
permissions:
contents: write
with:
versioning: false
deploy: true
Pages must serve the gh-pages branch from / once the first publish has run; until then, leave the Pages source
as it is, so the current site stays up.
After the first publish¶
GitHub Pages must serve the gh-pages branch from / (Settings > Pages > Build and deployment: source
Deploy from a branch, branch gh-pages, folder /). The workflow never changes repository settings.
When it fails¶
| Symptom | Cause |
|---|---|
| "Install the theme" fails | The caller pins a ref of hf-general-ci-tools without the theme package (older than v1.1.0) |
| The strict build fails | A warning: a broken link or anchor, a page missing from .nav.yml, Jekyll front matter, a page without # Title, an image without alt text, a link to a repository file that does not exist |
| Drift checks fail | A figure or generated table no longer matches the code: run hardfoc-docs figures / hardfoc-docs gen and commit |
| lychee fails | A broken link or anchor in the built site, the API reference included |
| Lighthouse fails | A score under budget; the lighthouse-report artifact names the audits |
| Old pages still served at the root | The first publish has not run yet, or prune_legacy is false |
| The site shows the README rendered by Jekyll | Pages serves a branch other than gh-pages, or GitHub Actions |