#!/usr/bin/env python3 """Deploy the website (and the download zip) to Vercel as a production deployment. Only uses the standard library. The token comes from $VERCEL_TOKEN or from ~/Dokumente/tokens/vercel token, and is only ever sent in the Authorization header. Usage: python3 tools/deploy_vercel.py """ import hashlib import json import os import re import sys import tempfile import time import urllib.error import urllib.request from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) from deploy_space import ROOT, build_zip # noqa: E402 PROJECT = "techglass-mint" API = "https://api.vercel.com" SITE = ["index.html", "icon.svg", "assets"] def token(): if os.environ.get("VERCEL_TOKEN"): return os.environ["VERCEL_TOKEN"] text = (Path.home() / "Dokumente/tokens/vercel token").read_text() return max(re.findall(r"[A-Za-z0-9_\-]{20,}", text), key=len) def call(method, path, body=None, headers=None): h = {"Authorization": f"Bearer {token()}", **(headers or {})} data = body if body is not None and not isinstance(body, bytes): data = json.dumps(body).encode() h["Content-Type"] = "application/json" req = urllib.request.Request(API + path, data=data, method=method, headers=h) try: with urllib.request.urlopen(req, timeout=180) as r: text = r.read().decode() except urllib.error.HTTPError as e: sys.exit(f"Vercel answered {e.code} for {path}: {e.read().decode()[:400]}") return json.loads(text) if text.strip() else {} def upload_files(): entries = [] with tempfile.TemporaryDirectory() as tmp: zip_path = Path(tmp) / "techglass-mint.zip" build_zip(zip_path) items = [] for name in SITE: src = ROOT / name for p in ([src] if src.is_file() else sorted(src.rglob("*"))): if p.is_file(): items.append((p, str(p.relative_to(ROOT)))) items.append((zip_path, "techglass-mint.zip")) for path, rel in items: data = path.read_bytes() sha = hashlib.sha1(data).hexdigest() call("POST", "/v2/files", data, {"Content-Type": "application/octet-stream", "x-vercel-digest": sha, "Content-Length": str(len(data))}) entries.append({"file": rel, "sha": sha, "size": len(data)}) return entries def main(): files = upload_files() print(f"Uploaded {len(files)} files") dep = call("POST", "/v13/deployments?skipAutoDetectionConfirmation=1", { "name": PROJECT, "files": files, "target": "production", "projectSettings": {"framework": None, "buildCommand": None, "installCommand": None, "outputDirectory": None, "devCommand": None}, }) for _ in range(90): state = call("GET", f"/v13/deployments/{dep['id']}") if state.get("readyState") in ("READY", "ERROR", "CANCELED"): break time.sleep(2) print("State:", state.get("readyState")) print("Deployment:", "https://" + state.get("url", "")) for alias in state.get("alias", []): print("Alias:", "https://" + alias) if __name__ == "__main__": main()