Skip to content

Latest commit

 

History

History
327 lines (262 loc) · 14 KB

File metadata and controls

327 lines (262 loc) · 14 KB

Build scripts

How to assemble a complete local copy of libtmux.org, and how the version manifest it serves is produced.

Prerequisites

Only Node and pnpm are required. Everything else — uv, bun, doxygen, docfx, swift — is optional: each self-hosted port's reference generator is skipped, not fatal, when its toolchain isn't on PATH or isn't fully usable (see "Reference generator statuses" below).

Two of them need help finding themselves, and the build supplies it rather than asking you to. bun installed through npm leaves a shim whose postinstall never ran; a working copy in a mise install directory is preferred over it. docfx is a .NET global tool, so it needs DOTNET_ROOT set — mise installs the SDK outside every location the apphost searches, and without it docfx exits with "You must install .NET to run this application" even though .NET is right there. Both are silent no-ops on a machine that has neither.

Full local build

$ ./scripts/build-site.sh

This assembles _site/ at the repo root:

  • The Astro shell, built once for shared prose (landing page, concepts, guides, examples, parity) at the site root.
  • The Astro shell again, once per self-hosted port times version (latest, stable by default), each with LIBTMUX_DOCS_BASE and the version env vars set to that port+version's own values, output under _site/<port>/<version>/.
  • Each self-hosted port's reference generator, run once per port+version and copied to _site/<port>/<version>/api/ when its toolchain is usable and the checkout has the generator's config/entrypoint in place.
  • One Pagefind pass over the assembled _site/ tree, so search spans the shell and every generator's output together.

Ecosystem ports (Rust, Go, Java — see site/src/lib/ports.ts) get no version subtree at all: the site links out to their canonical host (docs.rs, pkg.go.dev, javadoc.io), so there is nothing local to build.

The script ends with a summary table, one row per port+version (a single row for ecosystem ports, which have no version subtree):

PORT     VERSION  MODE           STATUS     REASON
py       latest   sphinx         built      [Sphinx + sphinx-gp-theme] ...
py       stable   sphinx         built      [Sphinx + sphinx-gp-theme] ...
ts       latest   astro          skipped    [@microsoft/api-extractor JSON] bun not usable: ...
rs       -        ecosystem      n/a        deep-links to docs.rs, no local output

Reference generator statuses

  • built — the generator produced final HTML, copied into <port>/<version>/api/. True today for Sphinx (Python, C++) and DocC (Swift), which render their own final pages.
  • model-only — the generator ran and produced an intermediate model (TypeScript's docs/api.md, .NET's docfx YAML), but nothing is copied: the shell has no page route yet that renders that model into HTML at /<port>/<version>/api/. site/src/lib/ports.ts's referenceUrl() already promises that URL; closing this gap means either wiring a content-collection loader + page route for it, or moving that port to referenceMode: 'ecosystem'.
  • skipped — the toolchain isn't usable (absent from PATH, or present but broken — see below) or the checkout doesn't have the generator's config/entrypoint yet. Never aborts the run.
  • failed — the toolchain is usable and the generator was actually invoked, but it exited non-zero. Recorded in the table, logged under _site/.build-logs/<port>-<version>.log, and makes the script exit 1 after printing the full table — every other port still gets built.

A toolchain is checked by actually running it (uv --version, and so on), not just by resolving it on PATH: an installed-but-broken binary (seen in this sandbox — an npm-shimmed bun whose postinstall was skipped) is treated the same as an absent one, a skipped row with the first line of the error, rather than surfacing as a confusing generator failed.

Side effect worth knowing about: the TypeScript generator is that repo's own documented invocation, bun run docs:api (~/work/libtmux/libtmux-ts's AGENTS.md: "Run bun run docs:api and commit the result") — it writes packages/libtmux/docs/api.md in that checkout, a tracked file. Running a full build with a working bun toolchain may leave a real diff there; review or discard it as you would any other generated-file diff.

A broken shell build (an actual Astro/content error, not a missing reference toolchain) aborts the whole run — that's not something this script tries to work around, since it means the site itself doesn't build, not that one port's reference is unavailable.

Options

$ ./scripts/build-site.sh --ports py,ts --versions latest
$ ./scripts/build-site.sh --skip-refs
$ ./scripts/build-site.sh --skip-pagefind

--ports limits which self-hosted ports get a shell build and a reference generation pass (comma-separated slugs from site/src/lib/ports.ts). --versions overrides the default latest,stable. --skip-refs builds only the shell and search index. --skip-pagefind skips the final indexing pass, useful while iterating on everything before it.

Port CI binds any named tree to one source checkout. All identity inputs are required together; branches, PRs, tags, and aliases are refused without them:

$ LIBTMUX_DOCS_PORT=ruby \
    LIBTMUX_DOCS_VERSION=pr-42 \
    LIBTMUX_DOCS_VERSION_KIND=pr \
    LIBTMUX_DOCS_SOURCE_REF=0123456789abcdef0123456789abcdef01234567 \
    LIBTMUX_DOCS_SOURCE_SHA=0123456789abcdef0123456789abcdef01234567 \
    LIBTMUX_DOCS_IS_DEFAULT=false \
    LIBTMUX_DOCS_CHECKOUT_RUBY=/workspace/source \
    ./scripts/build-site.sh \
    --ports ruby \
    --versions pr-42 \
    --skip-refs \
    --skip-pagefind

An alias also sets LIBTMUX_DOCS_RESOLVES_TO to the immutable tag it names. The preflight verifies the ref, SHA, checkout HEAD, native export, API model, staged guides, examples, manifest identity, canonical URLs, and robots policy before producing the version subtree.

Checks

Versioned tmux CLI reference

The command reference uses the revisions in site/src/data/tmux/versions.json. Each snapshot combines that revision's manual with the corresponding binary's list-commands output. The generator starts and cleans up a private server. It also retains the upstream license and command source locations.

With mandoc 1.14.6 and binaries installed as <binaries>/<version>/bin/tmux, regenerate the committed snapshots:

$ node scripts/gen-tmux-reference.mjs --source ~/study/c/tmux \
    --binaries ~/.local/share/libtmux-tmux-matrix --mandoc mandoc

Verify that the snapshots match both the pinned source and installed binaries:

$ node scripts/gen-tmux-reference.mjs --source ~/study/c/tmux \
    --binaries ~/.local/share/libtmux-tmux-matrix --mandoc mandoc --check

The formatter's build date and host labels are excluded. Command syntax, descriptions, manual anchors and the source hashes remain version-specific.

Site checks

Four scripts assert things the build itself cannot notice. None of them needs an assembled site; all four are fast enough to run before a commit.

$ node scripts/check-citations.mjs

Every source path the prose cites must exist in the port it names — the file="…" fences, the // From <path> comments, and each page's "Where this comes from" table. A fence read from a checkout fails the build when its file disappears; a hand-quoted one does not, and that is the gap this closes. It does not require a hand-quoted excerpt to match its source byte for byte: several are excerpts with a clarifying comment added, which is what those tables already say.

$ node scripts/gen-mcp-tools.mjs

Regenerates site/src/data/mcp-tools.json, the cross-port MCP tool matrix /mcp/tools/ renders. Pass --check to fail instead of writing when the checked-in file is stale. It refuses to write a matrix unless its Python rule reproduces libtmux-mcp's own documented tool set name for name, every port declaring a wire prefix carries it on every tool, and every configured MCP checkout is present — a partial matrix looks exactly like a finding. Ports whose product status is unpublished, such as Lua, are excluded explicitly.

$ node scripts/gen-registry.mjs

Regenerates site/src/data/registry.json, which records what each port's package registry carries right now: a stable release, a prerelease only, or nothing at all. installCommand in ports.ts composes that with the install spellings to produce the command a page or an agent prompt shows, so the command cannot name a version the registry does not have.

It matters because most ports have no stable release, and several resolve nothing unless the prerelease is named: Cargo treats an alpha as out of range for a plain requirement, go get without a version resolves the highest release and a prerelease is not one, and SwiftPM's from: excludes prereleases from its range.

The published site does not read the committed file. deploy-shell.yml resolves the registry against the live registries on every deploy, including a scheduled one four times an hour, and publishes the result as /registry.json; a release therefore reaches the site without a commit here. The committed file is what pull requests, local builds and tests use, so their results never depend on another repository publishing, and nothing requires refreshing it.

Pass --check to fail instead of writing when a file differs from what the script would write. Pass --offline to re-emit the baseline without contacting any registry, for a job that must not depend on ten third-party services; the test gate runs --check --offline. Pass --baseline <file> to fall back on a file other than the committed one, as the deploy does with the copy it last published. A probe that fails for one port keeps that port's baseline entry rather than reporting it unpublished, the same way gen-versions.mjs falls back to its seed.

A tag is recorded only once the registry carries its version. A port tags first and publishes minutes later, and a tag with no package behind it would send an install command to nothing.

Release tags are read per port. Rust is a Cargo workspace and tags each crate (libtmux@v0.1.0-alpha.10); Go is multi-module and tags submodules under a path (mcp/v0.0.1-alpha.9) while the library tags bare. tagPrefix in ports.ts declares the first case, and its absence rejects any tag carrying / or @, so a submodule's release is never read as the library's.

Assemble a complete preview:

$ LIBTMUX_DOCS_LOCALES_ROOT=/pr-42 \
    LIBTMUX_DOCS_VERSION=pr-42 \
    LIBTMUX_DOCS_VERSION_KIND=pr \
    pnpm build:site --versions latest

Audit the assembled preview before publishing:

$ bash scripts/check-preview.sh _site pr-42

Checks the actual preview artifact for URLs escaping its prefix and internal links whose pages or fragments are missing. The deployment workflow runs this audit before uploading the complete preview, including its port/version trees.

$ node scripts/audit-site.mjs && node scripts/crawl-site.mjs

These two do need an assembled _site/: the first flags empty, thin and admonition-rendering pages, the second follows every internal link from /.

Version manifest

site/public/versions.json is a committed seed with a latest trunk entry per port, so the version switcher (see site/src/components/VersionSwitcher.astro) has something to render before any real deploy has produced a richer manifest. build-site.sh regenerates a fuller one from the checkouts it finds and writes it over the seed at _site/versions.json, so a local assembly shows real tags where a checkout is present.

To regenerate the manifest by hand, or to check what a fresh derivation looks like:

$ node scripts/gen-versions.mjs

Writing straight to a file instead of stdout:

$ node scripts/gen-versions.mjs --out site/public/versions.json

Entries are derived per port from each checkout named in site/src/lib/ports.ts (~/work/python/libtmux, and so on): git tags matching that port's grammar become tag entries, local branches matching vX.x become branch entries, the checkout's current HEAD is always latest (kind trunk), and stable is an alias resolving to the newest non-prerelease tag. A prerelease-only port gets next; it does not get a fabricated stable. RubyGems and LuaRocks release spellings keep their native forms. A port whose checkout is absent falls back to the same latest-only seed that --seed produces, with a note on stderr. Set LIBTMUX_DOCS_CHECKOUT_<PORT> to derive refs from an exact CI checkout.

To regenerate the committed seed itself (only needed if the manifest shape in site/src/lib/versions.ts changes):

$ node scripts/gen-versions.mjs --seed --out site/public/versions.json

--overrides <path> merges a partial manifest over the derived one — entries merge by slug within each port, defaultVersion keys replace outright:

$ node scripts/gen-versions.mjs --overrides overrides.json

Known gap: search only works from the site root today

site/src/pages/search.astro builds its Pagefind bundle URL from import.meta.env.BASE_URL — correct for the one Pagefind index Starlight or a single-version site would have, but this site runs Pagefind exactly once over the whole assembled _site/ tree (/pagefind/...), not once per port+version. A search page rendered under _site/<port>/<version>/search/ computes /<port>/<version>/pagefind/..., which doesn't exist — only the root-level _site/search/ page's BASE_URL of / happens to line up. SiteHeader.astro's search link already points at the root /search/ unconditionally, so this doesn't break site navigation today, but the per-port/version copies of that page are dead weight and would actively break if anything ever links to them directly. Worth an absolute /pagefind/ path in search.astro rather than a BASE_URL-relative one — flagged here for whoever owns that file, not fixed here.