Skip to the contentSkip to the navigation
RootsGitHubMenu
On this page

Publishing the site

The site is static files: roots docs build writes them, any static host serves them. This page is the procedure for GitHub Pages, from the repository that holds the sources. Nothing here is automatic: the toolchain never commits and never pushes.

What gets published #

roots-pages/                      a checkout of the gh-pages branch
  index.html docs.html guide/…    the newest version, at addresses without a version
  v/0.4.0/                        one complete, relocatable copy per version
    app.js app.css chunks/ fonts/   the app and its assets
    examples/todo/ gallery/ bookstore/   web builds of the example apps
  versions.json 404.html sitemap.xml robots.txt llms.txt .nojekyll

A project site lives under the repository's name: https://marcuspuchalla.github.io/roots/. That address is :origin and :base of :site in docs/roots.lisp. Only four files carry it (canonical links in the pages at the root, sitemap.xml, robots.txt and the root 404.html); every other URL is relative, so the same output also works at /.

1. Build #

$ (cd examples/bookstore && ../../bin/roots install)   # once per checkout: the bookstore's SQLite package
$ bin/roots docs check --strict                  # links, anchors, navigation, and that the example apps build
$ bin/roots docs build --strict --out ../roots-pages   # every version that is missing, and the newest at the root

--out may be an empty directory, the empty worktree of a new branch (all it holds is Git's .git), or a directory that already holds a site: version directories that exist are left alone. The example apps named by :site :examples are built with roots build --target web and written to v/<version>/examples/<name>/.

The bookstore imports roots/data, which needs the package @sqlite.org/sqlite-wasm for the web target. It is not in the repository: roots install in examples/bookstore fetches the pinned version into examples/bookstore/node_modules. Without it the example cannot be built (M180). With --strict that stops docs check and docs build; without --strict the build goes on with a warning, leaves the example out, and the home page says that it was built without it and links to its source. Publish with --strict.

2. Look at it, and run the browser pass #

$ bin/roots docs preview --out ../roots-pages                # http://localhost:4173/roots/, as GitHub Pages serves it
$ bin/roots docs preview --out ../roots-pages --base /       # the same files at the root of a host
$ NODE_PATH=/path/to/node_modules docs/site/tests/run.sh     # builds into docs/dist/site and opens every page in Chromium

Both previews must work: the first is the project site, the second is what a custom domain or a user site would serve. run.sh is part of the procedure, not an extra: docs check and docs build check what can be read from the sources (the links of every page, also of the home page, which is a component), and the browser pass checks what only a browser shows: that every section of the home page is there, that every link of the built page leads somewhere, that no page asks another origin for anything. It needs Playwright (NODE_PATH leads to it).

3. The first time: create the branch #

$ git worktree add --orphan -b gh-pages ../roots-pages       # Git 2.42 or newer: an empty branch in a directory beside the checkout
$ bin/roots docs build --strict --out ../roots-pages
$ cd ../roots-pages
$ git add -A
$ git commit -m "docs $(sed -n 's/.*"latest": *"\([^"]*\)".*/\1/p' versions.json)"
$ git push -u origin gh-pages

With a Git older than 2.42 (git worktree add has no --orphan; Apple's Git 2.39 is one), make the empty branch by hand:

$ git worktree add --detach ../roots-pages
$ cd ../roots-pages
$ git checkout --orphan gh-pages
$ git rm -rf --quiet .
$ cd -

Then, in the repository's settings on GitHub: Pages → Build and deployment → Deploy from a branch, branch gh-pages, folder / (root). The build writes .nojekyll, so GitHub serves the files as they are.

4. Every release #

$ git worktree add ../roots-pages gh-pages                   # if the directory is not there any more
$ bin/roots docs check --strict
$ bin/roots docs build --strict --out ../roots-pages         # adds v/<version>/, replaces the pages at the root
$ bin/roots docs preview --out ../roots-pages
$ cd ../roots-pages
$ git add -A
$ git commit -m "docs $(sed -n 's/.*"latest": *"\([^"]*\)".*/\1/p' versions.json)"   # the version the build wrote
$ git push

Then add the version to docs/versions.lisp with its commit, so that it can be built again from Git (Writing documentation). A version that is built from its commit is built in a bare checkout of that commit; the packages its examples need are lent to it from this checkout (the node_modules of each example, and the nearest one above the repository). If an example app that the commit publishes is still missing from the version, the build says so (M181), and with --strict it stops and writes nothing.

What a release adds. A version directory is a complete copy: the pages, the app, the fonts and the three example apps, about 8.4 MB for 0.4.0, of which the bookstore's SQLite (WebAssembly) is 1.9 MB. Every version stays in the branch.

Another address #

$ bin/roots docs build --out ../site --base / --origin https://roots.example.org
$ bin/roots docs build --out ../site --base /roots-site/     # a separate repository named roots-site

--base and --origin replace the two keys of docs/roots.lisp for one build. A site that was built for one address and is moved to another keeps working; only its canonical links, sitemap.xml and the 404.html at the root still name the old one, until it is built again.

In CI #

Not enabled. docs/site/ci/pages.yml.example is a workflow that does step 4 on a release tag: it checks out the repository with its history (versions.lisp names older commits), installs SBCL (apt-get install sbcl) and Node.js 22, runs bin/roots install in examples/bookstore, adds a worktree of gh-pages, runs bin/roots docs check --strict and bin/roots docs build --strict --out ../roots-pages, commits and pushes that worktree. To use it, copy it to .github/workflows/docs.yml and pin the two actions by commit. It needs contents: write and nothing else. It does not run the browser pass (no Playwright in it): run docs/site/tests/run.sh before the tag.

What is checked before anything is published #

  • docs check and docs build stop on a link that leads nowhere, in a Markdown page and in a page written as a component (M133, M134), on a file fetched from another origin (M137), a page that disappeared without a redirect (M131), an example that does not compile or throws; with --strict, also on an example app that cannot be built (M180, M181).
  • docs/site/tests/run.sh builds the site, serves it under /roots/, serves one version directory alone under another path, and opens every page with and without JavaScript: no console message, no request to another origin, no Content-Security-Policy violation. On the home page it follows every link and checks every section.
  • Fonts are files of the site (docs/site/public/fonts/, each with its licence). A page makes no request to any other origin.

Known limits #

  • A version directory served alone (only v/0.4.0/, without the site around it) asks for versions.json above itself, gets a 404 that the browser logs, and shows its own version only. The pages work.
  • The five headings of the 0.4.0 overview that moved from / to /docs (/#start-here and the others) are redirected by the app. Without JavaScript such an address shows the home page: a static host does not see the part after #.
  • The links of the home page into the repository name the commit the version was built from. They work for a reader once that commit is on GitHub and the repository is public.