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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ Thumbs.db

# Playwright MCP session artifacts (console logs + page snapshots)
.playwright-mcp/

# Generated at build time by scripts/fetch-*.py — never committed.
# Schema-only examples (*.example.json) are committed alongside as documentation.
src/data/plugins_metadata.json
src/data/verdi-cli.json
src/data/pypi-stats.json
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@
"type": "module",
"version": "0.0.1",
"scripts": {
"fetch-data": "python3 scripts/fetch-verdi-cli.py && python3 scripts/fetch-pypi-stats.py && python3 scripts/fetch-registry.py",
"predev": "test -f src/data/verdi-cli.json && test -f src/data/pypi-stats.json && test -f src/data/plugins_metadata.json || npm run fetch-data",
"dev": "astro dev",
"prebuild": "python3 scripts/fetch-verdi-cli.py",
"prebuild": "npm run fetch-data",
"build": "astro build",
"preview": "astro preview",
"astro": "astro",
"update-cli": "python3 scripts/fetch-verdi-cli.py",
"update-stats": "python3 scripts/fetch-pypi-stats.py"
"update-stats": "python3 scripts/fetch-pypi-stats.py",
"update-registry": "python3 scripts/fetch-registry.py"
},
"dependencies": {
"@astrojs/react": "^4.4.2",
"@astrojs/sitemap": "^3.7.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"astro": "^5.17.1",
"fuse.js": "^7.3.0",
"js-yaml": "^4.1.1",
"react": "^19.2.4",
"react-dom": "^19.2.4"
Expand Down
Binary file added public/plugin-registry/aiida-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 19 additions & 4 deletions scripts/fetch-pypi-stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@
"""Fetch monthly PyPI download stats for aiida-core.

Run: python scripts/fetch-pypi-stats.py
Output: src/data/pypi-stats.json
Output: src/data/pypi-stats.json (gitignored — generated fresh on every build)

Intended to run at build time or periodically.
The generated JSON is committed to the repo and imported statically.
Wired into:
- `npm run prebuild` — always re-fetches before every build (deploy, CI).
- `npm run predev` — re-fetches only when any of the three data files is
missing (first clone). Subsequent `npm run dev` is
instant; refresh manually with `npm run fetch-data`.

Build/dev fails loudly if pypistats.org is unreachable or returns zero
downloads — there is deliberately no graceful fallback to stale data.
Expected output schema: src/data/pypi-stats.example.json.
"""
import json
import sys
import urllib.request
from datetime import datetime, timedelta, timezone
from pathlib import Path
Expand Down Expand Up @@ -52,10 +60,17 @@ def fetch_stats():
"package": "aiida-core",
}

if monthly_total <= 0:
raise RuntimeError("pypistats returned zero downloads — refusing to overwrite with empty data.")

out_path = Path(__file__).resolve().parent.parent / "src" / "data" / "pypi-stats.json"
out_path.write_text(json.dumps(result, indent=2) + "\n")
print(f"PyPI stats: {display} downloads/month ({result['period_start']} to {result['period_end']})")
return result

if __name__ == "__main__":
fetch_stats()
try:
fetch_stats()
except Exception as e:
print(f"fetch-pypi-stats: {e}", file=sys.stderr)
sys.exit(1)
59 changes: 59 additions & 0 deletions scripts/fetch-registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Fetch the AiiDA plugin registry metadata.

Run: python3 scripts/fetch-registry.py
Output: src/data/plugins_metadata.json (gitignored — generated fresh on every build)

Pulls the pre-built metadata from the upstream aiida-registry, which
runs its own daily cron to scrape PyPI, parse pyproject.toml/setup.cfg,
test installation, and emit JSON. We just consume that artefact so this
site stays a thin presentation layer over the canonical registry.

Wired into:
- `npm run prebuild` — always re-fetches before every build (deploy, CI).
- `npm run predev` — re-fetches only when any of the three data files is
missing (first clone). Subsequent `npm run dev` is
instant; refresh manually with `npm run fetch-data`.

For nightly freshness in production, a Cloudflare Pages deploy hook re-triggers
the build, which re-runs prebuild. Build/dev fails loudly if upstream is
unreachable or the schema drifts — there is deliberately no graceful fallback
to stale data. Expected output schema: src/data/plugins_metadata.example.json.
"""
import json
import sys
import urllib.request
from pathlib import Path

UPSTREAM = "https://aiidateam.github.io/aiida-registry/plugins_metadata.json"
TIMEOUT_SEC = 30


def fetch():
req = urllib.request.Request(UPSTREAM, headers={"User-Agent": "aiida-website-build"})
with urllib.request.urlopen(req, timeout=TIMEOUT_SEC) as resp:
raw = resp.read()
data = json.loads(raw.decode())

expected = {"plugins", "globalsummary", "status_dict", "entrypointtypes"}
missing = expected - set(data.keys())
if missing:
raise RuntimeError(f"upstream JSON missing keys: {missing}")
if not data["plugins"]:
raise RuntimeError("upstream JSON contains zero plugins — refusing to overwrite with empty data.")

out_path = Path(__file__).resolve().parent.parent / "src" / "data" / "plugins_metadata.json"
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(data, indent=2) + "\n")

n_plugins = len(data["plugins"])
size_kb = len(raw) // 1024
print(f"Plugin registry: {n_plugins} plugins, {size_kb} KB")


if __name__ == "__main__":
try:
fetch()
except Exception as e:
print(f"fetch-registry: {e}", file=sys.stderr)
sys.exit(1)
30 changes: 22 additions & 8 deletions scripts/fetch-verdi-cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@
"""Generate verdi CLI tree from aiida-core for the website.

Run: python scripts/fetch-verdi-cli.py
Output: src/data/verdi-cli.json
Output: src/data/verdi-cli.json (gitignored — generated fresh on every build)

- Developer mode: uses locally installed aiida-core
- CI / deploy: fetches the latest stable release from PyPI automatically
- Developer mode: uses locally installed aiida-core (fast, no install).
- CI / fresh clone without aiida-core: creates a temp venv, installs aiida-core
from PyPI, and re-runs this script inside it (slow first time, ~30 seconds).

This only needs to be re-run when aiida-core updates its CLI.
The generated JSON is committed to the repo and imported statically.
Wired into:
- `npm run prebuild` — always re-fetches before every build (deploy, CI).
- `npm run predev` — re-fetches only when any of the three data files is
missing (first clone). Subsequent `npm run dev` is
instant; refresh manually with `npm run fetch-data`.

Build/dev fails loudly if generation fails — there is deliberately no graceful
fallback to stale data. Expected output schema: src/data/verdi-cli.example.json.
"""
import json
import os
Expand Down Expand Up @@ -60,6 +67,9 @@ def walk_group(group: click.MultiCommand, prefix: str = "verdi") -> dict:
"help": tree["help"],
}

if not output["commands"]:
raise RuntimeError("verdi command tree came back empty — aborting rather than write an empty CLI reference.")

OUT_PATH.parent.mkdir(parents=True, exist_ok=True)
OUT_PATH.write_text(json.dumps(output, indent=2, sort_keys=True) + "\n")
print(f"Generated {OUT_PATH} with {len(output['commands'])} commands, {len(output['subcommands'])} groups")
Expand Down Expand Up @@ -93,6 +103,10 @@ def fetch_and_generate():

if __name__ == "__main__":
try:
generate()
except ImportError:
fetch_and_generate()
try:
generate()
except ImportError:
fetch_and_generate()
except Exception as e:
print(f"fetch-verdi-cli: {e}", file=sys.stderr)
sys.exit(1)
4 changes: 3 additions & 1 deletion src/components/Docs.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { ReactNode } from 'react';

const base = import.meta.env.BASE_URL?.replace(/\/$/, '') || '';

const ExtIcon = () => (
<svg className="docs-ext-icon" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M4.5 1.5H2a.5.5 0 0 0-.5.5v8a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V7.5" />
Expand Down Expand Up @@ -168,7 +170,7 @@ export default function Docs(): ReactNode {
<p>Source code, issues, and contributing</p>
</div>
</a>
<a className="docs-extra-card" href="https://aiidateam.github.io/aiida-registry/" target="_blank" rel="noopener noreferrer">
<a className="docs-extra-card" href={`${base}/plugin-registry/`}>
<div className="docs-extra-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z" />
Expand Down
4 changes: 2 additions & 2 deletions src/components/Ecosystem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const NODES: EcoNode[] = [
name: 'Plugin registry',
tagline: '100+ community plugins',
desc: 'A curated directory of all AiiDA plugins — from simulation codes (Quantum ESPRESSO, VASP, CP2K, FLEUR, Siesta, …) to data types, schedulers, and transports. Plugins extend AiiDA via Python entry points.',
url: 'https://aiidateam.github.io/aiida-registry/',
url: `${base}/plugin-registry/`,
color: '#0096de',
category: 'core',
},
Expand Down Expand Up @@ -193,7 +193,7 @@ export default function Ecosystem(): ReactNode {

{/* Bottom row: extensions and tools */}
<div className="eco-layer eco-layer--extensions">
<a className="eco-ext-node" href="https://aiidateam.github.io/aiida-registry/" target="_blank" rel="noopener noreferrer"
<a className="eco-ext-node" href={`${base}/plugin-registry/`}
style={{ '--ext-color': '#0096de' } as React.CSSProperties}>
<div className="eco-ext-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="#0096de" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
Expand Down
4 changes: 2 additions & 2 deletions src/components/LandingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ function PluginShowcase(): ReactNode {
</div>
</div>
))}
<a className="plugin-registry-link" href="https://aiidateam.github.io/aiida-registry/">
<a className="plugin-registry-link" href={`${base}/plugin-registry/`}>
Browse all 100+ plugins in the registry &rarr;
</a>
</div>
Expand Down Expand Up @@ -3112,7 +3112,7 @@ function Numbers(): ReactNode {
<span className="number-value">1000+</span>
<span className="number-label">Publications</span>
</a>
<a className="number-item number-item--link" href="https://aiidateam.github.io/aiida-registry/" target="_blank" rel="noopener noreferrer">
<a className="number-item number-item--link" href={`${base}/plugin-registry/`}>
<span className="number-value">100+</span>
<span className="number-label">Plugins</span>
</a>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default function Navbar() {
<li><a href={`${base}/ecosystem`}>Ecosystem</a></li>
<li><a href={`${base}/docs`}>Docs</a></li>
<li><a href={`${base}/blog/`}>News</a></li>
<li><a href="https://aiidateam.github.io/aiida-registry/" target="_blank" rel="noopener noreferrer">Plugins <ExtIcon /></a></li>
<li><a href={`${base}/plugin-registry/`}>Plugins</a></li>
<li><a href="https://aiida.discourse.group/" target="_blank" rel="noopener noreferrer">Community <ExtIcon /></a></li>
<li className="navbar-dropdown" ref={moreRef}>
<button className="navbar-dropdown-btn" onClick={() => setMoreOpen(!moreOpen)}>
Expand Down
Loading
Loading