Android build v1.1.26
This commit is contained in:
Binary file not shown.
@@ -7,7 +7,7 @@ android {
|
|||||||
applicationId "com.warren.iwanttoheal"
|
applicationId "com.warren.iwanttoheal"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 105
|
versionCode 106
|
||||||
versionName "1.1.26"
|
versionName "1.1.26"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
|
|||||||
@@ -96,6 +96,12 @@ curl -sS -X POST "$GITEA_URL/api/v1/repos/$GITEA_OWNER/$GITEA_REPO/releases/$REL
|
|||||||
-F "attachment=@$APK"
|
-F "attachment=@$APK"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If the CLI release fails after creating the Gitea release but before uploading the APK asset, retry only the release upload:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run release:cli -- --release-only 1.1.26
|
||||||
|
```
|
||||||
|
|
||||||
## Step 5: Update TrueNAS
|
## Step 5: Update TrueNAS
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
Binary file not shown.
+44
-15
@@ -5,6 +5,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import queue
|
import queue
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
@@ -153,6 +154,48 @@ def gitea_request(
|
|||||||
except urllib.error.HTTPError as exc:
|
except urllib.error.HTTPError as exc:
|
||||||
data = exc.read()
|
data = exc.read()
|
||||||
return exc.code, data
|
return exc.code, data
|
||||||
|
except urllib.error.URLError as exc:
|
||||||
|
raise ReleaseError(f"{method} {url} failed: {exc.reason}") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def gitea_upload_asset(config: ReleaseConfig, path: str, apk_path: Path) -> tuple[int, bytes]:
|
||||||
|
curl = shutil.which("curl")
|
||||||
|
if curl is None:
|
||||||
|
raise ReleaseError("curl is required for Gitea asset uploads")
|
||||||
|
|
||||||
|
url = config.gitea_url.rstrip("/") + path
|
||||||
|
proc = subprocess.run(
|
||||||
|
[
|
||||||
|
curl,
|
||||||
|
"--silent",
|
||||||
|
"--show-error",
|
||||||
|
"--retry",
|
||||||
|
"3",
|
||||||
|
"--retry-all-errors",
|
||||||
|
"--retry-delay",
|
||||||
|
"2",
|
||||||
|
"--request",
|
||||||
|
"POST",
|
||||||
|
url,
|
||||||
|
"--header",
|
||||||
|
f"Authorization: token {config.token}",
|
||||||
|
"--form",
|
||||||
|
f"attachment=@{apk_path}",
|
||||||
|
"--write-out",
|
||||||
|
"\n%{http_code}",
|
||||||
|
],
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
detail = (proc.stderr or proc.stdout).decode("utf-8", "replace").strip()
|
||||||
|
raise ReleaseError(f"Gitea asset upload failed: curl exited {proc.returncode}: {detail}")
|
||||||
|
try:
|
||||||
|
body, status_text = proc.stdout.rsplit(b"\n", 1)
|
||||||
|
return int(status_text), body
|
||||||
|
except ValueError as exc:
|
||||||
|
output = proc.stdout.decode("utf-8", "replace")
|
||||||
|
raise ReleaseError(f"Gitea asset upload returned malformed curl output: {output}") from exc
|
||||||
|
|
||||||
|
|
||||||
def parse_json_response(status: int, data: bytes, action: str) -> dict:
|
def parse_json_response(status: int, data: bytes, action: str) -> dict:
|
||||||
@@ -211,26 +254,12 @@ def upload_release_asset(config: ReleaseConfig, log: Callable[[str], None]) -> N
|
|||||||
raise ReleaseError(f"APK not found for release upload: {apk_path}")
|
raise ReleaseError(f"APK not found for release upload: {apk_path}")
|
||||||
release_id = find_or_create_release(config, log)
|
release_id = find_or_create_release(config, log)
|
||||||
|
|
||||||
boundary = f"----iwanttoheal{int(time.time() * 1000)}"
|
|
||||||
header = (
|
|
||||||
f"--{boundary}\r\n"
|
|
||||||
f'Content-Disposition: form-data; name="attachment"; filename="{apk_path.name}"\r\n'
|
|
||||||
"Content-Type: application/vnd.android.package-archive\r\n\r\n"
|
|
||||||
).encode("utf-8")
|
|
||||||
footer = f"\r\n--{boundary}--\r\n".encode("utf-8")
|
|
||||||
body = header + apk_path.read_bytes() + footer
|
|
||||||
asset_name = urllib.parse.quote(apk_path.name)
|
asset_name = urllib.parse.quote(apk_path.name)
|
||||||
path = (
|
path = (
|
||||||
f"/api/v1/repos/{config.gitea_owner}/{config.gitea_repo}"
|
f"/api/v1/repos/{config.gitea_owner}/{config.gitea_repo}"
|
||||||
f"/releases/{release_id}/assets?name={asset_name}"
|
f"/releases/{release_id}/assets?name={asset_name}"
|
||||||
)
|
)
|
||||||
status, data = gitea_request(
|
status, data = gitea_upload_asset(config, path, apk_path)
|
||||||
config,
|
|
||||||
"POST",
|
|
||||||
path,
|
|
||||||
body=body,
|
|
||||||
content_type=f"multipart/form-data; boundary={boundary}",
|
|
||||||
)
|
|
||||||
if status == 409:
|
if status == 409:
|
||||||
raise ReleaseError(f"Release asset already exists: {apk_path.name}")
|
raise ReleaseError(f"Release asset already exists: {apk_path.name}")
|
||||||
parse_json_response(status, data, "Upload release asset")
|
parse_json_response(status, data, "Upload release asset")
|
||||||
|
|||||||
+97
-24
@@ -68,9 +68,16 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Skip the RESET confirmation when used with --reset-player-data.",
|
help="Skip the RESET confirmation when used with --reset-player-data.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--release-only",
|
||||||
|
metavar="VERSION",
|
||||||
|
help="Create or reuse the Gitea release and upload an existing IWantToHeal-Thor-vVERSION.apk, without rebuilding.",
|
||||||
|
)
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
if args.yes_reset and not args.reset_player_data:
|
if args.yes_reset and not args.reset_player_data:
|
||||||
parser.error("--yes-reset requires --reset-player-data")
|
parser.error("--yes-reset requires --reset-player-data")
|
||||||
|
if args.release_only and args.reset_player_data:
|
||||||
|
parser.error("--release-only cannot be combined with --reset-player-data")
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
@@ -131,44 +138,90 @@ def request_json(method: str, path: str, token: str, data: dict | None = None) -
|
|||||||
except urllib.error.HTTPError as exc:
|
except urllib.error.HTTPError as exc:
|
||||||
detail = exc.read().decode("utf-8", "replace")
|
detail = exc.read().decode("utf-8", "replace")
|
||||||
raise SystemExit(f"Gitea {method} failed ({exc.code}): {detail}") from exc
|
raise SystemExit(f"Gitea {method} failed ({exc.code}): {detail}") from exc
|
||||||
|
except urllib.error.URLError as exc:
|
||||||
|
raise SystemExit(f"Gitea {method} failed: {exc.reason}") from exc
|
||||||
|
|
||||||
|
|
||||||
def upload_asset(path: str, token: str, apk: Path) -> dict:
|
def upload_asset(path: str, token: str, apk: Path) -> dict:
|
||||||
boundary = "----iwanttoheal-release-boundary"
|
url = GITEA_URL + path
|
||||||
body = (
|
curl = shutil.which("curl")
|
||||||
f"--{boundary}\r\n"
|
if curl is None:
|
||||||
f'Content-Disposition: form-data; name="attachment"; filename="{apk.name}"\r\n'
|
raise SystemExit("curl is required for Gitea asset uploads")
|
||||||
"Content-Type: application/vnd.android.package-archive\r\n\r\n"
|
|
||||||
).encode("utf-8")
|
proc = subprocess.run(
|
||||||
body += apk.read_bytes()
|
[
|
||||||
body += f"\r\n--{boundary}--\r\n".encode("utf-8")
|
curl,
|
||||||
headers = {
|
"--silent",
|
||||||
"Authorization": f"token {token}",
|
"--show-error",
|
||||||
"Content-Type": f"multipart/form-data; boundary={boundary}",
|
"--retry",
|
||||||
}
|
"3",
|
||||||
req = urllib.request.Request(GITEA_URL + path, data=body, headers=headers, method="POST")
|
"--retry-all-errors",
|
||||||
|
"--retry-delay",
|
||||||
|
"2",
|
||||||
|
"--request",
|
||||||
|
"POST",
|
||||||
|
url,
|
||||||
|
"--header",
|
||||||
|
f"Authorization: token {token}",
|
||||||
|
"--form",
|
||||||
|
f"attachment=@{apk}",
|
||||||
|
"--write-out",
|
||||||
|
"\n%{http_code}",
|
||||||
|
],
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
detail = proc.stderr.strip() or proc.stdout.strip()
|
||||||
|
raise SystemExit(f"Gitea asset upload failed: curl exited {proc.returncode}: {detail}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
body, status_text = proc.stdout.rsplit("\n", 1)
|
||||||
|
status = int(status_text)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise SystemExit(f"Gitea asset upload returned malformed curl output: {proc.stdout}") from exc
|
||||||
|
|
||||||
|
if status >= 400:
|
||||||
|
raise SystemExit(f"Gitea asset upload failed ({status}): {body}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = json.loads(body or "{}")
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise SystemExit(f"Gitea asset upload returned invalid JSON: {body}") from exc
|
||||||
|
if not isinstance(parsed, dict):
|
||||||
|
raise SystemExit(f"Gitea asset upload returned unexpected JSON: {parsed}")
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def find_or_create_release(version: str, token: str) -> int:
|
||||||
|
repo_path = f"/api/v1/repos/{GITEA_OWNER}/{GITEA_REPO}"
|
||||||
|
tag = f"v{version}"
|
||||||
|
tag_path = repo_path + "/releases/tags/" + urllib.parse.quote(tag, safe="")
|
||||||
|
req = urllib.request.Request(GITEA_URL + tag_path, headers={"Authorization": f"token {token}"}, method="GET")
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(req) as resp:
|
with urllib.request.urlopen(req) as resp:
|
||||||
return json.loads(resp.read().decode("utf-8"))
|
release = json.loads(resp.read().decode("utf-8"))
|
||||||
|
release_id = release.get("id")
|
||||||
|
if not release_id:
|
||||||
|
raise SystemExit(f"Gitea release missing id: {release}")
|
||||||
|
print(f"Gitea release exists: {tag} (id {release_id})")
|
||||||
|
return int(release_id)
|
||||||
except urllib.error.HTTPError as exc:
|
except urllib.error.HTTPError as exc:
|
||||||
|
if exc.code != 404:
|
||||||
detail = exc.read().decode("utf-8", "replace")
|
detail = exc.read().decode("utf-8", "replace")
|
||||||
raise SystemExit(f"Gitea asset upload failed ({exc.code}): {detail}") from exc
|
raise SystemExit(f"Gitea release lookup failed ({exc.code}): {detail}") from exc
|
||||||
|
except urllib.error.URLError as exc:
|
||||||
|
raise SystemExit(f"Gitea release lookup failed: {exc.reason}") 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(
|
release = request_json(
|
||||||
"POST",
|
"POST",
|
||||||
repo_path + "/releases",
|
repo_path + "/releases",
|
||||||
token,
|
token,
|
||||||
{
|
{
|
||||||
"tag_name": f"v{version}",
|
"tag_name": tag,
|
||||||
"target_commitish": BRANCH,
|
"target_commitish": BRANCH,
|
||||||
"name": f"v{version}",
|
"name": tag,
|
||||||
"body": f"I Want to Heal Android build v{version}",
|
"body": f"I Want to Heal Android build v{version}",
|
||||||
"draft": False,
|
"draft": False,
|
||||||
"prerelease": False,
|
"prerelease": False,
|
||||||
@@ -177,7 +230,17 @@ def create_gitea_release(version: str, apk: Path) -> None:
|
|||||||
release_id = release.get("id")
|
release_id = release.get("id")
|
||||||
if not release_id:
|
if not release_id:
|
||||||
raise SystemExit(f"Gitea release missing id: {release}")
|
raise SystemExit(f"Gitea release missing id: {release}")
|
||||||
|
print(f"Gitea release created: {tag} (id {release_id})")
|
||||||
|
return int(release_id)
|
||||||
|
|
||||||
|
|
||||||
|
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_id = find_or_create_release(version, token)
|
||||||
asset_name = urllib.parse.quote(apk.name)
|
asset_name = urllib.parse.quote(apk.name)
|
||||||
upload_asset(f"{repo_path}/releases/{release_id}/assets?name={asset_name}", token, apk)
|
upload_asset(f"{repo_path}/releases/{release_id}/assets?name={asset_name}", token, apk)
|
||||||
print(f"Gitea release uploaded: v{version}")
|
print(f"Gitea release uploaded: v{version}")
|
||||||
@@ -289,6 +352,16 @@ def reset_player_data(database_path: Path, *, assume_yes: bool) -> None:
|
|||||||
def main(argv: list[str] | None = None) -> int:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
args = parse_args(sys.argv[1:] if argv is None else argv)
|
args = parse_args(sys.argv[1:] if argv is None else argv)
|
||||||
os.chdir(REPO_ROOT)
|
os.chdir(REPO_ROOT)
|
||||||
|
if args.release_only:
|
||||||
|
version = args.release_only.strip()
|
||||||
|
if not re.fullmatch(r"\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?", version):
|
||||||
|
raise SystemExit("Version must look like 1.1.2")
|
||||||
|
apk = REPO_ROOT / f"IWantToHeal-Thor-v{version}.apk"
|
||||||
|
if not apk.exists():
|
||||||
|
raise SystemExit(f"APK not found: {apk}")
|
||||||
|
create_gitea_release(version, apk)
|
||||||
|
return 0
|
||||||
|
|
||||||
version = prompt_version()
|
version = prompt_version()
|
||||||
apk = build_apk(version)
|
apk = build_apk(version)
|
||||||
commit_and_push(version)
|
commit_and_push(version)
|
||||||
|
|||||||
Reference in New Issue
Block a user