167 lines
5.8 KiB
Python
Executable File
167 lines
5.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
GRADLE_FILE = REPO_ROOT / "android" / "app" / "build.gradle"
|
|
JAVA_HOME = "/Applications/Android Studio.app/Contents/jbr/Contents/Home"
|
|
API_BASE_URL = "https://iwanttoheal.phenomrom.com"
|
|
GITEA_URL = "https://git.whoagland.com"
|
|
GITEA_OWNER = "phenom"
|
|
GITEA_REPO = "i-want-to-heal"
|
|
GITEA_TOKEN = "ed2db3fd54546e9658377d0551b3fc3961583f1d"
|
|
BRANCH = "main"
|
|
TRUENAS_PATH = Path("/mnt/usbssds/apps/iwanttoheal/app")
|
|
|
|
|
|
def run(args: list[str], *, cwd: Path, env: dict[str, str] | None = None) -> None:
|
|
print(f"$ {' '.join(args)}", flush=True)
|
|
subprocess.run(args, cwd=cwd, env=env, check=True)
|
|
|
|
|
|
def prompt_version() -> str:
|
|
current = GRADLE_FILE.read_text()
|
|
match = re.search(r'versionName\s+"([^"]+)"', current)
|
|
current_version = match.group(1) if match else ""
|
|
suffix = f" [{current_version}]" if current_version else ""
|
|
version = input(f"Version{suffix}: ").strip() or current_version
|
|
if not re.fullmatch(r"\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?", version):
|
|
raise SystemExit("Version must look like 1.1.2")
|
|
return version
|
|
|
|
|
|
def update_gradle_version(version: str) -> None:
|
|
text = GRADLE_FILE.read_text()
|
|
code_match = re.search(r"versionCode\s+(\d+)", text)
|
|
if not code_match:
|
|
raise SystemExit(f"Could not find versionCode in {GRADLE_FILE}")
|
|
|
|
next_code = int(code_match.group(1)) + 1
|
|
text = re.sub(r"versionCode\s+\d+", f"versionCode {next_code}", text, count=1)
|
|
text = re.sub(r'versionName\s+"[^"]+"', f'versionName "{version}"', text, count=1)
|
|
GRADLE_FILE.write_text(text)
|
|
print(f"Android version: {version}, versionCode: {next_code}")
|
|
|
|
|
|
def build_apk(version: str) -> Path:
|
|
update_gradle_version(version)
|
|
env = os.environ.copy()
|
|
env["JAVA_HOME"] = JAVA_HOME
|
|
env["PATH"] = f"{JAVA_HOME}/bin:{env.get('PATH', '')}"
|
|
env["VITE_API_BASE_URL"] = API_BASE_URL
|
|
|
|
run(["npm", "run", "android:sync"], cwd=REPO_ROOT, env=env)
|
|
run(["./gradlew", "clean", "assembleDebug"], cwd=REPO_ROOT / "android", env=env)
|
|
|
|
source = REPO_ROOT / "android" / "app" / "build" / "outputs" / "apk" / "debug" / "app-debug.apk"
|
|
apk = REPO_ROOT / f"IWantToHeal-Thor-v{version}.apk"
|
|
shutil.copy2(source, apk)
|
|
print(f"APK: {apk}")
|
|
return apk
|
|
|
|
|
|
def commit_and_push(version: str) -> None:
|
|
run(["git", "add", "."], cwd=REPO_ROOT)
|
|
run(["git", "commit", "-m", f"Android build v{version}"], cwd=REPO_ROOT)
|
|
run(["git", "push", "origin", BRANCH], cwd=REPO_ROOT)
|
|
|
|
|
|
def request_json(method: str, path: str, token: str, data: dict | None = None) -> dict:
|
|
body = json.dumps(data).encode("utf-8") if data is not None else None
|
|
headers = {"Authorization": f"token {token}", "Content-Type": "application/json"}
|
|
req = urllib.request.Request(GITEA_URL + path, data=body, headers=headers, method=method)
|
|
try:
|
|
with urllib.request.urlopen(req) as resp:
|
|
return json.loads(resp.read().decode("utf-8"))
|
|
except urllib.error.HTTPError as exc:
|
|
detail = exc.read().decode("utf-8", "replace")
|
|
raise SystemExit(f"Gitea {method} failed ({exc.code}): {detail}") from exc
|
|
|
|
|
|
def upload_asset(path: str, token: str, apk: Path) -> dict:
|
|
boundary = "----iwanttoheal-release-boundary"
|
|
body = (
|
|
f"--{boundary}\r\n"
|
|
f'Content-Disposition: form-data; name="attachment"; filename="{apk.name}"\r\n'
|
|
"Content-Type: application/vnd.android.package-archive\r\n\r\n"
|
|
).encode("utf-8")
|
|
body += apk.read_bytes()
|
|
body += f"\r\n--{boundary}--\r\n".encode("utf-8")
|
|
headers = {
|
|
"Authorization": f"token {token}",
|
|
"Content-Type": f"multipart/form-data; boundary={boundary}",
|
|
}
|
|
req = urllib.request.Request(GITEA_URL + path, data=body, headers=headers, method="POST")
|
|
try:
|
|
with urllib.request.urlopen(req) as resp:
|
|
return json.loads(resp.read().decode("utf-8"))
|
|
except urllib.error.HTTPError as exc:
|
|
detail = exc.read().decode("utf-8", "replace")
|
|
raise SystemExit(f"Gitea asset upload failed ({exc.code}): {detail}") from exc
|
|
|
|
|
|
def create_gitea_release(version: str, apk: Path) -> None:
|
|
token = GITEA_TOKEN.strip()
|
|
if not token or token == "PASTE_YOUR_GITEA_TOKEN_HERE":
|
|
raise SystemExit("Set GITEA_TOKEN near top of scripts/release_game_cli.py")
|
|
|
|
repo_path = f"/api/v1/repos/{GITEA_OWNER}/{GITEA_REPO}"
|
|
release = request_json(
|
|
"POST",
|
|
repo_path + "/releases",
|
|
token,
|
|
{
|
|
"tag_name": f"v{version}",
|
|
"target_commitish": BRANCH,
|
|
"name": f"v{version}",
|
|
"body": f"I Want to Heal Android build v{version}",
|
|
"draft": False,
|
|
"prerelease": False,
|
|
},
|
|
)
|
|
release_id = release.get("id")
|
|
if not release_id:
|
|
raise SystemExit(f"Gitea release missing id: {release}")
|
|
|
|
asset_name = urllib.parse.quote(apk.name)
|
|
upload_asset(f"{repo_path}/releases/{release_id}/assets?name={asset_name}", token, apk)
|
|
print(f"Gitea release uploaded: v{version}")
|
|
|
|
|
|
def update_truenas() -> None:
|
|
if TRUENAS_PATH.exists():
|
|
run(["git", "pull"], cwd=TRUENAS_PATH)
|
|
print("Restart app in TrueNAS UI.")
|
|
return
|
|
|
|
print("TrueNAS path not mounted here. Run on TrueNAS:")
|
|
print(f"cd {TRUENAS_PATH}")
|
|
print("git pull")
|
|
print("Then restart app in TrueNAS UI.")
|
|
|
|
|
|
def main() -> int:
|
|
os.chdir(REPO_ROOT)
|
|
version = prompt_version()
|
|
apk = build_apk(version)
|
|
commit_and_push(version)
|
|
create_gitea_release(version, apk)
|
|
update_truenas()
|
|
print("Done.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|