93 lines
2.6 KiB
Python
93 lines
2.6 KiB
Python
"""生成可校验的 Client 发布清单。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
MANIFEST_FILE_NAME = "autobuy_manifest.json"
|
|
_VERSION_PATTERN = re.compile(
|
|
r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?$"
|
|
)
|
|
|
|
|
|
def versioned_manifest_file_name(version: str) -> str:
|
|
"""返回版本化清单文件名,例如 ``autobuy_manifest_0.1.0.json``。"""
|
|
|
|
normalized = version.strip()
|
|
if _VERSION_PATTERN.fullmatch(normalized) is None:
|
|
raise ValueError("版本号必须是三段或四段非负整数")
|
|
return f"autobuy_manifest_{normalized}.json"
|
|
|
|
|
|
def file_description(path: Path) -> dict[str, Any]:
|
|
"""返回发布文件的名称、字节大小和 SHA256。"""
|
|
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as file:
|
|
for block in iter(lambda: file.read(1024 * 1024), b""):
|
|
digest.update(block)
|
|
return {
|
|
"file": path.name,
|
|
"size": path.stat().st_size,
|
|
"sha256": digest.hexdigest(),
|
|
}
|
|
|
|
|
|
def build_manifest(version: str, update_zip: Path, portable_zip: Path) -> dict[str, Any]:
|
|
"""组装发布清单数据。"""
|
|
|
|
if not version.strip():
|
|
raise ValueError("版本号不能为空")
|
|
for path in (update_zip, portable_zip):
|
|
if not path.is_file():
|
|
raise FileNotFoundError(path)
|
|
|
|
return {
|
|
"schema_version": 1,
|
|
"product": "CMAutoBuy",
|
|
"version": version,
|
|
"update": file_description(update_zip),
|
|
"portable": file_description(portable_zip),
|
|
}
|
|
|
|
|
|
def write_manifest(
|
|
version: str,
|
|
update_zip: Path,
|
|
portable_zip: Path,
|
|
output: Path,
|
|
) -> None:
|
|
"""把发布清单写成不带 BOM 的 UTF-8 JSON。"""
|
|
|
|
manifest = build_manifest(version, update_zip, portable_zip)
|
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
output.write_text(
|
|
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description="生成 Client 发布清单")
|
|
parser.add_argument("--version", required=True)
|
|
parser.add_argument("--update", required=True, type=Path)
|
|
parser.add_argument("--portable", required=True, type=Path)
|
|
parser.add_argument("--output", required=True, type=Path)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
write_manifest(args.version, args.update, args.portable, args.output)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|