#!/usr/bin/env bash # # Bump the project version and push images to the registry. # # Usage: # ./release.sh # # The current version is stored in VERSION (next to this script). # You will be asked whether to bump major, minor, or patch. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" VERSION_FILE="${SCRIPT_DIR}/VERSION" # --------------------------------------------------------------------------- # Read current version # --------------------------------------------------------------------------- if [[ ! -f "${VERSION_FILE}" ]]; then echo "0.0.0" > "${VERSION_FILE}" fi CURRENT="$(cat "${VERSION_FILE}")" # Strip leading 'v' for arithmetic VERSION="${CURRENT#v}" IFS='.' read -r MAJOR MINOR PATCH <<< "${VERSION}" echo "============================================" echo " AniWorld — Release" echo " Current version: v${MAJOR}.${MINOR}.${PATCH}" echo "============================================" echo "" echo "Which image(s) would you like to release?" echo " 1) app (Dockerfile.app)" echo " 2) vpn (Containerfile)" echo " 3) all (both images)" echo "" read -rp "Enter choice [1/2/3]: " TARGET_CHOICE case "${TARGET_CHOICE}" in 1) TARGET="app" ;; 2) TARGET="vpn" ;; 3) TARGET="all" ;; *) echo "Invalid choice. Aborting." >&2 exit 1 ;; esac echo "" echo "How would you like to bump the version?" echo " 1) patch (v${MAJOR}.${MINOR}.${PATCH} → v${MAJOR}.${MINOR}.$((PATCH + 1)))" echo " 2) minor (v${MAJOR}.${MINOR}.${PATCH} → v${MAJOR}.$((MINOR + 1)).0)" echo " 3) major (v${MAJOR}.${MINOR}.${PATCH} → v$((MAJOR + 1)).0.0)" echo "" read -rp "Enter choice [1/2/3]: " CHOICE case "${CHOICE}" in 1) NEW_TAG="v${MAJOR}.${MINOR}.$((PATCH + 1))" ;; 2) NEW_TAG="v${MAJOR}.$((MINOR + 1)).0" ;; 3) NEW_TAG="v$((MAJOR + 1)).0.0" ;; *) echo "Invalid choice. Aborting." >&2 exit 1 ;; esac echo "" echo "New version: ${NEW_TAG}" echo "Target: ${TARGET}" read -rp "Confirm? [y/N]: " CONFIRM if [[ ! "${CONFIRM}" =~ ^[yY]$ ]]; then echo "Aborted." exit 0 fi # --------------------------------------------------------------------------- # Write new version # --------------------------------------------------------------------------- echo "${NEW_TAG}" > "${VERSION_FILE}" echo "Version file updated → ${VERSION_FILE}" # Keep root package.json in sync. FRONT_VERSION="${NEW_TAG#v}" FRONT_PKG="${SCRIPT_DIR}/../package.json" if [[ -f "${FRONT_PKG}" ]]; then # Use a python one-liner for portable, safe JSON editing. The previous # `sed -i` used single-quoted bash strings, which prevented # ${FRONT_VERSION} from being interpolated and silently rewrote the file # to the literal string "${FRONT_VERSION}". python3 - "$FRONT_PKG" "$FRONT_VERSION" <<'PY' import json, sys path, new_version = sys.argv[1], sys.argv[2] with open(path, encoding="utf-8") as fh: data = json.load(fh) data["version"] = new_version with open(path, "w", encoding="utf-8") as fh: json.dump(data, fh, indent=2) fh.write("\n") PY echo "package.json version updated → ${FRONT_VERSION}" else echo "Warning: package.json not found, skipping package.json version sync" >&2 fi # Keep root pyproject.toml in sync. BACKEND_PYPROJECT="${SCRIPT_DIR}/../pyproject.toml" if [[ -f "${BACKEND_PYPROJECT}" ]]; then # Use python instead of sed: the previous `sed -i` used double-quoted # patterns whose `&` and `\` characters would have to be escaped, and # more importantly it could silently do nothing if the [project] section # was missing. python reads/writes the file as a string, preserving # the existing format, and reports whether anything changed. if FRONT_VERSION="$FRONT_VERSION" BACKEND_PYPROJECT="$BACKEND_PYPROJECT" python3 <<'PY' import os, re, sys path = os.environ["BACKEND_PYPROJECT"] new_version = os.environ["FRONT_VERSION"] with open(path, encoding="utf-8") as fh: text = fh.read() # If there is a [project] table, update only the `version = "..."` line # inside it; otherwise update the first top-level `version = "..."` line. project_match = re.search(r"^\[project\]\s*$", text, re.MULTILINE) if project_match: start = project_match.end() end = re.search(r"^\[", text[start:], re.MULTILINE) section_end = start + end.start() if end else len(text) section = text[start:section_end] new_section, n = re.subn( r'^version = ".*"$', f'version = "{new_version}"', section, count=1, flags=re.MULTILINE, ) if n == 0: print( f"Warning: no `version = ...` line found under [project] in {path}", file=sys.stderr, ) sys.exit(2) text = text[:start] + new_section + text[section_end:] else: new_text, n = re.subn( r'^version = ".*"$', f'version = "{new_version}"', text, count=1, flags=re.MULTILINE, ) if n == 0: print( f"Warning: no `version = ...` line found in {path}", file=sys.stderr, ) sys.exit(2) text = new_text with open(path, "w", encoding="utf-8") as fh: fh.write(text) PY then echo "pyproject.toml version updated → ${FRONT_VERSION}" fi else echo "Warning: pyproject.toml not found, skipping pyproject.toml version sync" >&2 fi # --------------------------------------------------------------------------- # Push containers # --------------------------------------------------------------------------- bash "${SCRIPT_DIR}/push.sh" "${TARGET}" "${NEW_TAG}" bash "${SCRIPT_DIR}/push.sh" "${TARGET}" # --------------------------------------------------------------------------- # Git tag (local only; push after container build) # --------------------------------------------------------------------------- cd "${SCRIPT_DIR}/.." git add Docker/VERSION package.json pyproject.toml git commit -m "chore: bump version" git tag -a "${NEW_TAG}" -m "Release ${NEW_TAG}" echo "Local git commit + tag ${NEW_TAG} created." # --------------------------------------------------------------------------- # Push git commits & tag # --------------------------------------------------------------------------- git push origin HEAD git push origin "${NEW_TAG}" echo "Git commit and tag ${NEW_TAG} pushed."