Add 7.41e patch notes with a hand-authored reading sidebar.
Ship Climperor Web 0.6.13: latest-patch summary panel, icon/innate fallbacks, and layout/scroll fixes; keep shared ability badges in git. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+84
-18
@@ -32,9 +32,17 @@ ROOT = Path(__file__).resolve().parent
|
||||
REPO_ROOT = ROOT.parent
|
||||
sys.path.insert(0, str(REPO_ROOT))
|
||||
|
||||
from shared.http_utils import write_json_atomic
|
||||
from shared.http_utils import http_json, write_json_atomic
|
||||
from shared.paths import HEROES_JSON, RELATIONS_JSON, WEB_FRONTEND
|
||||
|
||||
OPENDOTA_PROBE_URL = "https://api.opendota.com/api/heroStats"
|
||||
# Must be non-empty before any Pages deploy — empty shells overwrite production.
|
||||
CRITICAL_BY_HERO_FILES = (
|
||||
ROOT / "data" / "hero_stats.json",
|
||||
ROOT / "data" / "stratz_hero_meta.json",
|
||||
ROOT / "data" / "stratz_matchup_tops.json",
|
||||
)
|
||||
|
||||
# Paths whose content change should trigger deploy / OSS.
|
||||
DATA_WATCH = [
|
||||
ROOT / "data" / "hero_stats.json",
|
||||
@@ -58,6 +66,7 @@ INPUT_WATCH = [
|
||||
RELATIONS_JSON,
|
||||
HEROES_JSON,
|
||||
ROOT / "data" / "hero_grid_order.json",
|
||||
ROOT / "data" / "patch_summaries.json",
|
||||
WEB_FRONTEND / "index.html",
|
||||
WEB_FRONTEND / "app.js",
|
||||
WEB_FRONTEND / "style.css",
|
||||
@@ -270,26 +279,82 @@ def run_patch_linked(*, dry_run: bool = False) -> None:
|
||||
)
|
||||
|
||||
|
||||
def probe_opendota_available() -> bool:
|
||||
"""Single fail-fast GET; False means skip OpenDota daily fetches this run."""
|
||||
try:
|
||||
http_json(OPENDOTA_PROBE_URL, timeout=15, retries=0)
|
||||
except Exception as exc:
|
||||
print(
|
||||
f"OpenDota probe failed ({type(exc).__name__}: {exc}); "
|
||||
"skipping hero_stats / hero_matches / pro_matches this run",
|
||||
flush=True,
|
||||
)
|
||||
RUN_RESULTS.append(
|
||||
{
|
||||
"step": "opendota_probe",
|
||||
"ok": False,
|
||||
"skipped_opendota": True,
|
||||
"error": f"{type(exc).__name__}: {exc}",
|
||||
}
|
||||
)
|
||||
return False
|
||||
print("OpenDota probe ok", flush=True)
|
||||
RUN_RESULTS.append({"step": "opendota_probe", "ok": True})
|
||||
return True
|
||||
|
||||
|
||||
def assert_critical_data_ready() -> None:
|
||||
"""Refuse deploy when restored/generated caches would publish empty shells."""
|
||||
problems: list[str] = []
|
||||
for path in CRITICAL_BY_HERO_FILES:
|
||||
rel = path.name
|
||||
if not path.is_file():
|
||||
problems.append(f"{rel}: missing")
|
||||
continue
|
||||
try:
|
||||
raw = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
||||
problems.append(f"{rel}: unreadable ({exc})")
|
||||
continue
|
||||
by_hero = raw.get("by_hero") if isinstance(raw, dict) else None
|
||||
if not isinstance(by_hero, dict) or not by_hero:
|
||||
problems.append(f"{rel}: empty by_hero")
|
||||
if problems:
|
||||
raise RuntimeError(
|
||||
"refusing deploy: critical web data incomplete ("
|
||||
+ "; ".join(problems)
|
||||
+ ")"
|
||||
)
|
||||
|
||||
|
||||
def run_daily(*, dry_run: bool = False) -> bool:
|
||||
"""Return True if patch-linked fetch ran."""
|
||||
# Soft-fail OpenDota quota burns: keep restored cache and continue the tier.
|
||||
run_script("fetch_hero_stats.py", dry_run=dry_run, soft_fail=True)
|
||||
# Probe once: if OpenDota is 429/down, keep restored cache and do not burn quota.
|
||||
opendota_ok = True if dry_run else probe_opendota_available()
|
||||
if opendota_ok:
|
||||
# Soft-fail OpenDota quota burns: keep restored cache and continue the tier.
|
||||
run_script("fetch_hero_stats.py", dry_run=dry_run, soft_fail=True)
|
||||
run_script(
|
||||
"fetch_hero_matches.py",
|
||||
"--source",
|
||||
"league",
|
||||
dry_run=dry_run,
|
||||
soft_fail=True,
|
||||
)
|
||||
run_script(
|
||||
"fetch_pro_matches.py",
|
||||
"--include-pubs",
|
||||
"--refresh-limit",
|
||||
"15",
|
||||
dry_run=dry_run,
|
||||
soft_fail=True,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"skipped OpenDota daily fetches (probe failed); keeping prior cache",
|
||||
flush=True,
|
||||
)
|
||||
run_script("fetch_leaderboards.py", dry_run=dry_run)
|
||||
run_script(
|
||||
"fetch_hero_matches.py",
|
||||
"--source",
|
||||
"league",
|
||||
dry_run=dry_run,
|
||||
soft_fail=True,
|
||||
)
|
||||
run_script(
|
||||
"fetch_pro_matches.py",
|
||||
"--include-pubs",
|
||||
"--refresh-limit",
|
||||
"15",
|
||||
dry_run=dry_run,
|
||||
soft_fail=True,
|
||||
)
|
||||
# Soft-fail Douyin enrichment (script itself exits 0; keep previous values on miss).
|
||||
run_script("fetch_streamers.py", dry_run=dry_run, soft_fail=True)
|
||||
# Soft-fail live probe (exits 0; probe failures clear is_live / live_probed_at).
|
||||
@@ -555,6 +620,7 @@ def main() -> None:
|
||||
|
||||
should_deploy = args.force_deploy or data_changed or assets_changed
|
||||
if should_deploy and not args.skip_deploy:
|
||||
assert_critical_data_ready()
|
||||
run_script("deploy_relations.py")
|
||||
summary["deployed"] = True
|
||||
elif should_deploy:
|
||||
|
||||
Reference in New Issue
Block a user