сборка манифеста для лаунчера

This commit is contained in:
2026-03-21 17:18:47 +04:00
parent bb67aa2648
commit a196cef207
2 changed files with 165 additions and 1 deletions
+3 -1
View File
@@ -1 +1,3 @@
.env .env
__pycache__/
manifest.json
+162
View File
@@ -0,0 +1,162 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import hashlib
import json
import os
import sys
from pathlib import Path
from typing import Optional
IGNORED_TOP_LEVEL_DIRS = {
"Cache",
"Errors",
"Logs",
"Screenshots",
"WTF",
}
ENV_VAR_NAME = "WOW_HOME"
def sha256_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def sha256_file(path: Path, chunk_size: int = 1024 * 1024) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
while chunk := f.read(chunk_size):
h.update(chunk)
return h.hexdigest()
def is_ignored(rel_path: Path) -> bool:
if not rel_path.parts:
return False
return rel_path.parts[0] in IGNORED_TOP_LEVEL_DIRS
def load_dotenv(env_path: Path) -> dict[str, str]:
values: dict[str, str] = {}
if not env_path.exists():
return values
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
key = key.strip()
value = value.strip()
if key.startswith("export "):
key = key.removeprefix("export ").strip()
if not key:
continue
if len(value) >= 2 and value[0] == value[-1] and value[0] in {"'", '"'}:
value = value[1:-1]
values[key] = value
return values
def resolve_root(explicit_dir: Optional[str]) -> Optional[Path]:
if explicit_dir:
return Path(explicit_dir).resolve()
env_value = os.environ.get(ENV_VAR_NAME)
if env_value:
return Path(env_value).expanduser().resolve()
project_root = Path(__file__).resolve().parent
dotenv_values = load_dotenv(project_root / ".env")
env_value = dotenv_values.get(ENV_VAR_NAME)
if env_value:
os.environ.setdefault(ENV_VAR_NAME, env_value)
return Path(env_value).expanduser().resolve()
return None
def compute_build_hash(files: list[dict]) -> str:
lines = [
f'{item["path"]}:{item["size"]}:{item["sha256"]}'
for item in sorted(files, key=lambda x: x["path"])
]
payload = "\n".join(lines).encode("utf-8")
return sha256_bytes(payload)
def main() -> int:
parser = argparse.ArgumentParser(description="Collect files into manifest.json")
parser.add_argument(
"--dir",
help=f"Directory to scan. Defaults to {ENV_VAR_NAME} from env or .env",
)
parser.add_argument("--output", default="manifest.json", help="Output file")
args = parser.parse_args()
root = resolve_root(args.dir)
output = Path(args.output).resolve()
if root is None:
print(
f"ERROR: directory is not set. Pass --dir or define {ENV_VAR_NAME} in env/.env",
file=sys.stderr,
)
return 1
if not root.exists() or not root.is_dir():
print(f"ERROR: directory not found: {root}", file=sys.stderr)
return 1
files = []
for file_path in sorted(root.rglob("*")):
if not file_path.is_file():
continue
if file_path.resolve() == output:
continue
rel_path_obj = file_path.relative_to(root)
if is_ignored(rel_path_obj):
continue
rel_path = rel_path_obj.as_posix()
files.append({
"path": rel_path,
"size": file_path.stat().st_size,
"sha256": sha256_file(file_path),
})
build_hash = compute_build_hash(files)
manifest = {
"buildHash": build_hash,
"files": files
}
with output.open("w", encoding="utf-8", newline="\n") as f:
json.dump(manifest, f, ensure_ascii=False, indent=2)
f.write("\n")
print(f"OK: written {output}")
print(f"Files: {len(files)}")
print(f"Build hash: {build_hash}")
return 0
if __name__ == "__main__":
raise SystemExit(main())