v0.5.84: matches origin filter, mobile gate, refresh reliability.
Ship Web refresh cache/lock, mobile demand gate, matches 职业/国服 filter, and related site updates through 0.5.84. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+85
-6
@@ -34,7 +34,9 @@ import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
@@ -239,10 +241,16 @@ def wrangler_deploy(
|
||||
) -> None:
|
||||
"""Upload dist via `wrangler pages deploy` (direct upload, non-interactive).
|
||||
|
||||
The whole dist directory is uploaded, so dist/functions/ (Pages Functions,
|
||||
e.g. /api/live-status) is included automatically — Pages Functions need
|
||||
no extra wrangler config or flags for direct-upload projects.
|
||||
Wrangler looks for ``functions/`` relative to the process cwd (not inside
|
||||
an absolute asset-directory argument). Run from ``dist`` and deploy ``.``
|
||||
so ``dist/functions/`` (e.g. /api/live-status) is compiled and uploaded.
|
||||
"""
|
||||
functions_dir = dist / "functions"
|
||||
if not functions_dir.is_dir():
|
||||
print(
|
||||
f"warn: {functions_dir} missing — Pages Functions will not deploy "
|
||||
"(re-run export_relations_site.py)"
|
||||
)
|
||||
env = os.environ.copy()
|
||||
# wrangler reads Global API Key auth from these env vars. They are passed
|
||||
# to the subprocess only; wrangler does not echo them.
|
||||
@@ -256,15 +264,76 @@ def wrangler_deploy(
|
||||
"wrangler@3",
|
||||
"pages",
|
||||
"deploy",
|
||||
str(dist),
|
||||
".",
|
||||
"--project-name",
|
||||
project,
|
||||
"--commit-dirty",
|
||||
"--branch",
|
||||
"main",
|
||||
]
|
||||
print(f"uploading {dist} via wrangler ...")
|
||||
subprocess.run(cmd, cwd=str(ROOT), env=env, check=True)
|
||||
print(f"uploading {dist} via wrangler (cwd=dist, functions={functions_dir.is_dir()}) ...")
|
||||
subprocess.run(cmd, cwd=str(dist), env=env, check=True)
|
||||
|
||||
|
||||
def smoke_test_production(
|
||||
base_url: str,
|
||||
*,
|
||||
expected_run_id: str = "",
|
||||
attempts: int = 6,
|
||||
delay_s: float = 5.0,
|
||||
) -> None:
|
||||
"""Verify the deployed payload and Pages Function after propagation."""
|
||||
base = base_url.rstrip("/")
|
||||
marker = expected_run_id or str(int(time.time()))
|
||||
error = "unknown failure"
|
||||
for attempt in range(1, attempts + 1):
|
||||
try:
|
||||
data_url = f"{base}/data.json?refresh={urllib.parse.quote(marker)}"
|
||||
req = urllib.request.Request(data_url, headers={"User-Agent": "climperor-smoke"})
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
payload = json.loads(resp.read().decode("utf-8"))
|
||||
if not isinstance(payload, dict) or not payload.get("heroes"):
|
||||
raise RuntimeError("data.json has no heroes")
|
||||
actual_run_id = str((payload.get("meta") or {}).get("refresh_run_id") or "")
|
||||
if expected_run_id and actual_run_id != expected_run_id:
|
||||
raise RuntimeError(
|
||||
f"stale deployment run_id={actual_run_id!r}, expected={expected_run_id!r}"
|
||||
)
|
||||
|
||||
live_url = f"{base}/api/live-status?refresh={urllib.parse.quote(marker)}"
|
||||
live_req = urllib.request.Request(
|
||||
live_url, headers={"User-Agent": "climperor-smoke"}
|
||||
)
|
||||
with urllib.request.urlopen(live_req, timeout=30) as resp:
|
||||
content_type = resp.headers.get_content_type()
|
||||
live_cache = (resp.headers.get("X-Live-Cache") or "").lower()
|
||||
live_payload = json.loads(resp.read().decode("utf-8"))
|
||||
if content_type != "application/json" or not isinstance(live_payload, dict):
|
||||
raise RuntimeError(
|
||||
f"/api/live-status is not JSON (content-type={content_type})"
|
||||
)
|
||||
if live_cache in ("error", "stale-override"):
|
||||
print(
|
||||
f"warn: live-status Function is routed but probe state is {live_cache}"
|
||||
)
|
||||
print(
|
||||
f"production smoke passed: {base} "
|
||||
f"(heroes={len(payload['heroes'])}, run_id={actual_run_id or '-'}, "
|
||||
f"live_cache={live_cache or '?'})"
|
||||
)
|
||||
return
|
||||
except (
|
||||
OSError,
|
||||
ValueError,
|
||||
RuntimeError,
|
||||
json.JSONDecodeError,
|
||||
urllib.error.URLError,
|
||||
) as exc:
|
||||
error = str(exc)
|
||||
print(f"smoke attempt {attempt}/{attempts} failed: {error}")
|
||||
if attempt < attempts:
|
||||
time.sleep(delay_s)
|
||||
raise SystemExit(f"deployment smoke failed after {attempts} attempts: {error}")
|
||||
|
||||
|
||||
def ensure_cname(email: str, api_key: str, domain: str, project: str) -> None:
|
||||
@@ -358,6 +427,11 @@ def main() -> None:
|
||||
help="passed to export as --static-asset-base (default: same as ability-video-base "
|
||||
"or STATIC_ASSET_BASE env)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--skip-smoke",
|
||||
action="store_true",
|
||||
help="skip post-deploy data.json and /api/live-status verification",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
dist = Path(args.dist).resolve()
|
||||
@@ -379,6 +453,11 @@ def main() -> None:
|
||||
check_integrity(dist)
|
||||
wrangler_deploy(dist, args.project_name, email=email, api_key=api_key, account_id=account_id)
|
||||
bind_domain(email, api_key, account_id, args.project_name, args.domain)
|
||||
if not args.skip_smoke:
|
||||
smoke_test_production(
|
||||
f"https://{args.project_name}.pages.dev",
|
||||
expected_run_id=os.environ.get("REFRESH_RUN_ID") or "",
|
||||
)
|
||||
|
||||
print()
|
||||
print(f"deployment url : https://{args.project_name}.pages.dev")
|
||||
|
||||
Reference in New Issue
Block a user