Skip to content
Open
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
17 changes: 17 additions & 0 deletions .github/workflows/check-variants.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Check variants

on:
pull_request:
paths:
- 'frameworks/*/meta.json'
- 'scripts/check_variants.py'

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: python3 scripts/check_variants.py frameworks
3 changes: 3 additions & 0 deletions frameworks/hono-bun/meta.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"display_name": "hono-bun",
"framework": "hono",
"variant": "bun",
"default_visible": true,
"language": "TS",
"type": "flagship",
"mode": "standard",
Expand Down
3 changes: 3 additions & 0 deletions frameworks/hono-node/meta.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"display_name": "hono-node",
"framework": "hono",
"variant": "node",
"default_visible": true,
"language": "TS",
"type": "flagship",
"mode": "standard",
Expand Down
3 changes: 2 additions & 1 deletion scripts/check_badge_parity.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@ const stubs = `
Object.keys(D.results).forEach(function(k){ D.results[k].forEach(function(r){ if(r.lang) FWLANG[r.fw]=r.lang; }); });
function typeOf(fw){ return (D.meta[fw] && D.meta[fw].type) || 'emerging'; }
function modeOf(fw){ return (D.meta[fw] && D.meta[fw].mode) || 'standard'; }
function isHiddenVariant(fw){ return !!(D.meta[fw] && D.meta[fw].hidden); }
function langOf(fw){ return FWLANG[fw] || ''; }
function matchQ(fw){ return true; } // state.q is '' — no search filter
var state = { useMem:false, rescale:false, showTuned:true, q:'', lang:'', scope:'h1', types:[] };
var state = { useMem:false, rescale:false, showTuned:true, showVariants:false, q:'', lang:'', scope:'h1', types:[] };
`;
const run = new Function('D', `
${stubs}
Expand Down
44 changes: 44 additions & 0 deletions scripts/check_variants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Checks the variant fields of every meta.json. See the Variants section of the framework docs.

python3 scripts/check_variants.py frameworks
"""
import json
import sys
from pathlib import Path

MAX_VARIANTS = 2


def problems(root):
found, seen = [], {}
for path in sorted(Path(root).glob("*/meta.json")):
name = path.parent.name
try:
meta = json.loads(path.read_text(encoding="utf-8"))
except ValueError as e:
found.append(f"{name}: {e}")
continue
framework, variant = meta.get("framework"), meta.get("variant")
if bool(framework) != bool(variant):
found.append(f"{name}: framework and variant go together")
if "default_visible" in meta and not (framework and variant):
found.append(f"{name}: default_visible is only for a variant")
if not isinstance(meta.get("default_visible", True), bool):
found.append(f"{name}: default_visible is true or false")
if framework and variant:
seen.setdefault(framework, []).append((name, variant))
for framework, entries in seen.items():
names = ", ".join(name for name, _ in entries)
if len(entries) > MAX_VARIANTS:
found.append(f"{framework}: at most {MAX_VARIANTS} variants, found {names}")
if len({variant for _, variant in entries}) != len(entries):
found.append(f"{framework}: two entries name the same variant: {names}")
return found


if __name__ == "__main__":
wrong = problems(sys.argv[1] if len(sys.argv) > 1 else "frameworks")
for line in wrong:
print("FAIL: " + line)
sys.exit(1 if wrong else 0)
9 changes: 8 additions & 1 deletion scripts/gen_leaderboard_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,8 @@ def badge_composite(agg, profiles, meta, scope, types, show_tuned=True, lang=Non

def shown(fw):
"""hidden() in index.html — display only, never touches the maxima."""
if meta.get(fw, {}).get("hidden"):
return False
if not show_tuned and meta.get(fw, {}).get("mode", "standard") == "tuned":
return False
if lang and fw_lang.get(fw) != lang:
Expand Down Expand Up @@ -1770,7 +1772,8 @@ def profile_medals(agg, profiles, meta):
if not p["scoredConns"]:
continue
field = [(fw, eff(pid, fw)) for fw in agg["avg"].get(pid, {})
if in_league(fw) and _scored_for(prof, meta, pid, fw)]
if in_league(fw) and _scored_for(prof, meta, pid, fw)
and not meta.get(fw, {}).get("hidden")]
field = [(fw, v) for fw, v in field if v > 0]
for with_tuned in (False, True):
rows = sorted((r for r in field if with_tuned or not is_tuned(r[0])),
Expand Down Expand Up @@ -3623,6 +3626,10 @@ def build_meta(frameworks, tls_check=None):
"repo": m.get("repo", ""),
# optional and rare, so only on the entries that set it
**({"website": m["website"]} if m.get("website") else {}),
**({"framework": m["framework"], "variant": m["variant"]}
if m.get("variant") else {}),
**({"hidden": True}
if m.get("variant") and m.get("default_visible") is not True else {}),
"dir": m.get("dir", ""),
"engine": m.get("engine", ""),
"cmp": m.get("completeness"),
Expand Down
3 changes: 3 additions & 0 deletions scripts/rebuild_site_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ def rebuild_frameworks_json(root: Path, site_data: Path) -> None:
}
if "mode" in m:
entry["mode"] = m["mode"]
for key in ("framework", "variant", "default_visible"):
if key in m:
entry[key] = m[key]
if m.get("website"):
entry["website"] = m["website"]
if "completeness" in m:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,17 @@ Every framework entry also declares a **mode** in `meta.json.mode` - how strictl
{{< card link="standard" title="Standard" subtitle="Default, production-style usage: documented framework APIs, production settings, and standard libraries." icon="shield-check" >}}
{{< card link="tuned" title="Tuned" subtitle="Non-default configs, experimental flags, and custom optimizations allowed." icon="adjustments" >}}
{{< /cards >}}

## Variants

A **variant** is the same framework on another runtime or with another documented API, for example Hono on Node and Hono on Bun. It is a different concept from mode: each variant follows all the rules of its own mode, and no variant is the real one, they are all legitimate.

```json
"framework": "hono",
"variant": "bun"
```

- A framework has **at most two** variants, because every entry runs on the same hardware. Subscribe a variant only to the tests where it is different.
- A variant has its own row, with a pill that says which variant it is, and it is scored like any other entry.
- A variant is listed only when the Variants filter is on, and it has no badges or medals, unless it sets `"default_visible": true`. Like a tuned entry that is filtered out, it still counts for the leader of each profile.
- Each variant has its own badge URL, under its own entry name.
3 changes: 3 additions & 0 deletions site/content/docs/add-framework/meta-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Create a `meta.json` file in your framework directory:
| `engine` | HTTP server engine (e.g., `Kestrel`, `Tomcat`, `hyper`) |
| `type` | `flagship`, `emerging` or `experimental` for frameworks - servers you write application code against, tiered by how production-proven they are; `engine` for implementations applications are not written against (raw sockets, WSGI/ASGI hosts); `infrastructure` for reverse proxies / static-file servers. See [Implementation Rules](implementation-rules/) |
| `mode` | Frameworks only: `standard` (default - idiomatic, production-style usage) or `tuned` (non-default config / optimizations) |
| `framework` | With `variant`: the framework this entry is a variant of. See [Variants](/docs/add-framework/implementation-rules/frameworks/#variants) |
| `variant` | With `framework`: what is different in this entry, a runtime or an API, e.g. `bun` |
| `default_visible` | Variants only: `true` shows the entry in the default view of the board and gives it badges and medals. Default is `false` |
| `completeness` | Frameworks only: which of `routing`, `middleware`, `request` and `response` the framework does for you. Each one set to `false` takes 2.5% off the composite; an axis you leave out reads as done, and omitting the field scores a full ×1.00. Omit it entirely on an entry that runs only WebSocket or gRPC profiles - the factor never applies on those boards. See [Completeness](/docs/scoring/completeness/) |
| `description` | Shown in the framework detail popup on the leaderboard |
| `repo` | Link to the framework's source repository |
Expand Down
6 changes: 6 additions & 0 deletions site/data/frameworks.json
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,9 @@
"engine": "helidon-nima"
},
"hono-bun": {
"framework": "hono",
"variant": "bun",
"default_visible": true,
"dir": "hono-bun",
"description": "Ultrafast Web Standards framework running natively on Bun \u2014 no adapter overhead.",
"repo": "https://github.com/honojs/hono",
Expand All @@ -1081,6 +1084,9 @@
}
},
"hono-node": {
"framework": "hono",
"variant": "node",
"default_visible": true,
"dir": "hono-node",
"description": "Hono on node:http through @hono/node-server, with the cluster module for multi-core scaling.",
"repo": "https://github.com/honojs/node-server",
Expand Down
Loading
Loading