arabic-audio-reader-worker / scripts /configure_vercel_worker.py
Syncre's picture
Deploy Arabic Audio Reader worker
088795a verified
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())