The main goal of Landr is to generate useful and decent websites to describe a software project with the least possible amount of configuration. Landr is supposed to be opinionated by design and rely on common open source conventions rather than on user input.
Landr reads all the information it needs to know about a repository from a file called
meta.json, which is supposed to be the only input for Landr. This is a pretty big JSON file generated by a tool such as scrutinizer, which scans the content of the repo, the GitHub API, the package managers where the repository is published (i.e. npmjs.org), and much more. Check Landr's
meta.json as an example.
Right now Landr works on the assumption that the
meta.json file is there by the time Landr runs. Within Balena, we do this by setting up Balena CI to run
scrutinizer on every PR merge and making the
balena-ci user commit the file to
master. This is a step that makes it hard for people outside Balena to use Landr, and we hope to sort this out in the short term.
The amount of information we can obtain from a repository differs on the project and on how standard the conventions that the project is adopting are, so most of the things that
meta.json might contain is optional.
The routes that Landr will statically generate are defined in
lib/routes.js. This file contains a function that takes
meta.json and returns an array of routes that Landr will render. Each route contains the following properties:
title: The page title, most often used inside
path: The page path, as an array of fragments. For example
[ 'foo', 'bar' ]corresponds to
context: A free-form object that represents the page's focus information. For example, a page that renders the project's code of conduct will have the code of conduct plus any related information as its
The order in which routes are returned doesn't make a difference.
The Landr project ships with a collection of React component that represent the library of UI section that Landr will use when rendering a website. These components live in
lib/components/. Each component has the following properties:
name: The unique name of the component
render: A function that takes a
propsobject and returns a React component
variants: The different ways this component can be rendered given a
meta.json. We will explain this in more detail later
Each component has a
variants function that takes information such as
meta.json and a route
context and returns an array defining all the ways that the component is happy to render itself given the information it has, sorted by order of preference.
For example, a jumbotron may have a title, a subtitle, and a primary action button. Our jumbotron component may define that its happy to render itself in the following ways:
If a "choice" is returned before another choice, then the component expresses that it prefers to be rendered in the first way, but is happy to take any choice. We internally call these preferences the rank.
The Landr engine will go through each of the routes defined for a website, and it will generate all possible combinations of components and the choices they are willing to consider when rendering themselves. A lot of these options will be invalid, such as saying that we want a footer before the navigation bar, so Landr also has a set of declarative rules to filter out invalid component combinations. These rules live in
lib/rules.js and express things such as:
And many more.
Landr will check every combination it generated against these rules, keep the valid ones, and choose the one where the components are rendered with the highest possible ranks.
Once we figure out what to display and how, we generate React components and pass them to React Static to get you some lovely static files!