#!/usr/bin/env bash set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd "$script_dir/.." && pwd)" cd "$repo_root" require_command() { if ! command -v "$1" >/dev/null 2>&1; then echo "Fehlender Befehl: $1" exit 1 fi } PARSED_MAJOR="" PARSED_MINOR="" PARSED_PATCH="" PARSED_BUILD="" PARSED_SUFFIX="" friendly_remote_name() { local remote_name="$1" local remote_url="$2" if [[ "$remote_url" == *github.com* ]]; then printf 'GitHub (%s)' "$remote_name" return fi if [[ "$remote_url" == *git.d-razz.de* ]] || [[ "$remote_name" == "gitea" ]]; then printf 'Gitea (%s)' "$remote_name" return fi printf '%s' "$remote_name" } parse_version() { local version="$1" if [[ ! "$version" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-([0-9]+|alpha|beta|rc([.]?[0-9]+)?))?$ ]]; then echo "Aktuelle Version hat kein unterstuetztes Format: $version" exit 1 fi PARSED_MAJOR="${BASH_REMATCH[1]}" PARSED_MINOR="${BASH_REMATCH[2]}" PARSED_PATCH="${BASH_REMATCH[3]}" PARSED_SUFFIX="${BASH_REMATCH[5]:-}" if [[ "$PARSED_SUFFIX" =~ ^[0-9]+$ ]]; then PARSED_BUILD="$PARSED_SUFFIX" else PARSED_BUILD="" fi } bump_version() { local version="$1" local bump_type="$2" parse_version "$version" local major="$PARSED_MAJOR" local minor="$PARSED_MINOR" local patch="$PARSED_PATCH" case "$bump_type" in major) major=$((major + 1)) minor=0 patch=0 ;; minor) minor=$((minor + 1)) patch=0 ;; patch) patch=$((patch + 1)) ;; esac printf '%s.%s.%s' "$major" "$minor" "$patch" } increment_rc_version() { local version="$1" parse_version "$version" local rc_number=1 if [[ "$PARSED_SUFFIX" =~ ^rc([.]?([0-9]+))?$ ]]; then if [[ -n "${BASH_REMATCH[2]:-}" ]]; then rc_number=$((BASH_REMATCH[2] + 1)) fi elif [[ -n "$PARSED_SUFFIX" ]]; then echo "RC-Bump nicht moeglich fuer Suffix '$PARSED_SUFFIX'." echo "Unterstuetzt: keine Suffixe oder -rc." exit 1 fi printf '%s.%s.%s-rc%s' "$PARSED_MAJOR" "$PARSED_MINOR" "$PARSED_PATCH" "$rc_number" } can_increment_rc_version() { local version="$1" parse_version "$version" [[ -z "$PARSED_SUFFIX" || "$PARSED_SUFFIX" =~ ^rc([.]?[0-9]+)?$ ]] } remove_rc_suffix() { local version="$1" parse_version "$version" if [[ ! "$PARSED_SUFFIX" =~ ^rc([.]?[0-9]+)?$ ]]; then echo "RC-Suffix kann nur von Versionen mit -rc entfernt werden." echo "Aktuelle Version: $version" exit 1 fi printf '%s.%s.%s' "$PARSED_MAJOR" "$PARSED_MINOR" "$PARSED_PATCH" } select_release_variant() { local base_version="$1" local choice="" local rc_number="" echo "Release-Typ waehlen:" echo " 1) Final -> $base_version" echo " 2) Release Candidate -> ${base_version}-rc" echo " 3) Beta -> ${base_version}-beta" echo " 4) Alpha -> ${base_version}-alpha" read -r -p "Auswahl [1]: " choice choice="${choice:-1}" case "$choice" in 1) SELECTED_RELEASE_VERSION="$base_version" SELECTED_RELEASE_LABEL="Final" ;; 2) while true; do read -r -p "RC-Nummer eingeben (z.B. 1): " rc_number if [[ "$rc_number" =~ ^[0-9]+$ ]]; then break fi echo "Bitte eine gueltige Zahl eingeben." done SELECTED_RELEASE_VERSION="${base_version}-rc$rc_number" SELECTED_RELEASE_LABEL="Release Candidate" ;; 3) SELECTED_RELEASE_VERSION="${base_version}-beta" SELECTED_RELEASE_LABEL="Beta" ;; 4) SELECTED_RELEASE_VERSION="${base_version}-alpha" SELECTED_RELEASE_LABEL="Alpha" ;; *) echo "Ungueltige Release-Typ-Auswahl." exit 1 ;; esac } confirm_or_exit() { local prompt="$1" local response read -r -p "$prompt" response response="${response:-y}" if [[ ! "$response" =~ ^[YyJj]$ ]]; then echo "Abgebrochen." exit 0 fi } require_command git require_command node require_command npm find_deploy_script() { local candidate="" for candidate in \ "$repo_root/scripts/deploy-ripster.sh" \ "$repo_root/deploy-ripster.sh" do if [[ -f "$candidate" ]]; then printf '%s' "$candidate" return 0 fi done return 1 } if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then echo "Dieses Skript muss in einem Git-Repository ausgefuehrt werden." exit 1 fi current_branch="$(git branch --show-current)" github_release_mode="snapshot" github_source_branch="" github_source_ref="" mapfile -t remotes < <(git remote) default_remote_index=1 for i in "${!remotes[@]}"; do if [[ "${remotes[$i]}" == "gitea" ]]; then default_remote_index=$((i + 1)) break fi done echo "Verfuegbare Remotes:" for i in "${!remotes[@]}"; do remote_name="${remotes[$i]}" remote_url="$(git remote get-url "$remote_name")" remote_label="$(friendly_remote_name "$remote_name" "$remote_url")" echo " $((i + 1))) $remote_label -> $remote_url" done read -r -p "Remote waehlen [$default_remote_index]: " remote_choice remote_choice="${remote_choice:-$default_remote_index}" selected_remote="${remotes[$((remote_choice - 1))]}" selected_remote_url="$(git remote get-url "$selected_remote")" selected_is_github=false if [[ "$selected_remote_url" == *github.com* ]]; then selected_is_github=true fi echo "Lokaler Branch: $current_branch" if [[ "$selected_is_github" == true ]]; then echo echo "GitHub-Modus waehlen:" echo " 1) Snapshot-Release auf Ziel-Branch (bisheriger Ablauf)" echo " 2) Branch-Merge/Veroeffentlichung dev -> main (force, main uebernimmt dev komplett)" default_github_mode="1" if [[ "$current_branch" == "dev" ]]; then default_github_mode="2" fi read -r -p "Auswahl [$default_github_mode]: " github_mode_choice github_mode_choice="${github_mode_choice:-$default_github_mode}" case "$github_mode_choice" in 1) github_release_mode="snapshot" ;; 2) github_release_mode="branch-force-merge" ;; *) echo "Ungueltige GitHub-Modus-Auswahl." exit 1 ;; esac fi if [[ "$selected_is_github" == true && "$github_release_mode" == "branch-force-merge" ]]; then echo read -r -p "Quell-Branch fuer GitHub-Veroeffentlichung [dev]: " github_source_branch github_source_branch="${github_source_branch:-dev}" if ! git check-ref-format --branch "$github_source_branch" >/dev/null 2>&1; then echo "Ungueltiger Quell-Branch: $github_source_branch" exit 1 fi if git show-ref --verify --quiet "refs/heads/$github_source_branch"; then github_source_ref="refs/heads/$github_source_branch" elif git ls-remote --exit-code --heads "$selected_remote" "$github_source_branch" >/dev/null 2>&1; then github_source_ref="refs/remotes/$selected_remote/$github_source_branch" git fetch "$selected_remote" "$github_source_branch" else echo "Quell-Branch '$github_source_branch' wurde weder lokal noch auf $selected_remote gefunden." exit 1 fi read -r -p "Ziel-Branch auf $selected_remote [main]: " target_branch target_branch="${target_branch:-main}" else read -r -p "Ziel-Branch auf $selected_remote [$current_branch]: " target_branch target_branch="${target_branch:-$current_branch}" fi if ! git check-ref-format --branch "$target_branch" >/dev/null 2>&1; then echo "Ungueltiger Branch-Name: $target_branch" exit 1 fi selected_push_ref="refs/heads/$target_branch" root_version="$(node -p "require('./package.json').version")" frontend_version="$(node -p "require('./frontend/package.json').version")" backend_version="$(node -p "require('./backend/package.json').version")" if [[ "$root_version" != "$frontend_version" ]] || [[ "$root_version" != "$backend_version" ]]; then echo "Hinweis: Die Versionen sind aktuell nicht synchron." echo " Root: $root_version" echo " Frontend: $frontend_version" echo " Backend: $backend_version" echo "Das Skript synchronisiert sie gleich auf eine gemeinsame Zielversion." echo fi next_patch="$(bump_version "$root_version" patch)" next_minor="$(bump_version "$root_version" minor)" next_major="$(bump_version "$root_version" major)" next_rc="" if can_increment_rc_version "$root_version"; then next_rc="$(increment_rc_version "$root_version")" fi current_final="" if parse_version "$root_version" >/dev/null 2>&1 && [[ "$PARSED_SUFFIX" =~ ^rc([.]?[0-9]+)?$ ]]; then current_final="$(remove_rc_suffix "$root_version")" fi apply_version_change=false echo "Aktuelle Version: $root_version" if [[ "$selected_is_github" == true ]]; then target_version="$root_version" release_mode="GitHub-Sync ohne Versionsaenderung" echo "GitHub-Remote erkannt: Versions- und Build-Prozess wird uebersprungen." else read -r -p "Versionsnummer hochsetzen? [Y/n]: " should_bump_version should_bump_version="${should_bump_version:-y}" case "$should_bump_version" in [YyJj]) echo "Version waehlen:" echo " 1) Patch -> $next_patch" echo " 2) Minor -> $next_minor" echo " 3) Major -> $next_major" read -r -p "Auswahl [1]: " version_choice version_choice="${version_choice:-1}" case "$version_choice" in 1) target_version="$next_patch" release_mode="Patch" apply_version_change=true ;; 2) select_release_variant "$next_minor" target_version="$SELECTED_RELEASE_VERSION" release_mode="Minor ($SELECTED_RELEASE_LABEL)" apply_version_change=true ;; 3) select_release_variant "$next_major" target_version="$SELECTED_RELEASE_VERSION" release_mode="Major ($SELECTED_RELEASE_LABEL)" apply_version_change=true ;; *) echo "Ungueltige Versions-Auswahl." exit 1 ;; esac ;; [Nn]) echo "Option ohne Basisversionssprung waehlen:" echo " 1) Ohne Versionsaenderung -> $root_version" if [[ -n "$next_rc" ]]; then echo " 2) Nur RC-Nummer hochsetzen -> $next_rc" fi if [[ -n "$current_final" ]]; then echo " 3) RC entfernen -> $current_final" fi read -r -p "Auswahl [1]: " no_bump_choice no_bump_choice="${no_bump_choice:-1}" case "$no_bump_choice" in 1) target_version="$root_version" release_mode="Ohne Versionsaenderung" ;; 2) if [[ -z "$next_rc" ]]; then echo "RC-Bump ist fuer die aktuelle Version nicht verfuegbar." exit 1 fi target_version="$next_rc" release_mode="RC" apply_version_change=true ;; 3) if [[ -z "$current_final" ]]; then echo "RC entfernen ist nur moeglich, wenn die aktuelle Version ein -rc Suffix hat." exit 1 fi target_version="$current_final" release_mode="RC entfernt" apply_version_change=true ;; *) echo "Ungueltige Auswahl." exit 1 ;; esac ;; *) echo "Bitte nur Y oder N eingeben." exit 1 ;; esac fi echo echo "Zusammenfassung:" echo " Branch: $current_branch -> $selected_remote/$target_branch" echo " Ziel-Ref: $selected_push_ref" echo " Modus: $release_mode" echo " Version: $root_version -> $target_version" if [[ "$selected_is_github" == true ]]; then echo " GitHub: $github_release_mode" if [[ "$github_release_mode" == "branch-force-merge" ]]; then echo " Quelle: $github_source_branch -> $target_branch (force)" fi fi confirm_or_exit "Fortfahren? [Y/n]: " echo if [[ "$apply_version_change" == true ]]; then echo "Setze Version auf $target_version ..." npm version "$target_version" --no-git-tag-version npm version "$target_version" --no-git-tag-version --prefix frontend npm version "$target_version" --no-git-tag-version --prefix backend else echo "Version bleibt bei $target_version." fi git add -A echo echo "Gestagte Aenderungen:" git status --short if git diff --cached --quiet; then echo "Keine neuen Aenderungen zum Committen. Verwende aktuellen Commit fuer den Push." else echo commit_message="" while [[ -z "$commit_message" ]]; do read -r -p "Commit-Nachricht: " commit_message if [[ -z "$commit_message" ]]; then echo "Commit-Nachricht darf nicht leer sein." fi done git commit -m "$target_version $commit_message" fi echo echo "Push vorbereiten..." if [[ "$selected_is_github" == true ]]; then if [[ "$github_release_mode" == "branch-force-merge" ]]; then echo "GitHub erkannt: $github_source_branch wird mit Force nach $target_branch uebernommen." git push "$selected_remote" "$github_source_ref:$selected_push_ref" --force else echo "GitHub erkannt: Squash-Commit wird erzeugt." temp_branch="github-publish-$target_branch" git checkout -B "$temp_branch" first_commit="$(git rev-list --max-parents=0 HEAD)" git reset --soft "$first_commit" git commit -m "$target_version release snapshot" git push "$selected_remote" "$temp_branch:$selected_push_ref" --force git checkout "$current_branch" git branch -D "$temp_branch" fi else git push "$selected_remote" "HEAD:$selected_push_ref" fi deploy_script="" if deploy_script="$(find_deploy_script)"; then echo echo "Starte Deploy..." bash "$deploy_script" echo "Deploy abgeschlossen." fi echo echo "Release abgeschlossen: $target_version -> $selected_remote/$target_branch"