Skip to content

manni site

Start, build and preview the docs site, whatever framework it runs on.

Every docs framework starts its site differently. Mintlify is mint dev, Starlight is astro dev, MkDocs is mkdocs serve, and each picks its own port. manni site finds the site, detects its framework from its files, and runs that framework’s own command. One command per job works in every docs repository manni detects.

manni site is not a document tool. It runs the site that every other domain checks. manni a11y check crawls the site it serves, on the URL your collection already names.

CommandDoes
manni site start [dir]Runs the framework’s dev server, with hot reload, until Ctrl-C.
manni site build [dir]Runs the framework’s production build once.
manni site preview [dir]Runs build, then serves the output until Ctrl-C. It always builds first, so it never serves stale output.
manni site annotate [dir]Joins the graph’s pins and statements to the built pages, as data. See Show readers what is proven.

There is no default subcommand. manni site alone prints usage and exits 2. Each command prints one line on stderr before it runs anything. The line names the framework, the directory and the exact command. After that the output is the framework’s own.

This repository’s site is Starlight, in docs/. Its manni.config.yaml sets only site.annotate.after-build, and a collection whose url: is http://127.0.0.1:4321/manni/. After manni graph publish, build, annotate and serve the site:

Terminal window
$ manni site preview
manni: Starlight in docs/. Running npm run build
manni: Starlight in docs/. Annotating docs/dist from docs/public/graph/chunks.json.
docs/dist: 258 pages annotated. 4163 of 4202 pins placed, 39 listed on their page only. 2 blocks carry a verification.
manni: Starlight in docs/. Running npm run preview -- --host 127.0.0.1 --port 4321

The host and the port came from the collection url:. In a second terminal, the accessibility check reads the same url: and crawls the served build with no flags:

Terminal window
manni a11y check

Ctrl-C stops the server, exit 0.

  1. The directory you pass, as in manni site start website.
  2. Else site.dir in manni.config.yaml.
  3. Else a search, where the first match wins. It looks in the config file’s directory, or the working directory when there is no config. Then it looks in that directory’s docs/, website/ and site/.

In each directory the framework’s own files decide. A mint.json is Mintlify, an @astrojs/starlight dependency is Starlight, and a mkdocs.yml is MkDocs. There is no framework: key, because the files already say which framework it is. A directory with two frameworks in it is an error, exit 2, which names both and asks for an override.

manni detects Mintlify, Fern, Starlight, Docusaurus, VitePress, Nextra, Fumadocs, Rspress, MkDocs, Zensical, Sphinx, Hugo, Jekyll and MadCap Flare. The framework table has each one’s marker, commands and default port.

  • A Node framework runs the project’s own script when package.json has one, such as npm run dev, because it carries the project’s flags. Without a script, it runs the framework’s binary through the package manager the project uses.
  • A Python tool runs under uv run or poetry run when the project has a uv.lock or poetry.lock. Jekyll runs under bundle exec.
  • A framework with no preview server of its own writes a static directory. MkDocs, Zensical, Sphinx, Hugo, Jekyll and Flare are in this group. preview serves that directory with a small server built into manni, bound to 127.0.0.1.

Mintlify and Fern have no local build, so manni site build and manni site preview stop with exit 2 there. manni site start works. Flare is the reverse. It has no development server, so start exits 2, and preview serves the last build. Check a MadCap Flare project covers it.

manni a11y check reads a collection’s url: as the place to start its crawl. manni site start and manni site preview read the same url: for the port and host to bind, so the two agree without being told:

manni.config.yaml
collections:
- name: site
paths: ["docs/**/*.md"]
url: http://127.0.0.1:4321/

Only a local url: counts, on localhost, 127.0.0.1 or [::1]. A public URL names the deployed host, not a port on your machine. --port and --host win over the url:. With neither a flag nor a local url:, the framework’s default port applies, and manni passes no port flag at all. When two collections name different local URLs, manni warns and uses the framework’s default.

Write the commands in manni.config.yaml. Each one is a whole command line, run through the shell in the site’s directory:

manni.config.yaml
site:
dir: handbook
commands:
start: pnpm dev --port 4000
build: pnpm build
preview: npx serve dist -l 4000

A command you set replaces the detected one for that verb, and the others stay detected. preview still runs build first. When every command a verb needs is set, nothing is detected, so any framework works this way. --port and --host do not apply to a command you wrote. Put the port in the command.