diff --git a/scripts/uat.sh b/scripts/uat.sh index 965b2814..bab7f465 100755 --- a/scripts/uat.sh +++ b/scripts/uat.sh @@ -34,7 +34,7 @@ cd "$ROOT_DIR" RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BOLD='\033[1m'; NC='\033[0m' info() { echo -e "${GREEN}[INFO]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } -error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; } +error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; } DO_IOS=1; DO_ANDROID=1 case "${1:-}" in @@ -46,12 +46,12 @@ esac command -v node >/dev/null || error "node not installed" command -v gh >/dev/null || error "gh CLI not installed" command -v bundle >/dev/null || error "bundler not installed (bundle install)" -[ -f fastlane/Fastfile ] || error "fastlane/Fastfile not found" +[[ -f fastlane/Fastfile ]] || error "fastlane/Fastfile not found" # Ignore fastlane/README.md — fastlane regenerates it on every run, so it is dirty by the time a # second build starts (and after any prior run). It is not source we build from. -[ -z "$(git status --porcelain | grep -vE 'fastlane/README\.md$' || true)" ] || error "Working tree is dirty. Commit or stash first." -[ "$DO_ANDROID" = 0 ] || { [ -f android/gradlew ] || error "android/gradlew not found"; [ -n "${ANDROID_HOME:-}" ] || error "ANDROID_HOME not set"; } -[ "$DO_IOS" = 0 ] || command -v xcodebuild >/dev/null || error "xcodebuild not installed" +[[ -z "$(git status --porcelain | grep -vE 'fastlane/README\.md$' || true)" ]] || error "Working tree is dirty. Commit or stash first." +[[ "$DO_ANDROID" = 0 ]] || { [[ -f android/gradlew ]] || error "android/gradlew not found"; [[ -n "${ANDROID_HOME:-}" ]] || error "ANDROID_HOME not set"; } +[[ "$DO_IOS" = 0 ]] || command -v xcodebuild >/dev/null || error "xcodebuild not installed" # ── compute the beta version ─────────────────────────────────────── # A beta targets the NEXT version, not the live one (the live train is closed - see header). @@ -70,7 +70,7 @@ BUILD_NUMBER=$(date +%s) info "Beta build: ${BOLD}${BETA_VERSION}${NC} (build ${BUILD_NUMBER}) - pre-release of ${TARGET_VERSION} (current live: ${CURRENT_VERSION})" # ── apply the build-number / beta-versionName bump (working tree; committed only on success) ── -if [ "$DO_ANDROID" = 1 ]; then +if [[ "$DO_ANDROID" = 1 ]]; then sed -i '' "s/versionCode .*/versionCode $BUILD_NUMBER/" android/app/build.gradle # versionName = the PRODUCTION version (no -beta suffix), matching iOS's MARKETING_VERSION # below. versionName is frozen into the AAB and is user-visible, so a "-beta" suffix here @@ -80,7 +80,7 @@ if [ "$DO_ANDROID" = 1 ]; then # "-beta.N" label lives in the git tag, the GitHub prerelease, and the store release notes. sed -i '' "s/versionName .*/versionName \"$TARGET_VERSION\"/" android/app/build.gradle fi -if [ "$DO_IOS" = 1 ]; then +if [[ "$DO_IOS" = 1 ]]; then # iOS marketing version = NEXT plain numeric version (App Store rejects "-beta", and this # opens a fresh TestFlight train since the live version's train is closed); build no. bumps. sed -i '' "s/MARKETING_VERSION = .*/MARKETING_VERSION = $TARGET_VERSION;/" ios/OffgridMobile.xcodeproj/project.pbxproj @@ -109,7 +109,7 @@ gen_notes_with_claude() { 2>/dev/null } -if NOTES=$(gen_notes_with_claude) && [ -n "$NOTES" ]; then +if NOTES=$(gen_notes_with_claude) && [[ -n "$NOTES" ]]; then printf '%s\n' "$NOTES" > "$NOTES_FILE" info "Release notes generated by claude -p" else @@ -132,29 +132,29 @@ info "Notes:"; sed 's/^/ /' "$NOTES_FILE"; echo "" # every retry). So we build ALL artifacts up front - the steps that actually fail (compile, # signing, export) happen before a single upload - and only publish once every artifact # exists. The fastlane beta lanes read UAT_CHANGELOG_PATH at upload time. -if [ "$DO_ANDROID" = 1 ]; then +if [[ "$DO_ANDROID" = 1 ]]; then info "Android → building signed AAB…"; bundle exec fastlane android build # Also build the sideloadable APK for the GitHub prerelease (the AAB isn't installable; # testers grabbing the build off GitHub need the APK - same as scripts/release.sh). info "Android → building installable APK for GitHub…"; (cd android && ./gradlew assembleRelease) AAB_SRC="android/app/build/outputs/bundle/release/app-release.aab" APK_SRC="android/app/build/outputs/apk/release/app-release.apk" - [ -f "$AAB_SRC" ] || error "AAB not found at $AAB_SRC" - [ -f "$APK_SRC" ] || error "APK not found at $APK_SRC" + [[ -f "$AAB_SRC" ]] || error "AAB not found at $AAB_SRC" + [[ -f "$APK_SRC" ]] || error "APK not found at $APK_SRC" fi -if [ "$DO_IOS" = 1 ]; then +if [[ "$DO_IOS" = 1 ]]; then info "iOS → building signed IPA…"; bundle exec fastlane ios build - [ -f build/OffgridMobile.ipa ] || error "IPA not found at build/OffgridMobile.ipa" + [[ -f build/OffgridMobile.ipa ]] || error "IPA not found at build/OffgridMobile.ipa" fi # ── PUBLISH - reached only if every build above succeeded ─────────── -if [ "$DO_ANDROID" = 1 ]; then info "Android → Play internal (AAB)…"; bundle exec fastlane android upload_beta; fi -if [ "$DO_IOS" = 1 ]; then info "iOS → TestFlight…"; bundle exec fastlane ios upload_beta; fi +if [[ "$DO_ANDROID" = 1 ]]; then info "Android → Play internal (AAB)…"; bundle exec fastlane android upload_beta; fi +if [[ "$DO_IOS" = 1 ]]; then info "iOS → TestFlight…"; bundle exec fastlane ios upload_beta; fi # ── success → commit the bump, cut the PRERELEASE tag, GH prerelease ── trap - EXIT # keep the bump now that the build shipped -FILES=(); [ "$DO_ANDROID" = 1 ] && FILES+=(android/app/build.gradle) -[ "$DO_IOS" = 1 ] && FILES+=(ios/OffgridMobile.xcodeproj/project.pbxproj) +FILES=(); [[ "$DO_ANDROID" = 1 ]] && FILES+=(android/app/build.gradle) +[[ "$DO_IOS" = 1 ]] && FILES+=(ios/OffgridMobile.xcodeproj/project.pbxproj) git add "${FILES[@]}" git commit -m "chore(beta): ${BETA_VERSION} (build ${BUILD_NUMBER}) [skip ci]" # Annotate the tag with the tested store build id (Android versionCode / iOS @@ -173,10 +173,10 @@ git push && git push origin "$TAG" GH_ARGS=() APK_DST="$ROOT_DIR/OffgridMobile-${BETA_VERSION}.apk" AAB_DST="$ROOT_DIR/OffgridMobile-${BETA_VERSION}.aab" -if [ "$DO_ANDROID" = 1 ]; then - [ -f android/app/build/outputs/apk/release/app-release.apk ] && \ +if [[ "$DO_ANDROID" = 1 ]]; then + [[ -f android/app/build/outputs/apk/release/app-release.apk ]] && \ { cp android/app/build/outputs/apk/release/app-release.apk "$APK_DST"; GH_ARGS+=("$APK_DST"); } - [ -f android/app/build/outputs/bundle/release/app-release.aab ] && \ + [[ -f android/app/build/outputs/bundle/release/app-release.aab ]] && \ { cp android/app/build/outputs/bundle/release/app-release.aab "$AAB_DST"; GH_ARGS+=("$AAB_DST"); } fi # Carry the tested build id into the prerelease body too (redundant with the annotated tag), @@ -195,7 +195,7 @@ SLACK_WEBHOOK_URL="${SLACK_WEBHOOK_URL:-$(sed -n 's/^SLACK_WEBHOOK_URL=//p' "$RO rm -f "$NOTES_FILE" "${ANDROID_CHANGELOG:-}" "$APK_DST" "$AAB_DST" echo "" info "${BOLD}Beta ${BETA_VERSION} shipped.${NC}" -[ "$DO_IOS" = 1 ] && info " iOS: TestFlight (App Store Connect → TestFlight)" -[ "$DO_ANDROID" = 1 ] && info " Android: Play Console → Internal testing" +[[ "$DO_IOS" = 1 ]] && info " iOS: TestFlight (App Store Connect → TestFlight)" +[[ "$DO_ANDROID" = 1 ]] && info " Android: Play Console → Internal testing" info " GitHub: $(gh release view "$TAG" --json url -q .url 2>/dev/null)" info "Approve the beta, then run scripts/release.sh to cut the real version to production."