Zenith
zenith is a language-agnostic architecture linter for monorepos.
You write down how the parts of your codebase may depend on each other, as
contracts in a zenith.toml, and zenith check reports every import, or
chain of imports, that breaks one. A check of a monorepo with thousands of
files takes well under a second.
If you have used import-linter in Python, the contracts will feel familiar: zenith brings the same ideas to monorepos, whatever their languages.
An example
Two contracts: the first puts the app's code in layers, so that lower layers never import higher ones, and the second keeps server-only code out of client bundles:
[contracts.web-layers]
type = "layers"
layers = ["web/src/app", "web/src/components", "web/src/lib"]
guidance = "Lower layers must not import higher ones."
[contracts.client-safety]
type = "forbidden"
from = [{ path = "web/src/**", directive = "use client" }]
to = ["npm:sharp", "node:*", "npm:server-only"]
guidance = "Keep server-only modules out of client bundles."
zenith check finds a component that imports from the app layer above it, and
a client component that reaches sharp through another module. It shows each
violation with the import, or the chain of imports, that makes it, and how to
fix it, then a line per contract:
error[web-layers]: `web/src/components` must not import `web/src/app`
--> web/src/components/sidebar.tsx:3:32
|
3 | import { DASHBOARD_TABS } from "@/app/tabs";
| ^^^^^^^^^^^^ imports `web/src/app/tabs.ts`
|
= help: move the imported code into a module that may be imported here, or remove this import
= help: Lower layers must not import higher ones.
error[client-safety]: `web/src/components/upload.tsx` must not import `npm:sharp`
--> web/src/components/upload.tsx:3:24
|
3 | import { resize } from "@/lib/images";
| ^^^^^^^^^^^^^^ starts a chain of 2 imports
|
= note: import chain:
web/src/components/upload.tsx:3
-> web/src/lib/images.ts:1
-> npm:sharp
= help: remove this import, or one further along the chain
= help: Keep server-only modules out of client bundles.
Contracts
✗ web-layers layers broken (1 violation)
✗ client-safety forbidden broken (1 violation)
0 kept, 2 broken · 412 files, 968 imports
What it does
- Seven contract types:
forbidden,independence,layers,protected,acyclic,encapsulationandlimits. - Several languages: JavaScript and TypeScript, Go, Python, Rust and Terraform, in one import graph, with the same contracts for each.
- Monorepo aware: workspace packages, Go modules, Python packages and Cargo
crates are found automatically, and can be named in contracts with
pkg:selectors. - Built for adoption: a baseline records the violations a codebase already has, so only new ones fail, and suppressions exempt single imports.
- Made for CI: a stable exit code, and
compact,jsonandgithuboutput formats.