168 lines
5.0 KiB
Python
Executable File
168 lines
5.0 KiB
Python
Executable File
#!/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())
|