Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,6 @@ node_modules/
.DS_Store
.superpowers/
.playwright-mcp/

# Generated by `contributor-network build` from python/contributor_network/templates/index.html.j2
/index.html
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ Edit `config.toml` to configure:
- **contributors.devseed**: Current DevSeed employees (format: `github_username = "Display Name"`)
- **contributors.alumni**: Friends and alumni (commented out by default)

### SEO / meta tags

The following `config.toml` fields populate `<meta>` tags in `index.html` at build time:

- `og_url` — the canonical URL for the deployed site (used in `og:url`)
- `og_image` — the URL to the social-share image (used in `og:image`)
- `theme_color` — the browser theme color (used in `<meta name="theme-color">`); defaults to `branding.primary_color` if unset

`index.html` is rendered from [`python/contributor_network/templates/index.html.j2`](./python/contributor_network/templates/index.html.j2) by `contributor-network build` and is gitignored. To change page structure, edit the template (not the generated file). Forks can override the meta-tag values without modifying the template.

## Branding

This visualization uses the Development Seed brand colors:
Expand Down
3 changes: 3 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ title = "The Development Seed Contributor Network"
description = "An interactive visualization of contributors to Development Seed code and their connections to other repositories"
organization_name = "Development Seed"
organization_nickname = "DevSeed"
og_url = "https://developmentseed.org/contributor-network/"
og_image = "https://developmentseed.org/contributor-network/site-image.jpg"
theme_color = "#CF3F02"

# Repositories
repositories = [
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"click>=8.2.1",
"jinja2>=3.1.4",
"pydantic>=2.11.9",
"pygithub>=2.8.1",
]
Expand Down
24 changes: 24 additions & 0 deletions python/contributor_network/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,32 @@

import click
from github import Auth, Github
from jinja2 import Environment, FileSystemLoader, StrictUndefined

from .client import Client
from .config import Config
from .models import Link, Repository

TEMPLATES_DIR = Path(__file__).absolute().parent / "templates"


def render_index_html(config: Config) -> str:
"""Render the index.html template from a Config."""
env = Environment(
loader=FileSystemLoader(TEMPLATES_DIR),
autoescape=True,
undefined=StrictUndefined,
)
template = env.get_template("index.html.j2")
return template.render(
organization_name=config.organization_name,
description=config.description,
og_url=config.og_url,
og_image=config.og_image,
theme_color=config.resolved_theme_color,
)


ROOT = Path(__file__).absolute().parents[2]
DEFAULT_CONFIG_PATH = "config.toml"
directory = click.option(
Expand Down Expand Up @@ -153,6 +174,9 @@ def build(directory: Path, config_path: str | None, all_contributors: bool) -> N
)
print(f"Generated config.json in {directory}")

(ROOT / "index.html").write_text(render_index_html(config))
print(f"Generated index.html at {ROOT / 'index.html'}")


@main.command()
@config
Expand Down
8 changes: 8 additions & 0 deletions python/contributor_network/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ class Config(BaseModel):
contributors: dict[str, dict[str, str]] # Nested: {"core": {...}, "alumni": {...}}
contributor_padding: int = 40
branding: BrandingConfig = BrandingConfig()
og_url: str = ""
og_image: str = ""
theme_color: str = ""

@property
def resolved_theme_color(self) -> str:
"""Theme color falls back to branding.primary_color when unset."""
return self.theme_color or self.branding.primary_color

@property
def core_contributors(self) -> dict[str, str]:
Expand Down
14 changes: 7 additions & 7 deletions index.html → ...ntributor_network/templates/index.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Contributor Network</title>
<title>The {{ organization_name }} Contributor Network</title>

<meta name="author" content="Development Seed">
<meta name="description" content="An interactive visualization of contributors and their connections to repositories">
<meta name="theme-color" content="#cf3f02">
<meta property="og:title" content="The Development Seed Contributor Network">
<meta property="og:url" content="https://developmentseed.org/contributor-network/">
<meta name="author" content="{{ organization_name }}">
<meta name="description" content="{{ description }}">
<meta name="theme-color" content="{{ theme_color }}">
<meta property="og:title" content="The {{ organization_name }} Contributor Network">
<meta property="og:url" content="{{ og_url }}">
<meta property="og:type" content="article">
<meta property="og:locale" content="en_US">
<meta property="og:image" content="https://developmentseed.org/contributor-network/site-image.jpg">
<meta property="og:image" content="{{ og_image }}">
<meta property="og:image:type" content="image/jpeg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="800">
Expand Down
26 changes: 26 additions & 0 deletions python/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,29 @@

def test_config() -> None:
Config.from_toml(Path(__file__).parents[2] / "config.toml")


def test_config_meta_fields_have_defaults():
config = Config.from_toml(Path(__file__).parents[2] / "config.toml")
assert config.og_url == "https://developmentseed.org/contributor-network/"
assert (
config.og_image
== "https://developmentseed.org/contributor-network/site-image.jpg"
)
assert config.theme_color == "#CF3F02"


def test_config_meta_fields_optional_in_toml(tmp_path):
minimal = tmp_path / "minimal.toml"
minimal.write_text(
'title = "X"\n'
'description = "Y"\n'
'organization_name = "Z"\n'
"repositories = []\n"
"[contributors.core]\n"
)
config = Config.from_toml(minimal)
assert config.og_url == ""
assert config.og_image == ""
assert config.resolved_theme_color == "#CF3F02"
assert config.theme_color == ""
Loading
Loading