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
5 changes: 4 additions & 1 deletion .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
branches: [main]
pull_request:
workflow_dispatch:
schedule:
- cron: "17 5 * * *"

permissions:
contents: read
Expand All @@ -30,6 +32,7 @@ jobs:
- run: uv run ruff format --check scripts tests
- run: uv run pytest -q
- run: uv run python scripts/export_ecosystem.py --check
- run: uv run python scripts/check_release_freshness.py
- run: uv run zensical build --clean --strict
- run: uv run python scripts/write_headers.py site
- run: uv run python scripts/check_site.py site
Expand Down Expand Up @@ -71,7 +74,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

deploy:
if: github.event_name != 'pull_request'
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
needs: [build, audit-dependencies, lint-workflows]
runs-on: ubuntu-latest
timeout-minutes: 10
Expand Down
14 changes: 7 additions & 7 deletions data/ecosystem.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
schema_version = 1
as_of = "2026-07-30"
source_repository = "hayatepy/.github"
source_commit = "975173da2c1be720a4f902cde36ba3822b9b6e56"
compatibility_url = "https://github.com/hayatepy/.github/blob/975173da2c1be720a4f902cde36ba3822b9b6e56/docs/COMPATIBILITY.md"
source_commit = "9320a766fd5eab03298ae04a39ac39a68799dc48"
compatibility_url = "https://github.com/hayatepy/.github/blob/9320a766fd5eab03298ae04a39ac39a68799dc48/docs/COMPATIBILITY.md"

[[packages]]
name = "hayate"
Expand All @@ -16,23 +16,23 @@ source_commit = "5592276315c9b75e736d3fc204b4900e15f8867a"

[[packages]]
name = "create-hayate"
version = "0.13.0"
version = "0.13.1"
status = "Published alpha"
role = "Composable applications with drift-checked first-party TypeScript clients"
use_when = "Start with a tested API, Workers, MCP, auth, SQL, admin, or frontend composition and keep its generated client in sync."
repository = "https://github.com/hayatepy/create-hayate"
pypi = "https://pypi.org/project/create-hayate/"
source_commit = "9fdb98e5642ff047e04e24251be1c2747fc7572a"
source_commit = "97d19b59e1256ec6d50e9ecb725296442fda4b20"

[[packages]]
name = "hayate-auth"
version = "0.10.1"
version = "0.10.3"
status = "Published alpha; independent audit pending"
role = "Sessions, API keys, OAuth, passkeys, and authorization server"
use_when = "Add first-party identity or protect an HTTP or MCP resource server."
repository = "https://github.com/hayatepy/hayate-auth"
pypi = "https://pypi.org/project/hayate-auth/"
source_commit = "eff705c335d0903084988edea5b783be258f42e8"
source_commit = "20dd7ae08c12051d23fdbe4b242578f800dacdfc"

[[packages]]
name = "hayate-mcp"
Expand Down Expand Up @@ -99,4 +99,4 @@ status = "Reference application"
role = "Executable production integration"
use_when = "Review tested trust boundaries and deployment choices before shipping."
repository = "https://github.com/hayatepy/golden-app"
source_commit = "842fd2486f3d8bd5c284e44e4f8528b0441a574a"
source_commit = "cfa07c1d129b588b3c539a6d6c9fcdd443cefbf7"
2 changes: 1 addition & 1 deletion docs/ecosystem/compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ customer workflows.
them:

```sh
uvx --from create-hayate==0.13.0 create-hayate --help
uvx --from create-hayate==0.13.1 create-hayate --help
```

Use the [family map](index.md) for current release state and package links.
14 changes: 7 additions & 7 deletions docs/ecosystem/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

Choose packages by the boundary your application needs. The core stays small; capabilities are explicit packages with their own release and evidence.

This snapshot is dated **2026-07-30** and is pinned to [`hayatepy/.github` at `975173da2c1b`](https://github.com/hayatepy/.github/tree/975173da2c1be720a4f902cde36ba3822b9b6e56). Every package is pre-1.0; check the compatibility snapshot before combining versions.
This snapshot is dated **2026-07-30** and is pinned to [`hayatepy/.github` at `9320a766fd5e`](https://github.com/hayatepy/.github/tree/9320a766fd5eab03298ae04a39ac39a68799dc48). Every package is pre-1.0; check the compatibility snapshot before combining versions.

| Project | Version | Responsibility | Release state |
|---|---:|---|---|
| [`hayate`](https://github.com/hayatepy/hayate) | [`0.15.1`](https://pypi.org/project/hayate/) | Core framework and native ASGI, Workers, and Lambda adapters | Published alpha |
| [`create-hayate`](https://github.com/hayatepy/create-hayate) | [`0.13.0`](https://pypi.org/project/create-hayate/) | Composable applications with drift-checked first-party TypeScript clients | Published alpha |
| [`hayate-auth`](https://github.com/hayatepy/hayate-auth) | [`0.10.1`](https://pypi.org/project/hayate-auth/) | Sessions, API keys, OAuth, passkeys, and authorization server | Published alpha; independent audit pending |
| [`create-hayate`](https://github.com/hayatepy/create-hayate) | [`0.13.1`](https://pypi.org/project/create-hayate/) | Composable applications with drift-checked first-party TypeScript clients | Published alpha |
| [`hayate-auth`](https://github.com/hayatepy/hayate-auth) | [`0.10.3`](https://pypi.org/project/hayate-auth/) | Sessions, API keys, OAuth, passkeys, and authorization server | Published alpha; independent audit pending |
| [`hayate-mcp`](https://github.com/hayatepy/hayate-mcp) | [`0.12.0`](https://pypi.org/project/hayate-mcp/) | MCP 2026-07-28 Streamable HTTP and discovery | Published alpha |
| [`hayate-openapi`](https://github.com/hayatepy/hayate-openapi) | [`0.8.1`](https://pypi.org/project/hayate-openapi/) | OpenAPI 3.1, Scalar, and generated zero-runtime TypeScript clients | Published alpha |
| [`hayate-sql`](https://github.com/hayatepy/hayate-sql) | [`0.1.1`](https://pypi.org/project/hayate-sql/) | Checked SQL contracts and typed query facades | Published alpha |
Expand All @@ -19,7 +19,7 @@ This snapshot is dated **2026-07-30** and is pinned to [`hayatepy/.github` at `9
| [`hayate-htmx`](https://github.com/hayatepy/hayate-htmx) | `0.2.0` | Hypermedia, Jinja, htmx, fragments, and SSE | Pre-release source |
| [`golden-app`](https://github.com/hayatepy/golden-app) | `0.1.0` | Executable production integration | Reference application |

[Open the tested compatibility snapshot](https://github.com/hayatepy/.github/blob/975173da2c1be720a4f902cde36ba3822b9b6e56/docs/COMPATIBILITY.md)
[Open the tested compatibility snapshot](https://github.com/hayatepy/.github/blob/9320a766fd5eab03298ae04a39ac39a68799dc48/docs/COMPATIBILITY.md)

## hayate

Expand All @@ -33,15 +33,15 @@ Build one Fetch-style Python application for ASGI, Workers, or Lambda.

Start with a tested API, Workers, MCP, auth, SQL, admin, or frontend composition and keep its generated client in sync.

**Current evidence:** Published alpha, version `0.13.0` · [source at `9fdb98e5642f`](https://github.com/hayatepy/create-hayate/tree/9fdb98e5642ff047e04e24251be1c2747fc7572a)
**Current evidence:** Published alpha, version `0.13.1` · [source at `97d19b59e125`](https://github.com/hayatepy/create-hayate/tree/97d19b59e1256ec6d50e9ecb725296442fda4b20)

[Repository](https://github.com/hayatepy/create-hayate) · [PyPI](https://pypi.org/project/create-hayate/)

## hayate-auth

Add first-party identity or protect an HTTP or MCP resource server.

**Current evidence:** Published alpha; independent audit pending, version `0.10.1` · [source at `eff705c335d0`](https://github.com/hayatepy/hayate-auth/tree/eff705c335d0903084988edea5b783be258f42e8)
**Current evidence:** Published alpha; independent audit pending, version `0.10.3` · [source at `20dd7ae08c12`](https://github.com/hayatepy/hayate-auth/tree/20dd7ae08c12051d23fdbe4b242578f800dacdfc)

[Repository](https://github.com/hayatepy/hayate-auth) · [PyPI](https://pypi.org/project/hayate-auth/)

Expand Down Expand Up @@ -97,6 +97,6 @@ Build progressively enhanced server-rendered applications without a JavaScript S

Review tested trust boundaries and deployment choices before shipping.

**Current evidence:** Reference application, version `0.1.0` · [source at `842fd2486f3d`](https://github.com/hayatepy/golden-app/tree/842fd2486f3d8bd5c284e44e4f8528b0441a574a)
**Current evidence:** Reference application, version `0.1.0` · [source at `cfa07c1d129b`](https://github.com/hayatepy/golden-app/tree/cfa07c1d129b588b3c539a6d6c9fcdd443cefbf7)

[Repository](https://github.com/hayatepy/golden-app)
2 changes: 1 addition & 1 deletion docs/get-started/first-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Cloudflare Access, checked SQL, request correlation, and production middleware.
Install [uv](https://docs.astral.sh/uv/), then run:

```sh
uvx --refresh --from create-hayate==0.13.0 \
uvx --refresh --from create-hayate==0.13.1 \
create-hayate my-app --template workers --preset production
cd my-app
```
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,6 @@ description: One Fetch-style Python application across ASGI, Cloudflare Workers,
<section class="hayate-final">
<p class="hayate-kicker">START FROM A TESTED COMPOSITION</p>
<h2>From empty directory to passing production checks.</h2>
<p><code>uvx --from create-hayate==0.13.0 create-hayate my-app --template workers --preset production</code></p>
<p><code>uvx --from create-hayate==0.13.1 create-hayate my-app --template workers --preset production</code></p>
<a class="md-button md-button--primary" href="get-started/first-app/">Follow the verified path</a>
</section>
108 changes: 108 additions & 0 deletions scripts/check_release_freshness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""Fail when the public ecosystem manifest or quickstart pins lag behind PyPI."""

from __future__ import annotations

import argparse
import json
import re
import tomllib
from collections.abc import Callable, Mapping
from pathlib import Path
from typing import Any
from urllib.request import Request, urlopen

ROOT = Path(__file__).resolve().parents[1]
MANIFEST = ROOT / "data" / "ecosystem.toml"
CREATE_HAYATE_PIN = re.compile(r"create-hayate==(\d+\.\d+\.\d+)")
PYPI_ORIGIN = "https://pypi.org"
USER_AGENT = "hayatepy-docs-release-freshness/1"

FetchProject = Callable[[str], Mapping[str, Any]]


def _normalize_project_name(name: str) -> str:
return re.sub(r"[-_.]+", "-", name).lower()


def _public_packages(data: Mapping[str, Any]) -> list[Mapping[str, Any]]:
packages = data.get("packages")
if not isinstance(packages, list):
raise ValueError("ecosystem manifest needs a package list")
return [package for package in packages if isinstance(package, Mapping) and package.get("pypi")]


def fetch_pypi_project(name: str) -> Mapping[str, Any]:
request = Request(
f"{PYPI_ORIGIN}/pypi/{name}/json",
headers={"Accept": "application/json", "User-Agent": USER_AGENT},
)
with urlopen(request, timeout=10) as response:
payload = json.load(response)
if not isinstance(payload, Mapping):
raise ValueError(f"PyPI returned a non-object payload for {name}")
return payload


def validate_pypi_versions(
data: Mapping[str, Any], fetch_project: FetchProject = fetch_pypi_project
) -> list[str]:
failures: list[str] = []
for package in _public_packages(data):
expected_name = str(package["name"])
expected_version = str(package["version"])
payload = fetch_project(expected_name)
info = payload.get("info")
if not isinstance(info, Mapping):
failures.append(f"{expected_name}: PyPI payload has no info object")
continue
published_name = str(info.get("name", ""))
published_version = str(info.get("version", ""))
if _normalize_project_name(published_name) != _normalize_project_name(expected_name):
failures.append(
f"{expected_name}: PyPI returned project {published_name or '<missing>'}"
)
if published_version != expected_version:
failures.append(
f"{expected_name}: manifest has {expected_version}, "
f"PyPI latest is {published_version or '<missing>'}"
)
return failures


def validate_create_hayate_pins(root: Path, data: Mapping[str, Any]) -> list[str]:
versions = {str(package["name"]): str(package["version"]) for package in _public_packages(data)}
expected = versions.get("create-hayate")
if expected is None:
return ["ecosystem manifest has no public create-hayate package"]

failures: list[str] = []
references = 0
for path in sorted((root / "docs").rglob("*.md")):
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
for match in CREATE_HAYATE_PIN.finditer(line):
references += 1
if match.group(1) != expected:
relative = path.relative_to(root)
failures.append(
f"{relative}:{line_number}: create-hayate pin "
f"{match.group(1)} must match manifest {expected}"
)
if references == 0:
failures.append("documentation contains no exact create-hayate release pin")
return failures


def main() -> int:
parser = argparse.ArgumentParser()
parser.parse_args()
data = tomllib.loads(MANIFEST.read_text(encoding="utf-8"))
failures = validate_pypi_versions(data)
failures.extend(validate_create_hayate_pins(ROOT, data))
if failures:
raise SystemExit("\n".join(failures))
print("Validated public PyPI releases and documentation pins")
return 0


if __name__ == "__main__":
raise SystemExit(main())
63 changes: 63 additions & 0 deletions tests/test_release_freshness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from __future__ import annotations

from pathlib import Path

from scripts.check_release_freshness import (
validate_create_hayate_pins,
validate_pypi_versions,
)


def manifest(version: str = "0.13.1") -> dict:
return {
"packages": [
{
"name": "create-hayate",
"version": version,
"pypi": "https://pypi.org/project/create-hayate/",
},
{
"name": "source-only",
"version": "0.1.0",
},
]
}


def test_public_release_matches_manifest() -> None:
failures = validate_pypi_versions(
manifest(),
lambda name: {"info": {"name": "create_hayate", "version": "0.13.1"}},
)

assert failures == []


def test_public_release_drift_is_reported() -> None:
failures = validate_pypi_versions(
manifest("0.13.0"),
lambda name: {"info": {"name": name, "version": "0.13.1"}},
)

assert failures == ["create-hayate: manifest has 0.13.0, PyPI latest is 0.13.1"]


def test_documentation_pin_must_match_manifest(tmp_path: Path) -> None:
docs = tmp_path / "docs"
docs.mkdir()
(docs / "start.md").write_text(
"uvx --from create-hayate==0.13.0 create-hayate demo\n",
encoding="utf-8",
)

failures = validate_create_hayate_pins(tmp_path, manifest())

assert failures == ["docs/start.md:1: create-hayate pin 0.13.0 must match manifest 0.13.1"]


def test_documentation_requires_an_exact_release_pin(tmp_path: Path) -> None:
(tmp_path / "docs").mkdir()

assert validate_create_hayate_pins(tmp_path, manifest()) == [
"documentation contains no exact create-hayate release pin"
]
Loading