mirror of
https://github.com/Shu-Ying/noname-shuying.git
synced 2026-10-08 01:20:00 +08:00
86 lines
3.4 KiB
Python
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()
|