Version 1.1
This commit is contained in:
153
release.py
Normal file
153
release.py
Normal file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Release helper: git commit/push + docker build/push.
|
||||
|
||||
Flow:
|
||||
1) Prompt for version
|
||||
2) git add -A
|
||||
3) git commit -m "Version <version>" (skips if nothing to commit)
|
||||
4) git push -u origin main (ensures origin points to target repo)
|
||||
5) docker build + push tags: <version> and latest
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Config:
|
||||
target_git_url: str = "https://git.alphen.cloud/bramval/PsalmbordOnlineCE.git"
|
||||
docker_image: str = "git.alphen.cloud/bramval/psalmbordonlinece"
|
||||
branch: str = "main"
|
||||
|
||||
|
||||
VERSION_RE = re.compile(r"^[0-9A-Za-z][0-9A-Za-z._-]{0,127}$")
|
||||
|
||||
|
||||
def run(cmd: list[str], *, check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||
"""Run command and stream output; return CompletedProcess."""
|
||||
print(f"\n$ {' '.join(cmd)}")
|
||||
return subprocess.run(cmd, text=True, check=check)
|
||||
|
||||
|
||||
def capture(cmd: list[str]) -> str:
|
||||
"""Run command and capture stdout."""
|
||||
return subprocess.check_output(cmd, text=True).strip()
|
||||
|
||||
|
||||
def ensure_tools() -> None:
|
||||
for tool in ("git", "docker"):
|
||||
if shutil.which(tool) is None:
|
||||
raise SystemExit(f"Error: '{tool}' is not available in PATH")
|
||||
|
||||
|
||||
def prompt_version() -> str:
|
||||
version = input("Enter version (e.g. 1.2.3): ").strip()
|
||||
if not version:
|
||||
raise SystemExit("Error: version cannot be empty")
|
||||
if not VERSION_RE.match(version):
|
||||
raise SystemExit(
|
||||
"Error: invalid version. Use only letters/numbers and . _ - (max 128 chars)"
|
||||
)
|
||||
return version
|
||||
|
||||
|
||||
def prompt_yes_no(question: str, default_no: bool = True) -> bool:
|
||||
prompt = "(y/N)" if default_no else "(Y/n)"
|
||||
ans = input(f"{question} {prompt}: ").strip().lower()
|
||||
if not ans:
|
||||
return not default_no
|
||||
return ans in ("y", "yes")
|
||||
|
||||
|
||||
def ensure_origin(config: Config) -> None:
|
||||
try:
|
||||
origin_url = capture(["git", "remote", "get-url", "origin"])
|
||||
except subprocess.CalledProcessError:
|
||||
origin_url = ""
|
||||
|
||||
if not origin_url:
|
||||
print(f"Remote 'origin' not found; adding {config.target_git_url}")
|
||||
run(["git", "remote", "add", "origin", config.target_git_url])
|
||||
return
|
||||
|
||||
if origin_url != config.target_git_url:
|
||||
print(
|
||||
f"Remote 'origin' is '{origin_url}' -> updating to '{config.target_git_url}'"
|
||||
)
|
||||
run(["git", "remote", "set-url", "origin", config.target_git_url])
|
||||
|
||||
|
||||
def main() -> int:
|
||||
config = Config()
|
||||
|
||||
ensure_tools()
|
||||
version = prompt_version()
|
||||
commit_message = f"Version {version}"
|
||||
|
||||
# Branch warning
|
||||
try:
|
||||
current_branch = capture(["git", "branch", "--show-current"])
|
||||
except subprocess.CalledProcessError:
|
||||
current_branch = ""
|
||||
if current_branch and current_branch != config.branch:
|
||||
print(
|
||||
f"Warning: you are on branch '{current_branch}' (expected '{config.branch}')."
|
||||
)
|
||||
if not prompt_yes_no(f"Continue and push '{config.branch}' anyway?", default_no=True):
|
||||
return 1
|
||||
|
||||
ensure_origin(config)
|
||||
|
||||
print("\n== Git: status ==")
|
||||
run(["git", "status", "-sb"], check=False)
|
||||
|
||||
if not prompt_yes_no("Proceed with commit+push and docker push?", default_no=True):
|
||||
print("Aborted.")
|
||||
return 1
|
||||
|
||||
print("\n== Git: add/commit/push ==")
|
||||
run(["git", "add", "-A"])
|
||||
|
||||
# commit may fail if nothing to commit
|
||||
try:
|
||||
run(["git", "commit", "-m", commit_message])
|
||||
print(f"Committed: {commit_message}")
|
||||
except subprocess.CalledProcessError:
|
||||
print("No changes to commit (or commit failed). Continuing...")
|
||||
|
||||
run(["git", "push", "-u", "origin", config.branch])
|
||||
|
||||
print("\n== Docker: build/tag/push ==")
|
||||
print(f"Docker image: {config.docker_image}")
|
||||
print(f"Tags: {version}, latest")
|
||||
|
||||
run(
|
||||
[
|
||||
"docker",
|
||||
"build",
|
||||
"-t",
|
||||
f"{config.docker_image}:{version}",
|
||||
"-t",
|
||||
f"{config.docker_image}:latest",
|
||||
".",
|
||||
]
|
||||
)
|
||||
run(["docker", "push", f"{config.docker_image}:{version}"])
|
||||
run(["docker", "push", f"{config.docker_image}:latest"])
|
||||
|
||||
print("\nDone.")
|
||||
print("If docker push failed due to auth, run: docker login git.alphen.cloud")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Make Ctrl+C exit nicely
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except KeyboardInterrupt:
|
||||
print("\nAborted (Ctrl+C).")
|
||||
raise SystemExit(130)
|
||||
Reference in New Issue
Block a user