Getting started

1. Write a configuration

In the root of your repository, run:

zenith init

It writes a zenith.toml for the workspace packages it finds. With two or more packages, its one contract keeps them free of dependency cycles:

#:schema https://raw.githubusercontent.com/feds01/zenith/main/zenith.schema.json

[contracts.packages-acyclic]
type = "acyclic"
modules = ["pkg:*"]

The #:schema line lets editors with a TOML language server (Taplo, or Even Better TOML in VS Code) complete and check the file.

zenith reads JavaScript and TypeScript by default. For a project in any other language, or one that mixes several, enable each language with its table, such as [languages.python]: see languages.

2. Check the contracts

zenith check

zenith finds the project's source files, parses and resolves their imports in parallel, and checks every contract against the import graph. Each violation is shown with the code that makes it, then a line per contract and a summary:

Contracts
  ✗ packages-acyclic  acyclic       broken (1 violation)

0 kept, 1 broken · 2 files, 2 imports

zenith check exits with 1 when a contract is broken, so it can fail a CI job, as with zenith's GitHub Action.

3. Add contracts

Describe the architecture you want, a rule at a time: see the contract types. Three commands help while writing them:

  • zenith suggest prints contracts that the code already keeps, inferred from the import graph, ready to add to zenith.toml: see suggesting contracts.
  • zenith graph web/src --depth 1 shows a directory's children and the imports between them.
  • zenith why web/src/lib web/src/app shows why one part imports another, chain by chain.

4. Adopt it on an existing codebase

A large codebase usually breaks its contracts in many places at first. zenith baseline records the existing violations, so that zenith check only fails on new ones, and the baseline shrinks as they are fixed.