File size: 6,285 Bytes
088795a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | from __future__ import annotations
import argparse
import json
import shutil
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Sequence
ROOT_DIR = Path(__file__).resolve().parent.parent
if str(ROOT_DIR) not in sys.path:
sys.path.insert(0, str(ROOT_DIR))
from scripts.verify_site import verify_site
DEFAULT_SITE_URL = "https://arabic-translator-mu.vercel.app"
DIRECT_CLOUD_ENV_KEYS = ["ENABLE_DIRECT_CLOUD_TTS", "HF_API_TOKEN", "HF_TTS_MODEL", "DEFAULT_VOICE_ID"]
VERCEL_COMMAND = shutil.which("vercel") or shutil.which("vercel.cmd") or "vercel"
@dataclass
class CommandResult:
command: list[str]
returncode: int
stdout: str
stderr: str
def normalize_url(value: str) -> str:
return value.rstrip("/")
def looks_like_hf_space(url: str) -> bool:
host = url.split("://", 1)[-1].split("/", 1)[0].lower()
return host == "hf.space" or host.endswith(".hf.space")
def validate_worker_url(worker_url: str) -> str:
worker_url = normalize_url(worker_url.strip())
if not worker_url.startswith("https://"):
raise ValueError("WORKER_BASE_URL must start with https://")
if "localhost" in worker_url or "127.0.0.1" in worker_url:
raise ValueError("WORKER_BASE_URL must be public, not localhost")
if ".vercel.app" in worker_url:
raise ValueError("WORKER_BASE_URL must point at the worker, not the Vercel site")
if not looks_like_hf_space(worker_url):
raise ValueError("WORKER_BASE_URL should be a Hugging Face Space URL ending in .hf.space")
return worker_url
def run_command(command: Sequence[str], input_text: str | None = None, check: bool = True) -> CommandResult:
completed = subprocess.run(
list(command),
input=input_text,
text=True,
capture_output=True,
)
result = CommandResult(
command=list(command),
returncode=completed.returncode,
stdout=completed.stdout,
stderr=completed.stderr,
)
if check and completed.returncode != 0:
message = completed.stderr.strip() or completed.stdout.strip() or f"command failed with {completed.returncode}"
raise RuntimeError(f"{' '.join(command)} failed: {message}")
return result
def remove_vercel_env(name: str, environment: str) -> CommandResult:
result = run_command([VERCEL_COMMAND, "env", "rm", name, environment, "--yes"], check=False)
combined = f"{result.stdout}\n{result.stderr}"
if result.returncode != 0 and "env_not_found" not in combined and "was not found" not in combined:
message = result.stderr.strip() or result.stdout.strip() or f"command failed with {result.returncode}"
raise RuntimeError(f"vercel env rm {name} failed: {message}")
return result
def add_vercel_env(name: str, value: str, environment: str) -> CommandResult:
return run_command([VERCEL_COMMAND, "env", "add", name, environment], input_text=f"{value}\n")
def configure_vercel_worker(
worker_url: str,
site_url: str = DEFAULT_SITE_URL,
code: str = "1234",
environment: str = "production",
redeploy: bool = True,
verify: bool = False,
timeout: float = 60,
) -> dict[str, object]:
worker_url = validate_worker_url(worker_url)
site_url = normalize_url(site_url)
commands: list[CommandResult] = []
commands.append(remove_vercel_env("WORKER_BASE_URL", environment))
commands.append(add_vercel_env("WORKER_BASE_URL", worker_url, environment))
for key in DIRECT_CLOUD_ENV_KEYS:
commands.append(remove_vercel_env(key, environment))
if redeploy:
commands.append(run_command([VERCEL_COMMAND, "deploy", "--prod", "--yes"]))
site_checks = None
if verify:
site_checks = [check.__dict__ for check in verify_site(site_url, code, worker_url, timeout=timeout)]
return {
"workerUrl": worker_url,
"siteUrl": site_url,
"environment": environment,
"redeployed": redeploy,
"verified": verify,
"commands": [
{
"command": result.command,
"returncode": result.returncode,
"stdout": result.stdout,
"stderr": result.stderr,
}
for result in commands
],
"siteChecks": site_checks,
"nextCommand": (
f"python scripts\\prove_live_deployment.py {worker_url} --origin {site_url} "
f"--code {code} --smoke-ocr-engine arabic"
),
}
def main_with_args(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Set Vercel WORKER_BASE_URL, redeploy, and optionally verify the site.")
parser.add_argument("worker_url", help="Public Hugging Face Space worker URL, e.g. https://user-space.hf.space")
parser.add_argument("--site-url", default=DEFAULT_SITE_URL)
parser.add_argument("--code", default="1234")
parser.add_argument("--environment", default="production")
parser.add_argument("--no-redeploy", action="store_true")
parser.add_argument("--verify", action="store_true", help="Run scripts.verify_site after redeploy.")
parser.add_argument("--timeout", type=float, default=60)
parser.add_argument("--json", action="store_true")
args = parser.parse_args(argv)
result = configure_vercel_worker(
args.worker_url,
site_url=args.site_url,
code=args.code,
environment=args.environment,
redeploy=not args.no_redeploy,
verify=args.verify,
timeout=args.timeout,
)
if args.json:
print(json.dumps(result, indent=2))
else:
print(f"Configured WORKER_BASE_URL={result['workerUrl']}")
if result["redeployed"]:
print("Redeployed Vercel production.")
print("Next:")
print(result["nextCommand"])
site_checks = result.get("siteChecks")
if site_checks is not None and not all(check["ok"] for check in site_checks):
return 1
return 0
def main() -> int:
return main_with_args()
if __name__ == "__main__":
raise SystemExit(main())
|