Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions scripts/uat.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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).
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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),
Expand All @@ -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."
Loading