Update 3D game 2026-07-10 21:20
This commit is contained in:
Executable
+167
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build, test, commit, and publish the game to its Gitea repository."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
GITEA_REMOTE = "https://git.whoagland.com/phenom/i-want-to-heal-mmo.git"
|
||||
BRANCH = "main"
|
||||
TRUENAS_PATH = Path("/mnt/usbssds/apps/iwanttoheal/app")
|
||||
|
||||
|
||||
def run(args: list[str], *, cwd: Path = REPO_ROOT) -> None:
|
||||
print(f"$ {' '.join(args)}", flush=True)
|
||||
subprocess.run(args, cwd=cwd, check=True)
|
||||
|
||||
|
||||
def capture(args: list[str], *, cwd: Path = REPO_ROOT) -> str:
|
||||
return subprocess.run(
|
||||
args,
|
||||
cwd=cwd,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout.strip()
|
||||
|
||||
|
||||
def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||
default_message = f"Update 3D game {datetime.now():%Y-%m-%d %H:%M}"
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Build, test, and push I Want to Heal 3D MMO to Gitea."
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
"--message",
|
||||
default=default_message,
|
||||
help=f"Git commit message (default: {default_message!r}).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-checks",
|
||||
action="store_true",
|
||||
help="Skip dependency, build, and test checks before publishing.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-truenas",
|
||||
action="store_true",
|
||||
help="Do not pull the new commit into a locally mounted TrueNAS app directory.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="Run checks and validate Git configuration without staging, committing, pushing, or pulling.",
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def package_manager() -> list[str]:
|
||||
pnpm = shutil.which("pnpm")
|
||||
if pnpm:
|
||||
return [pnpm]
|
||||
|
||||
corepack = shutil.which("corepack")
|
||||
if corepack:
|
||||
return [corepack, "pnpm"]
|
||||
|
||||
raise SystemExit("pnpm or corepack is required")
|
||||
|
||||
|
||||
def run_checks() -> None:
|
||||
pnpm = package_manager()
|
||||
run([*pnpm, "install", "--frozen-lockfile"])
|
||||
run([*pnpm, "run", "build"])
|
||||
run([*pnpm, "test"])
|
||||
|
||||
|
||||
def normalized_remote(url: str) -> str:
|
||||
return url.rstrip("/").removesuffix(".git")
|
||||
|
||||
|
||||
def ensure_git_target(*, dry_run: bool) -> None:
|
||||
try:
|
||||
origin = capture(["git", "remote", "get-url", "origin"])
|
||||
except subprocess.CalledProcessError:
|
||||
if dry_run:
|
||||
raise SystemExit(f"Missing origin remote; expected {GITEA_REMOTE}")
|
||||
run(["git", "remote", "add", "origin", GITEA_REMOTE])
|
||||
origin = GITEA_REMOTE
|
||||
|
||||
if normalized_remote(origin) != normalized_remote(GITEA_REMOTE):
|
||||
raise SystemExit(
|
||||
f"Refusing to publish to unexpected origin {origin!r}; expected {GITEA_REMOTE!r}"
|
||||
)
|
||||
|
||||
branch = capture(["git", "branch", "--show-current"])
|
||||
if branch != BRANCH:
|
||||
raise SystemExit(
|
||||
f"Refusing to publish branch {branch!r}; switch to {BRANCH!r} first"
|
||||
)
|
||||
|
||||
|
||||
def has_staged_changes() -> bool:
|
||||
result = subprocess.run(
|
||||
["git", "diff", "--cached", "--quiet", "--exit-code"],
|
||||
cwd=REPO_ROOT,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode not in (0, 1):
|
||||
raise SystemExit("Could not inspect staged Git changes")
|
||||
return result.returncode == 1
|
||||
|
||||
|
||||
def commit_and_push(message: str) -> None:
|
||||
run(["git", "add", "--all"])
|
||||
if has_staged_changes():
|
||||
run(["git", "commit", "-m", message])
|
||||
else:
|
||||
print("No changes to commit.")
|
||||
run(["git", "push", "--set-upstream", "origin", BRANCH])
|
||||
|
||||
|
||||
def update_truenas() -> None:
|
||||
if (TRUENAS_PATH / ".git").is_dir():
|
||||
origin = capture(["git", "remote", "get-url", "origin"], cwd=TRUENAS_PATH)
|
||||
if normalized_remote(origin) != normalized_remote(GITEA_REMOTE):
|
||||
raise SystemExit(
|
||||
"Refusing to update TrueNAS from its old repository origin "
|
||||
f"{origin!r}. Replace that checkout with {GITEA_REMOTE!r} first."
|
||||
)
|
||||
run(["git", "pull", "--ff-only", "origin", BRANCH], cwd=TRUENAS_PATH)
|
||||
print("TrueNAS source updated. Restart the iwanttoheal app in the TrueNAS UI.")
|
||||
return
|
||||
|
||||
print("New TrueNAS app clone is not mounted on this machine.")
|
||||
print(f"Move or remove the old app directory at {TRUENAS_PATH}, then run:")
|
||||
print(f" git clone {GITEA_REMOTE} {TRUENAS_PATH}")
|
||||
print("For later releases:")
|
||||
print(f" git -C {TRUENAS_PATH} pull --ff-only origin {BRANCH}")
|
||||
print("Then restart the iwanttoheal app in the TrueNAS UI.")
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = parse_args(sys.argv[1:] if argv is None else argv)
|
||||
ensure_git_target(dry_run=args.dry_run)
|
||||
|
||||
if not args.skip_checks:
|
||||
run_checks()
|
||||
|
||||
if args.dry_run:
|
||||
print("Dry run complete. No Git or TrueNAS state changed.")
|
||||
return 0
|
||||
|
||||
commit_and_push(args.message)
|
||||
if not args.skip_truenas:
|
||||
update_truenas()
|
||||
print("Publish complete.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user