mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
301 lines
8.9 KiB
Bash
Executable File
301 lines
8.9 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# --- Setup ---
|
||
set -e
|
||
cd "$(dirname "$0")"
|
||
|
||
# --- Arguments ---
|
||
IS_PRERELEASE=false
|
||
|
||
show_help() {
|
||
echo "Usage: $0 [arguments]"
|
||
echo ""
|
||
echo "Arguments:"
|
||
echo " --pre Enable pre-release"
|
||
echo " --help Show this message"
|
||
}
|
||
|
||
for arg in "$@"; do
|
||
case $arg in
|
||
--pre)
|
||
IS_PRERELEASE=true
|
||
shift
|
||
;;
|
||
--help)
|
||
show_help
|
||
exit 0
|
||
;;
|
||
*)
|
||
echo -e "\033[0;31mError: Unknown argument '$arg'\033[0m"
|
||
show_help
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
# --- Colors ---
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
CYAN='\033[0;36m'
|
||
MAGENTA='\033[0;35m'
|
||
NC='\033[0m'
|
||
BOLD='\033[1m'
|
||
|
||
# --- Logging ---
|
||
info() { echo -e "${BLUE}ℹ${NC} $1"; }
|
||
success() { echo -e "${GREEN}✓${NC} $1"; }
|
||
warning() { echo -e "${YELLOW}⚠${NC} $1"; }
|
||
error() { echo -e "${RED}✗${NC} $1"; }
|
||
step() { echo -e "\n${CYAN}${BOLD}→${NC} ${BOLD}$1${NC}"; }
|
||
substep() { echo -e " ${GREEN}•${NC} $1"; }
|
||
|
||
echo -e "${MAGENTA}${BOLD}🚀 FromChat KMP Release Pipeline${NC}"
|
||
|
||
# --- 1. Version Input & Validation ---
|
||
step "Configuration"
|
||
echo -en " ${GREEN}•${NC} Release version: "
|
||
read -r USER_INPUT
|
||
|
||
# Regex for x, x.y, or x.y.z
|
||
VERSION_REGEX="^[0-9]+(\.[0-9]+)*$"
|
||
# shellcheck disable=SC2001
|
||
CLEAN_VERSION=$(echo "$USER_INPUT" | LC_ALL=C sed 's/^v//')
|
||
|
||
if [[ ! $CLEAN_VERSION =~ $VERSION_REGEX ]]; then
|
||
error "Error: Version must be in format x, x.y, or x.y.z (e.g. 1.2.3)"
|
||
exit 1
|
||
fi
|
||
|
||
TAG="v$CLEAN_VERSION"
|
||
VERSION_STR="$CLEAN_VERSION"
|
||
# shellcheck disable=SC2001
|
||
BUILD_NUMBER=$(echo "$VERSION_STR" | LC_ALL=C sed 's/[^0-9]//g')
|
||
[[ -z "$BUILD_NUMBER" ]] && BUILD_NUMBER=1
|
||
|
||
# Check if tag exists
|
||
RECREATE_TAG=false
|
||
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
||
echo -n " " && warning "Tag $TAG already exists."
|
||
echo -en " ${GREEN}•${NC} Delete it and move to the new commit? (y/N): "
|
||
read -r CONFIRM
|
||
if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then
|
||
RECREATE_TAG=true
|
||
else
|
||
exit 0
|
||
fi
|
||
fi
|
||
|
||
CURRENT_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD)
|
||
STASH_MARKER="release_stash_$(date +%s)"
|
||
HAS_STASHED=false
|
||
|
||
restore_git_state() {
|
||
if [ "$HAS_STASHED" = true ]; then
|
||
STASH_ID=$(git stash list | grep "$STASH_MARKER" | head -n 1 | cut -d':' -f1)
|
||
if [[ -n "$STASH_ID" ]]; then
|
||
git stash pop "$STASH_ID" > /dev/null 2>&1 || true
|
||
fi
|
||
HAS_STASHED=false
|
||
fi
|
||
}
|
||
|
||
trap restore_git_state EXIT INT TERM
|
||
|
||
# --- 2. Git Cleanup & Safety ---
|
||
if [[ -n $(git status --short) ]]; then
|
||
git stash push --include-untracked -m "$STASH_MARKER" > /dev/null 2>&1
|
||
HAS_STASHED=true
|
||
fi
|
||
|
||
git fetch origin "$CURRENT_BRANCH" > /dev/null 2>&1
|
||
LOCAL_HASH=$(git rev-parse HEAD)
|
||
REMOTE_HASH=$(git rev-parse "origin/$CURRENT_BRANCH")
|
||
|
||
if [ "$LOCAL_HASH" != "$REMOTE_HASH" ]; then
|
||
if ! git merge-base --is-ancestor "$REMOTE_HASH" "$LOCAL_HASH"; then
|
||
error "Remote branch has commits you don't have. Please pull first."
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
# --- Constants ---
|
||
BUILD_DIR="$(pwd)/build"
|
||
DESC_FILE="$BUILD_DIR/.release_desc.md"
|
||
RELEASES_DIR="$(pwd)/releases"
|
||
mkdir -p "$RELEASES_DIR"
|
||
export GRADLE_OPTS="-Dorg.gradle.jvmargs=-Xmx8g -Dkotlin.daemon.jvm.options=-Xmx8g"
|
||
|
||
# --- 3. Build Functions ---
|
||
|
||
build_android() {
|
||
step "Building Android Release"
|
||
if ./gradlew :app:android:assembleRelease; then
|
||
APK_SRC=$(find . -name "*release.apk" | head -n 1)
|
||
if [[ -f "$APK_SRC" ]]; then
|
||
DISPLAY_NAME="FromChat-$TAG-android.apk"
|
||
cp "$APK_SRC" "$RELEASES_DIR/$DISPLAY_NAME"
|
||
ANDROID_ASSET="$RELEASES_DIR/$DISPLAY_NAME"
|
||
success "Android APK ready: ${CYAN}$DISPLAY_NAME${NC}"
|
||
else
|
||
error "APK not found"; exit 1
|
||
fi
|
||
else
|
||
error "Android build failed"; exit 1
|
||
fi
|
||
}
|
||
|
||
build_ios() {
|
||
step "Building iOS Release"
|
||
if [[ "$OSTYPE" != "darwin"* ]]; then
|
||
warning "iOS build requires macOS. Skipping."
|
||
return
|
||
fi
|
||
|
||
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
|
||
# shellcheck disable=SC2155
|
||
[[ ! -d "$DEVELOPER_DIR" ]] && export DEVELOPER_DIR=$(xcode-select -p)
|
||
|
||
IOS_PROJECT_DIR="app/ios"
|
||
[[ ! -d "$IOS_PROJECT_DIR" ]] && IOS_PROJECT_DIR="iosApp"
|
||
PLIST_PATH=$(find "$IOS_PROJECT_DIR" -name "Info.plist" | head -n 1)
|
||
|
||
if [[ -f "$PLIST_PATH" ]]; then
|
||
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "$PLIST_PATH" || true
|
||
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION_STR" "$PLIST_PATH" || true
|
||
fi
|
||
|
||
substep "Xcode Archiving..."
|
||
rm -rf "$BUILD_DIR/ios" && mkdir -p "$BUILD_DIR/ios"
|
||
if ! xcodebuild -project "$IOS_PROJECT_DIR/iosApp.xcodeproj" \
|
||
-scheme iOS \
|
||
-configuration Release \
|
||
-sdk iphoneos \
|
||
-destination 'generic/platform=iOS' \
|
||
-derivedDataPath "$BUILD_DIR/ios" \
|
||
CODE_SIGNING_ALLOWED=NO \
|
||
CODE_SIGNING_REQUIRED=NO \
|
||
clean build > "$BUILD_DIR/ios/build.log" 2>&1
|
||
then
|
||
error "iOS build failed. Check: $BUILD_DIR/ios/build.log"; exit 1
|
||
fi
|
||
|
||
substep "Packaging to ${CYAN}.ipa${NC}..."
|
||
APP_BUNDLE_PATH=$(find "$BUILD_DIR/ios/Build/Products/Release-iphoneos" -name "*.app" -type d | head -n 1)
|
||
|
||
if [[ -n "$APP_BUNDLE_PATH" ]]; then
|
||
IPA_NAME="FromChat-$TAG-ios-unsigned.ipa"
|
||
IPA_PATH="$RELEASES_DIR/$IPA_NAME"
|
||
|
||
# Create Payload structure inside build dir
|
||
PAYLOAD_STAGE="$BUILD_DIR/ios/ipa_stage"
|
||
rm -rf "$PAYLOAD_STAGE" && mkdir -p "$PAYLOAD_STAGE/Payload"
|
||
|
||
# Use cp -R to dereference symlinks and copy actual files into the stage
|
||
# This is safe because it's only the final bundle
|
||
cp -R "$APP_BUNDLE_PATH" "$PAYLOAD_STAGE/Payload/"
|
||
|
||
# Zip from the stage directory
|
||
(cd "$PAYLOAD_STAGE" && zip -r "$IPA_PATH" Payload > /dev/null 2>&1)
|
||
|
||
# Cleanup stage
|
||
rm -rf "$PAYLOAD_STAGE"
|
||
|
||
if [[ -f "$IPA_PATH" ]]; then
|
||
IOS_ASSET="$IPA_PATH"
|
||
success "iOS IPA ready: ${CYAN}$IPA_NAME${NC}"
|
||
else
|
||
error "IPA generation failed (file not found in releases)"
|
||
exit 1
|
||
fi
|
||
else
|
||
error "Could not find .app bundle"; exit 1
|
||
fi
|
||
|
||
git checkout -- "$PLIST_PATH" > /dev/null 2>&1 || true
|
||
}
|
||
|
||
build_android
|
||
build_ios
|
||
|
||
# --- 4. Git Finalizing ---
|
||
|
||
IOS_PROJECT_DIR="app/ios"
|
||
[[ ! -d "$IOS_PROJECT_DIR" ]] && IOS_PROJECT_DIR="iosApp"
|
||
PLIST_PATH=$(find "$IOS_PROJECT_DIR" -name "Info.plist" | head -n 1)
|
||
|
||
if [[ -f "$PLIST_PATH" ]]; then
|
||
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "$PLIST_PATH" || true
|
||
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION_STR" "$PLIST_PATH" || true
|
||
git add "$PLIST_PATH" > /dev/null 2>&1
|
||
|
||
if ! git diff --cached --quiet; then
|
||
if git commit --amend --no-edit > /dev/null 2>&1; then
|
||
substep "Info.plist updated (amend)."
|
||
else
|
||
error "Failed to amend commit."
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
if [[ "$RECREATE_TAG" == true ]]; then
|
||
git tag -d "$TAG" > /dev/null 2>&1 || true
|
||
git push origin :refs/tags/"$TAG" > /dev/null 2>&1 || true
|
||
fi
|
||
|
||
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
|
||
git tag -a "$TAG" -m "Release $TAG" > /dev/null 2>&1
|
||
fi
|
||
|
||
git push origin "$CURRENT_BRANCH" --force-with-lease --tags > /dev/null 2>&1
|
||
|
||
# --- 5. GitHub Release ---
|
||
|
||
prepare_description() {
|
||
echo -e "<!-- Release Description -->" > "$DESC_FILE"
|
||
nano "$DESC_FILE"
|
||
CLEAN_DESC=$(LC_ALL=C perl -0777 -pe 's/<!--.*?-->//gs' "$DESC_FILE" | LC_ALL=C sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||
rm -f "$DESC_FILE"
|
||
if [[ -n "$CLEAN_DESC" ]]; then
|
||
echo "$CLEAN_DESC" > "$DESC_FILE"
|
||
NOTES_ARG=("-F" "$DESC_FILE")
|
||
else
|
||
NOTES_ARG=("--generate-notes")
|
||
fi
|
||
}
|
||
|
||
publish_github() {
|
||
step "GitHub Release"
|
||
if ! command -v gh &> /dev/null; then return; fi
|
||
|
||
ASSETS=()
|
||
[[ -f "$ANDROID_ASSET" ]] && ASSETS+=("$ANDROID_ASSET")
|
||
[[ -f "$IOS_ASSET" ]] && ASSETS+=("$IOS_ASSET")
|
||
[ ${#ASSETS[@]} -eq 0 ] && return
|
||
|
||
prepare_description
|
||
|
||
if gh release view "$TAG" >/dev/null 2>&1; then
|
||
substep "Editing existing release..."
|
||
EDIT_ARGS=("--draft=false")
|
||
[[ "$IS_PRERELEASE" == true ]] && EDIT_ARGS+=("--prerelease") || EDIT_ARGS+=("--prerelease=false")
|
||
[[ -f "$DESC_FILE" ]] && EDIT_ARGS+=("-F" "$DESC_FILE")
|
||
gh release edit "$TAG" "${EDIT_ARGS[@]}" 1> /dev/null
|
||
substep "Uploading files..."
|
||
gh release upload "$TAG" "${ASSETS[@]}" --clobber 1> /dev/null
|
||
else
|
||
substep "Creating the release..."
|
||
PR_FLAG=""
|
||
[[ "$IS_PRERELEASE" == true ]] && PR_FLAG="--prerelease"
|
||
gh release create "$TAG" "${NOTES_ARG[@]}" $PR_FLAG --draft=false "${ASSETS[@]}" 1> /dev/null
|
||
fi
|
||
rm -f "$DESC_FILE"
|
||
success "Success!"
|
||
}
|
||
|
||
publish_github
|
||
|
||
restore_git_state
|
||
trap - EXIT INT TERM
|
||
echo -e "\n${GREEN}${BOLD}✨ Release $TAG completed successfully!${NC}" |