Skip to content

Commit 67ce286

Browse files
test: validate static site metadata
1 parent 4860c40 commit 67ce286

2 files changed

Lines changed: 138 additions & 3 deletions

File tree

package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44
"description": "Static cyberpunk landing page for GraphTheory.",
55
"main": "index.js",
66
"scripts": {
7-
"test": "echo \"Error: no test specified\" && exit 1"
7+
"test": "python3 scripts/validate_static_site.py",
8+
"verify:responsive": "node scripts/verify-responsive.mjs"
89
},
9-
"keywords": [],
10-
"author": "",
10+
"keywords": [
11+
"hermes-agent",
12+
"ai-agents",
13+
"developer-tools",
14+
"agent-profiles"
15+
],
16+
"author": "GraphTheory",
1117
"license": "ISC",
1218
"type": "commonjs",
1319
"dependencies": {

scripts/validate_static_site.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#!/usr/bin/env python3
2+
"""Validate GraphTheory static site metadata and sitemap files."""
3+
4+
from __future__ import annotations
5+
6+
import json
7+
from html.parser import HTMLParser
8+
from pathlib import Path
9+
from urllib.parse import urlparse
10+
import xml.etree.ElementTree as ET
11+
12+
ROOT = Path(__file__).resolve().parents[1]
13+
SITE_HOST = "graphtheory.xyz"
14+
ORIGINAL_PROJECTS = {
15+
"hermes-profile-template",
16+
"heavy-coder",
17+
"context-forge-rag",
18+
"chainforge",
19+
"solana-rug",
20+
"codegraphtheory",
21+
}
22+
FORK_NAMES = {
23+
"octra",
24+
"hermes-agent",
25+
"hermes-profiles",
26+
"hermes-profile-kit",
27+
"agent-profiles",
28+
"awesome-rag-resources",
29+
"awesome-blockchain",
30+
"Supra-AI",
31+
"awesome-hermes-agent",
32+
"awesome-solana-oss",
33+
"awesome-solana",
34+
"awesome-solana-ai",
35+
"soul.md",
36+
"awesome-openclaw-agents",
37+
}
38+
39+
40+
class HeadParser(HTMLParser):
41+
def __init__(self) -> None:
42+
super().__init__()
43+
self.title = ""
44+
self._in_title = False
45+
self.meta: dict[str, str] = {}
46+
self.links: dict[str, str] = {}
47+
48+
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
49+
data = {k: v or "" for k, v in attrs}
50+
if tag == "title":
51+
self._in_title = True
52+
if tag == "meta":
53+
key = data.get("name") or data.get("property")
54+
if key:
55+
self.meta[key] = data.get("content", "")
56+
if tag == "link" and data.get("rel"):
57+
self.links[data["rel"]] = data.get("href", "")
58+
59+
def handle_endtag(self, tag: str) -> None:
60+
if tag == "title":
61+
self._in_title = False
62+
63+
def handle_data(self, data: str) -> None:
64+
if self._in_title:
65+
self.title += data
66+
67+
68+
def require(condition: bool, message: str) -> None:
69+
if not condition:
70+
raise SystemExit(message)
71+
72+
73+
def parse_xml(path: Path) -> ET.Element:
74+
require(path.exists(), f"missing XML file: {path.relative_to(ROOT)}")
75+
return ET.parse(path).getroot()
76+
77+
78+
def validate_html() -> None:
79+
html_path = ROOT / "index.html"
80+
parser = HeadParser()
81+
parser.feed(html_path.read_text(encoding="utf-8"))
82+
require("GraphTheory" in parser.title, "index title must name GraphTheory")
83+
require(bool(parser.meta.get("description")), "index needs meta description")
84+
require(parser.meta.get("og:image", "").startswith("https://graphtheory.xyz/assets/"), "index needs canonical OG image")
85+
require(parser.meta.get("twitter:card") == "summary_large_image", "index needs large Twitter card")
86+
require(parser.links.get("canonical") == "https://graphtheory.xyz/", "index needs canonical root link")
87+
88+
89+
def validate_sitemaps() -> None:
90+
ns = "{http://www.sitemaps.org/schemas/sitemap/0.9}"
91+
index = parse_xml(ROOT / "sitemap.xml")
92+
require(index.tag == ns + "sitemapindex", "sitemap.xml must be a sitemap index")
93+
child_locs = [node.findtext(ns + "loc") or "" for node in index.findall(ns + "sitemap")]
94+
require(bool(child_locs), "sitemap.xml must list child sitemaps")
95+
require("https://graphtheory.xyz/sitemap-pages.xml" in child_locs, "sitemap index must include flat page sitemap")
96+
for loc in child_locs:
97+
parsed = urlparse(loc or "")
98+
require(parsed.scheme == "https" and parsed.netloc == SITE_HOST, f"bad sitemap loc: {loc}")
99+
child_path = ROOT / parsed.path.lstrip("/")
100+
child = parse_xml(child_path)
101+
require(child.tag == ns + "urlset", f"child sitemap must be urlset: {child_path.relative_to(ROOT)}")
102+
urls = [node.findtext(ns + "loc") or "" for node in child.findall(ns + "url")]
103+
require(bool(urls), f"child sitemap has no URLs: {child_path.relative_to(ROOT)}")
104+
for url in urls:
105+
parsed_url = urlparse(url or "")
106+
require(parsed_url.scheme == "https" and parsed_url.netloc == SITE_HOST, f"bad page URL: {url}")
107+
require(not parsed_url.fragment, f"sitemap URL must not contain fragment: {url}")
108+
for fork_name in FORK_NAMES:
109+
require(f"/{fork_name}/" not in parsed_url.path, f"fork surfaced in sitemap: {url}")
110+
for project in ORIGINAL_PROJECTS:
111+
require((ROOT / project / "index.html").exists(), f"missing project doc page: {project}/index.html")
112+
require(f"https://graphtheory.xyz/sitemaps/{project}.xml" in child_locs, f"missing project sitemap: {project}")
113+
114+
115+
def validate_manifest() -> None:
116+
manifest = json.loads((ROOT / "assets" / "site.webmanifest").read_text(encoding="utf-8"))
117+
require(manifest.get("name"), "webmanifest needs name")
118+
require(manifest.get("start_url") in {"/", "."}, "webmanifest needs root start_url")
119+
120+
121+
def main() -> None:
122+
validate_html()
123+
validate_sitemaps()
124+
validate_manifest()
125+
print("static site validation passed")
126+
127+
128+
if __name__ == "__main__":
129+
main()

0 commit comments

Comments
 (0)