"""只向固定真寻预览分支普通更新;默认离线dry-run。""" import argparse import base64 import json import os from urllib.error import HTTPError from urllib.request import Request, urlopen from verify_zhenxun import read_package, verify_files REPOSITORY = "Shu-Ying/zhenxun_plugin_farm" BRANCH = "farm-preview" def github(method, path, payload, token): request = Request( f"https://api.github.com/repos/{REPOSITORY}/{path}", data=json.dumps(payload).encode() if payload is not None else None, headers={"Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28", "User-Agent": "farm-preview-publisher"}, method=method, ) try: with urlopen(request, timeout=30) as response: return json.load(response) except HTTPError as error: if error.code == 404 and method == "GET": return None raise RuntimeError( f"GitHub request failed: HTTP {error.code}", ) from None def publish(package, confirmed=False, request=None): files = read_package(package) manifest = verify_files(files) result = {"repository": REPOSITORY, "branch": BRANCH, "version": manifest["version"], "published": False} if not confirmed: return result if manifest["source"]["dirty"]: raise ValueError("Publish requires a clean source checkout") token = os.environ.get("FARM_RELEASE_TOKEN") if not token: raise ValueError("Configure FARM_RELEASE_TOKEN in a protected job") call = request or (lambda method, path, data: github( method, path, data, token, )) repository = call("GET", "", None) if not repository or repository.get("default_branch") == BRANCH: raise ValueError("Preview must not be the repository default branch") ref = call("GET", f"git/ref/heads/{BRANCH}", None) parent = ref["object"]["sha"] if ref else None if parent: owned = call("GET", f"contents/FARM-MANIFEST.json?ref={BRANCH}", None) if not owned or owned.get("encoding") != "base64": raise ValueError("Existing preview branch is not generated by Farm") previous = json.loads(base64.b64decode(owned["content"])) if (previous.get("target") != "zhenxun" or previous.get("plugin") != manifest["plugin"]): raise ValueError("Existing preview branch ownership differs") tree = [] for name, content in sorted(files.items()): blob = call("POST", "git/blobs", { "content": base64.b64encode(content).decode(), "encoding":"base64", }) tree.append({"path":name, "mode":"100644", "type":"blob", "sha":blob["sha"]}) generated = call("POST", "git/trees", {"tree":tree}) commit = call("POST", "git/commits", { "message": f"Generate farm preview {manifest['version']} " f"from {manifest['source']['commit']}", "tree": generated["sha"], "parents": [parent] if parent else [], }) if parent: current = call("GET", f"git/ref/heads/{BRANCH}", None) if not current or current["object"]["sha"] != parent: raise RuntimeError("Preview branch changed; retry explicitly") call("PATCH", f"git/refs/heads/{BRANCH}", { "sha": commit["sha"], "force": False, }) else: call("POST", "git/refs", { "ref": f"refs/heads/{BRANCH}", "sha": commit["sha"], }) return {**result, "published": True, "commit": commit["sha"]} def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("package") parser.add_argument("--publish", action="store_true") args = parser.parse_args() print(json.dumps(publish(args.package, args.publish), ensure_ascii=False)) if __name__ == "__main__": main()