Files
noname-shuying/tools/prepare_release_manifest.py
T
Diuse 9e1732d2f8 fixed # 补全部分卡牌
fixed # 修复部分已知BUG
2026-10-04 19:32:38 +08:00

86 lines
3.4 KiB
Python

"""Generate release manifests from committed Git bytes, without creating a tag."""
from __future__ import annotations
import argparse
import os
import re
import shutil
import subprocess
import sys
import tempfile
import zipfile
from pathlib import Path
def git(root: Path, *args: str) -> bytes:
return subprocess.run(["git", "-c", "core.autocrlf=false", *args], cwd=root,
check=True, stdout=subprocess.PIPE).stdout
def prepare(root: Path, version: str) -> None:
if not re.fullmatch(r"\d+\.\d+\.\d+\.\d+(?:-rc\.\d+)?", version):
raise ValueError("Invalid release version")
repository = Path(git(root, "rev-parse", "--show-toplevel").decode("utf-8").strip()).resolve()
if repository != root:
raise ValueError("Run this tool from the extension repository")
rows = git(root, "status", "--porcelain=v1", "-z", "--untracked-files=all").decode("utf-8").split("\0")
dirty = []
i = 0
while i < len(rows):
row = rows[i]
if row:
path = row[3:]
if not path.startswith("dist/"):
dirty.append(path)
if "R" in row[:2] or "C" in row[:2]:
i += 1
if i < len(rows) and not rows[i].startswith("dist/"):
dirty.append(rows[i])
i += 1
if dirty:
raise ValueError("Commit source and new runtime files first; pending files: " + ", ".join(dirty[:8]))
with tempfile.TemporaryDirectory(prefix="shuying-release-") as temporary:
workspace = Path(temporary)
archive = workspace / "source.zip"
stage = workspace / "source"
git(root, "archive", "--format=zip", f"--output={archive}", "HEAD")
with zipfile.ZipFile(archive) as source:
# Git trees cannot contain traversal paths; still check before extracting.
for item in source.infolist():
path = stage / item.filename
if not path.resolve().is_relative_to(stage.resolve()):
raise ValueError("Unsafe archive path")
source.extractall(stage)
subprocess.run([sys.executable, str(stage / "tools/generate_manifest.py"),
"--root", str(stage), "--version", version], check=True)
subprocess.run([sys.executable, str(stage / "tools/verify_manifest.py"),
"--root", str(stage), "--version", version], check=True)
output = root / "dist"
output.mkdir(exist_ok=True)
shutil.copyfile(stage / "dist/manifest.json", output / "manifest.json")
components = output / "modules"
components.mkdir(exist_ok=True)
generated = list((stage / "dist/modules").glob("*.json"))
expected = {path.name for path in generated}
for path in generated:
shutil.copyfile(path, components / path.name)
for stale in components.glob("*.json"):
if stale.name not in expected:
stale.unlink()
print("Manifests are ready. Commit dist/manifest.json and dist/modules/*.json before creating the release tag.")
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", default=".")
parser.add_argument("--version", default=os.environ.get("SHUYING_RELEASE_VERSION"))
args = parser.parse_args()
if not args.version:
parser.error("--version is required")
prepare(Path(args.root).resolve(), args.version)
if __name__ == "__main__":
main()