Developer Guide
How to build, test, and run everything in the repository from a checkout.
Folioh ships as one native Rust binary: folioh.
The repository is one Cargo workspace plus the site template the binary renders
through. This guide covers building the binary, running the tests, and working
on the template.
Prerequisites
| Tool | Needed for | Notes |
|---|---|---|
| Rust toolchain | The workspace | Install via rustup . rust-toolchain.toml pins the stable channel with clippy and rustfmt; rustup picks it up automatically. |
| Node.js 20.19+ and pnpm 10 | folioh build, folioh serve, and template work | The generated site renders through the bundled Nextra/Next.js template. Not needed to build the binary or run cargo test. Get pnpm with corepack enable pnpm && corepack prepare pnpm@10 --activate, or npm install -g pnpm@10. |
Get the source
git clone https://github.com/pguijas/folioh
cd foliohRepository layout
The root Cargo.toml coordinates the packages in one virtual workspace.
crates/*contains the eleven engine crates: the CLI host (folioh-cli), configuration, parsers, page generation, plugin interfaces, site builder and watcher.folioh-cliis both the library and the package that builds the releasedfoliohbinary, fromsrc/bin/folioh.rs.
Beside the crates:
docs/examples/— the small example projects the guides build and the tests read. What a build must produce from them is checked in as a golden beside the crate that produces it, undercrates/<crate>/tests/fixtures/.template/— the Nextra/Next.js site template, built with pnpm.docs/— the guides on this site. The rootdocs.yamlis the config of Folioh’s own site andtheme/folioh-siteits theme.install.sh— the installer behind the install one-liner.
Build and verify
Run these from the repository root. The first three are the Rust checks CI
runs (CI adds --locked); What CI runs lists the rest.
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo build --releasecargo fmt --all --checkverifies formatting without rewriting files; drop--checkto apply it.cargo clippy --workspace --all-targets -- -D warningslints every crate and target. CI treats warnings as errors with-D warnings; match it locally to avoid surprises.cargo test --workspaceruns the tests across all crates.cargo build --releaseproducestarget/release/foliohusing the thin-LTO, stripped release profile.
Installing the binary you built
The release build is a single self-contained executable. Copy
target/release/folioh to a directory on your PATH (for example
~/.local/bin/) and run folioh --version to confirm the shell picks up your
build rather than an installed release. To run a command without installing,
cargo run --release -p folioh-cli -- build --clean builds and runs the same
binary.
Tests
cargo test --workspace runs every crate’s tests. Unit tests live in a
separate file beside the module they test: src/sidebar.rs declares
#[cfg(test)] #[path = "sidebar_tests.rs"] mod tests; and loads
src/sidebar_tests.rs. Tests for lib.rs, main.rs, or mod.rs use a
tests.rs file in the same directory. This keeps access to private helpers
without mixing the test bodies into implementation files, and a directory
under src/ only ever holds submodules. Shared test-only helpers use
separate child modules the same way.
Crate integration tests live under each crate’s tests/ directory. The CLI
suites live in crates/folioh-cli/tests/, including the documentation
checks in docs_surface.rs: that is the package that builds the binary, so
they keep testing the actual executable.
The same directory holds the release surface checks (release_surface.rs),
workspace layout rules (workspace_boundaries.rs), and the shared support the
CLI suites load with mod common; (common/).
cargo test -p folioh-cli runs all of these targets.
Narrow the run while iterating:
cargo test -p folioh-config # one crate
cargo test -p folioh-cli --test release_surface # one integration test file
cargo test -p folioh-cli the_one_liner # tests whose name contains a stringA new behaviour lands with its test: a unit test when it is local to one
function, an integration test under the crate’s tests/ when it crosses crates
or touches the filesystem, and a black-box run of the binary when it is what a
user sees. The CLI tests in crates/folioh-cli/tests/ run the built binary
through env!("CARGO_BIN_EXE_folioh") against a project from docs/examples/
and check the result against the checked-in golden. The fixtures and the tests
are the definition of correct output.
Each crate keeps the fixtures its own tests read under
crates/<crate>/tests/fixtures/: the config that must load without a warning
(folioh-config), the sources each reader must parse with the IR they must
parse to (golden_ir.json in folioh-lang-javascript and folioh-lang-rust;
rich_package_ir.json and example_package_ir.json in folioh-lang-python,
the second for the example package under docs/examples/generated-site/), the
MDX contract baseline (folioh-plugins) and the pages, llms.txt and
llms-full.txt a build must write (folioh-docs, compared again through the
CLI by folioh-cli). FOLIOH_UPDATE_GOLDEN=1 rewrites the goldens of these
tests:
FOLIOH_UPDATE_GOLDEN=1 cargo test -p folioh-lang-python --test golden
FOLIOH_UPDATE_GOLDEN=1 cargo test -p folioh-lang-javascript --test golden
FOLIOH_UPDATE_GOLDEN=1 cargo test -p folioh-lang-rust --test golden
FOLIOH_UPDATE_GOLDEN=1 cargo test -p folioh-docs --test example_site
FOLIOH_UPDATE_GOLDEN=1 cargo test -p folioh-plugins --test template_pinsThe diff is the point: a golden regenerated without reading it turns a regression into the contract.
Benchmarks
crates/folioh-lang-python/benches/parse.rs is a criterion
parse-throughput benchmark over the *.py files under $FOLIOH_BENCH_CORPUS.
The crate declares it as a [[bench]] target with harness = false and keeps
criterion as a dev-dependency, so cargo bench -p folioh-lang-python runs it.
The template
Everything folioh build renders comes from template/, a
Next.js/Nextra workspace. Work on it directly with pnpm:
cd template
pnpm install
pnpm run devpnpm run lint and pnpm run typecheck are the template’s own checks.
What CI runs
One workflow gates changes: rust (.github/workflows/rust.yml). It runs on
pushes and pull requests to main and develop and on v* tags, and the
release workflow calls it before building. Its jobs install the stable
toolchain and cache the build:
lint(Linux) runscargo fmt --all --checkandcargo clippy --workspace --all-targets --locked -- -D warnings, then the template’spnpm install --frozen-lockfile,pnpm lintandpnpm typecheckon Node 22 and pnpm 10.test(Linux and macOS) runscargo test --workspace --lockedon Node 22.windowsrunscargo test -p folioh-cli --test cli_surface --locked.real-export(Linux) runs the two suites ignored by default because they run a real pnpm install and Next export:cargo test -p folioh-site --test e2e_export --locked -- --ignoredandcargo test -p folioh-cli --test build_pipeline --locked -- --ignored.
CI does not run cargo build --release; the release workflow does.
Releasing
A release is a tag. Bump version in the root Cargo.toml and project.version
in docs.yaml to the same string, spelled the Cargo way (0.3.0-a1; the tests
check they agree), record the changes in CHANGELOG.md under a
## <version> — <date> heading, merge, then tag v<version> and push the
tag:
git tag v0.3.0-a1
git push origin v0.3.0-a1.github/workflows/release.yml fails before building if the tag does not match
the workspace version. It then builds one archive per target on five
targets (Linux and macOS on x86_64 and aarch64 as .tar.gz with the folioh
binary at the archive root, which install.sh installs, and Windows x86_64 as
a .zip to download by hand), runs
install.sh against the two archives the runners execute natively (x86_64
Linux, arm64 macOS), and publishes a GitHub Release with the archives, a
SHA256SUMS file and the version’s CHANGELOG.md entry as its notes; a
version with no entry fails the release. install.sh downloads from
releases/latest, so releases are published as regular releases, not
prereleases, while the version string carries the alpha.
Pushing a tag is the publication decision; only the repository owner tags. Pull requests that touch the workflow or the installer run the build matrix without publishing, so the release path is proven before a tag exists.
Next steps
- Architecture — How the CLI, parser, generator, and export pipeline fit together
- CLI Reference — Every command, flag, and option
- Writing Doc Comments — How Folioh reads Python docstrings, JSDoc and Rust doc comments