The Problem
Organizations that publish an open‑source landscape need a repeatable way to turn structured data (YAML, logos, optional GitHub/Crunchbase enrichment) into a polished, searchable website. Maintaining a custom build pipeline for each iteration quickly becomes a manual, error‑prone effort.
What This Does
landscape2 is a Rust‑based CLI (crates/cli/src/main.rs) that consumes four YAML sources—*data*, settings, guide, games—and produces a static site under ui/webapp. The core data model lives in crates/core/src/lib.rs and is shared with the WASM overlays in crates/wasm/. The React UI (ui/webapp/src) renders the generated JSON, while the embed packages (ui/embed, ui/embed-item) provide ready‑to‑drop iframe bundles.
Key files:
crates/cli/src/build/*.rs– fetches extra fields from GitHub (github.rs) and Crunchbase (crunchbase.rs).crates/cli/src/deploy/s3.rs– optional S3 upload of the final static assets.ui/webapp/src/index.tsx– bootstraps the React app, pulling inui/webapp/src/data.ts(the generated payload).
How It Is Wired
- Entry point –
crates/cli/src/main.rsparses CLI flags (paths to the four YAML files) and callscli::run()incrates/cli/src/lib.rs. - Data ingestion –
cli::run()loads the YAML files viaserde_yamlinto structs defined incrates/core/src/lib.rs. - Enrichment – optional network calls are orchestrated by the
buildmodule:build/github.rsreadsGITHUB_TOKENSenv var, queries the GitHub API, and augments each item’s repo metadata.build/crunchbase.rsreadsCRUNCHBASE_API_KEYand adds funding information. - Asset handling –
build/logos.rsresolves thelogofield against the location supplied in the settings file, downloading SVGs when needed. - Export –
build/export.rsserialises the fully‑populated data model to JSON underui/webapp/public/data.json. - Deploy (optional) –
deploy/s3.rscan push theui/webapp/distfolder to an S3 bucket; the Dockerfile incrates/cli/Dockerfilebuilds a minimal Alpine image that runs the CLI as its CMD. - Runtime – The React entry (
ui/webapp/src/index.tsx) imports the generated JSON, registers the type definitions fromui/webapp/src/types.ts(the highest‑fan‑in module – 57 imports) and renders the UI components inui/common/src/components/.
Blast‑radius hotspots
ui/webapp/src/types.ts– any change ripples to 57 downstream modules.ui/webapp/src/data.ts– participates in a circular import withutils/getFoundationNameLabel.ts; breaking the cycle reduces build‑time coupling.- Large files such as
ui/webapp/src/utils/itemsDataGetter.ts(720 LOC) andcrates/core/src/data.rsalso have wide impact and are prime candidates for refactoring.
How To Use It
# Clone the repo
git clone https://github.com/moses-y/landscape2
cd landscape2
# Build the CLI (requires Rust toolchain)
docker build -f crates/cli/Dockerfile -t landscape2-cli .
# or locally:
cargo build --workspace --release
# Prepare the four YAML inputs (see docs/config/*.yml)
# Example: data.yml, settings.yml, guide.yml, games.yml
# Run the generator
./target/release/landscape2 \
--data docs/config/data.yml \
--settings docs/config/settings.yml \
--guide docs/config/guide.yml \
--games docs/config/games.yml \
--output ui/webapp/public
# Build the UI
cd ui/webapp
yarn install --immutable # lockfile enforced
yarn build # produces ./dist
If you need the embed bundle, run the same CLI but point --output at ui/embed/public. The Docker image can be used in CI to produce artifacts without installing Rust locally.
Real‑World Use
A cloud‑native conference wants to publish a “Landscape of Tools” page each quarter. They store the master data.yml in a private repo, run the CLI in a GitHub Actions workflow, push the generated dist/ folder to an S3 bucket, and embed the site in their conference portal via the iframe bundle from ui/embed. The workflow only touches the CLI binary and the YAML sources – no manual UI work.
Code Health & Issues
Measured findings (static analysis)
- HIGH – Import cycle –
ui/webapp/src/data.ts↔ui/webapp/src/utils/getFoundationNameLabel.ts. Break the cycle by extracting shared types. - HIGH – Hub module –
ui/webapp/src/types.tsimported by 57 modules; keep stable, move volatile logic elsewhere. - HIGH – Oversized files –
ui/webapp/src/utils/itemsDataGetter.ts,crates/core/src/data.rs,ui/common/src/components/ItemModalContent.tsx(≈720 LOC each). Split by responsibility. - HIGH – Deep nesting – 39 files with indentation depth ≥ 7 (e.g.,
ui/webapp/src/layout/navigation/Footer.tsx). Refactor to early returns or smaller helpers. - MED – High branching density – 5 files with > 15 branches in < 60 lines (e.g.,
ui/webapp/src/utils/normalizeId.ts). Consider strategy tables. - HIGH – Duplicated code blocks – 543 identical 6‑line snippets across 57 files (e.g.,
crates/core/src/games.rs,ui/common/eslint.config.js). Consolidate into shared utilities.
Repository hygiene (audit)
- HIGH – Committed
.envfiles inui/embed-item/.env,ui/embed/.env,ui/webapp/.env. Remove, rotate credentials, add.env.example. - HIGH – GitHub Actions use mutable tags (
docker/login-action@v3,dtolnay/rust-toolchain@master). Pin to full SHAs. - HIGH – CI pushes directly to default branch (
.github/workflows/release.yml). Switch to PR‑based flow. - MEDIUM – No explicit
permissionsforGITHUB_TOKENin CI workflow. Declare least‑privilege. - MEDIUM –
yarn installruns without--immutable; enforce lockfile. - MEDIUM – Dockerfile base images (
rust:1-alpine3.23,alpine:3.23.2) unpinned; use digests. - MEDIUM – No dependency‑vulnerability scan in CI. Add
dependency-review-actionorosv-scanner. - MEDIUM – No pre‑commit secret‑scan hook. Add a secret‑gate.
- MEDIUM –
checkoutstep retains credentials; setpersist-credentials: false. - MEDIUM – Test coverage low (1 test file vs 225 source files). Expand tests, starting with high‑fan‑in modules.
The Bottom Line
landscape2 delivers a functional pipeline for generating static landscape sites, with a clear Rust CLI and a reusable React UI. The core generation flow is well‑defined, but the codebase suffers from high‑fan‑in hubs, import cycles, and oversized files that raise maintenance risk. Security hygiene needs urgent attention (committed .env, mutable action tags). It is suitable for teams comfortable with Rust and Yarn who can invest in refactoring and tightening CI, but not for projects that need an out‑of‑the‑box, low‑maintenance solution.