Created
August 18, 2025 08:30
-
-
Save flyingwebie/cede9992045ae7e66de639de9272108e to your computer and use it in GitHub Desktop.
This script automates the setup process for Expo Development Builds based on the official Expo documentation and best practices.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# ============================================================================== | |
# LaunchKit Native Development Build Setup Script | |
# ============================================================================== | |
# | |
# This script automates the setup process for Expo Development Builds | |
# based on the official Expo documentation and best practices. | |
# | |
# Usage: ./scripts/setup-dev-build.sh | |
# | |
# Prerequisites: | |
# - Node.js 18+ installed | |
# - Bun package manager (preferred) or npm | |
# - Expo account (will guide through creation) | |
# | |
# ============================================================================== | |
set -e # Exit on any error | |
# Script modes | |
INTERACTIVE_MODE=true | |
DEBUG_MODE=false | |
# Parse command line arguments | |
while [[ $# -gt 0 ]]; do | |
case $1 in | |
--non-interactive|-y) | |
INTERACTIVE_MODE=false | |
shift | |
;; | |
--debug) | |
DEBUG_MODE=true | |
set -x # Enable debug output | |
shift | |
;; | |
*) | |
shift | |
;; | |
esac | |
done | |
# Color codes for better output | |
readonly RED='\033[0;31m' | |
readonly GREEN='\033[0;32m' | |
readonly YELLOW='\033[1;33m' | |
readonly BLUE='\033[0;34m' | |
readonly PURPLE='\033[0;35m' | |
readonly CYAN='\033[0;36m' | |
readonly NC='\033[0m' # No Color | |
# Script configuration | |
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
readonly PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" | |
readonly LOG_FILE="$PROJECT_ROOT/dev-build-setup.log" | |
# Required versions | |
readonly MIN_NODE_VERSION="18.0.0" | |
readonly MIN_BUN_VERSION="1.0.0" | |
# ============================================================================== | |
# Utility Functions | |
# ============================================================================== | |
log() { | |
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" | tee -a "$LOG_FILE" | |
} | |
warn() { | |
echo -e "${YELLOW}[WARNING] $1${NC}" | tee -a "$LOG_FILE" | |
} | |
error() { | |
echo -e "${RED}[ERROR] $1${NC}" | tee -a "$LOG_FILE" | |
} | |
info() { | |
echo -e "${BLUE}[INFO] $1${NC}" | tee -a "$LOG_FILE" | |
} | |
success() { | |
echo -e "${GREEN}[SUCCESS] $1${NC}" | tee -a "$LOG_FILE" | |
} | |
prompt() { | |
echo -e "${CYAN}$1${NC}" | |
} | |
debug() { | |
if [[ "$DEBUG_MODE" == "true" ]]; then | |
echo -e "${PURPLE}[DEBUG] $1${NC}" | tee -a "$LOG_FILE" | |
fi | |
} | |
# Version comparison function | |
version_compare() { | |
if [[ $1 == $2 ]]; then | |
return 0 | |
fi | |
# Split versions into arrays | |
IFS='.' read -ra ver1 <<< "$1" | |
IFS='.' read -ra ver2 <<< "$2" | |
# Get maximum array length | |
local max_len=${#ver1[@]} | |
if [[ ${#ver2[@]} -gt $max_len ]]; then | |
max_len=${#ver2[@]} | |
fi | |
# Compare each version component | |
for ((i=0; i<max_len; i++)); do | |
local v1=${ver1[i]:-0} | |
local v2=${ver2[i]:-0} | |
# Remove leading zeros and handle empty values | |
v1=$(echo "$v1" | sed 's/^0*//' | grep -E '^[0-9]+$' || echo "0") | |
v2=$(echo "$v2" | sed 's/^0*//' | grep -E '^[0-9]+$' || echo "0") | |
# Default to 0 if empty after removing leading zeros | |
v1=${v1:-0} | |
v2=${v2:-0} | |
if [[ $v1 -gt $v2 ]]; then | |
return 1 # v1 > v2 | |
elif [[ $v1 -lt $v2 ]]; then | |
return 2 # v1 < v2 | |
fi | |
done | |
return 0 # v1 == v2 | |
} | |
# Check if command exists | |
command_exists() { | |
command -v "$1" >/dev/null 2>&1 | |
} | |
# Wait for user input | |
wait_for_user() { | |
if [[ "$INTERACTIVE_MODE" == "true" ]]; then | |
read -p "Press Enter to continue or Ctrl+C to exit..." | |
else | |
debug "Running in non-interactive mode, continuing..." | |
fi | |
} | |
# Handle command errors | |
handle_command_error() { | |
local cmd="$1" | |
local exit_code="$2" | |
error "Command failed: $cmd" | |
error "Exit code: $exit_code" | |
error "Check $LOG_FILE for full details" | |
if [[ "$INTERACTIVE_MODE" == "true" ]]; then | |
prompt "Press Enter to exit..." | |
read -r | |
fi | |
exit $exit_code | |
} | |
# ============================================================================== | |
# Pre-flight Checks | |
# ============================================================================== | |
check_node_version() { | |
info "Checking Node.js version..." | |
if ! command_exists node; then | |
error "Node.js is not installed. Please install Node.js $MIN_NODE_VERSION or later." | |
error "Visit: https://nodejs.org/" | |
exit 1 | |
fi | |
local node_version=$(node --version | sed 's/v//') | |
version_compare "$node_version" "$MIN_NODE_VERSION" || local result=$? | |
result=${result:-0} | |
debug "Version comparison result: $result (0=equal, 1=newer, 2=older)" | |
if [ $result -eq 2 ]; then | |
error "Node.js version $node_version is too old. Required: $MIN_NODE_VERSION or later." | |
exit 1 | |
fi | |
success "Node.js version $node_version is compatible ✓" | |
} | |
check_package_manager() { | |
info "Checking package manager..." | |
if command_exists bun; then | |
local bun_version=$(bun --version) | |
success "Bun $bun_version detected - using Bun as package manager ✓" | |
PACKAGE_MANAGER="bun" | |
PKG_INSTALL="bun install" | |
PKG_ADD="bun add" | |
PKG_RUN="bun run" | |
elif command_exists npm; then | |
local npm_version=$(npm --version) | |
success "npm $npm_version detected - using npm as package manager ✓" | |
PACKAGE_MANAGER="npm" | |
PKG_INSTALL="npm install" | |
PKG_ADD="npm install" | |
PKG_RUN="npm run" | |
else | |
error "No package manager found. Please install Bun or npm." | |
exit 1 | |
fi | |
} | |
check_expo_cli() { | |
info "Checking Expo CLI..." | |
# Check if expo is available directly or through bunx | |
if command_exists expo; then | |
local expo_version=$(expo --version) | |
success "Expo CLI $expo_version is available ✓" | |
EXPO_CMD="expo" | |
elif [ "$PACKAGE_MANAGER" = "bun" ] && bunx expo --version >/dev/null 2>&1; then | |
local expo_version=$(bunx expo --version) | |
success "Expo CLI $expo_version is available via bunx ✓" | |
EXPO_CMD="bunx expo" | |
else | |
warn "Expo CLI not found. Installing..." | |
if [ "$PACKAGE_MANAGER" = "bun" ]; then | |
bun install -g @expo/cli | |
# Check if it's available after installation | |
if bunx expo --version >/dev/null 2>&1; then | |
local expo_version=$(bunx expo --version) | |
success "Expo CLI $expo_version installed and available via bunx ✓" | |
EXPO_CMD="bunx expo" | |
else | |
error "Failed to install Expo CLI. Please install manually: npm install -g @expo/cli" | |
exit 1 | |
fi | |
else | |
npm install -g @expo/cli | |
if command_exists expo; then | |
local expo_version=$(expo --version) | |
success "Expo CLI $expo_version is available ✓" | |
EXPO_CMD="expo" | |
else | |
error "Failed to install Expo CLI. Please install manually: npm install -g @expo/cli" | |
exit 1 | |
fi | |
fi | |
fi | |
} | |
check_eas_cli() { | |
info "Checking EAS CLI..." | |
# Check if eas is available directly or through bunx | |
if command_exists eas; then | |
local eas_version=$(eas --version) | |
success "EAS CLI $eas_version is available ✓" | |
EAS_CMD="eas" | |
elif [ "$PACKAGE_MANAGER" = "bun" ] && bunx eas --version >/dev/null 2>&1; then | |
local eas_version=$(bunx eas --version) | |
success "EAS CLI $eas_version is available via bunx ✓" | |
EAS_CMD="bunx eas" | |
else | |
warn "EAS CLI not found. Installing..." | |
if [ "$PACKAGE_MANAGER" = "bun" ]; then | |
bun install -g eas-cli | |
# Check if it's available after installation | |
if bunx eas --version >/dev/null 2>&1; then | |
local eas_version=$(bunx eas --version) | |
success "EAS CLI $eas_version installed and available via bunx ✓" | |
EAS_CMD="bunx eas" | |
else | |
error "Failed to install EAS CLI. Please install manually: npm install -g eas-cli" | |
exit 1 | |
fi | |
else | |
npm install -g eas-cli | |
if command_exists eas; then | |
local eas_version=$(eas --version) | |
success "EAS CLI $eas_version is available ✓" | |
EAS_CMD="eas" | |
else | |
error "Failed to install EAS CLI. Please install manually: npm install -g eas-cli" | |
exit 1 | |
fi | |
fi | |
fi | |
} | |
check_project_structure() { | |
info "Validating project structure..." | |
if [ ! -f "$PROJECT_ROOT/package.json" ]; then | |
error "package.json not found. Are you in the correct directory?" | |
exit 1 | |
fi | |
if [ ! -f "$PROJECT_ROOT/app.json" ]; then | |
error "app.json not found. This doesn't appear to be an Expo project." | |
exit 1 | |
fi | |
success "Project structure is valid ✓" | |
} | |
# ============================================================================== | |
# Setup Functions | |
# ============================================================================== | |
install_expo_dev_client() { | |
info "Installing expo-dev-client..." | |
cd "$PROJECT_ROOT" | |
# Check if expo-dev-client is already installed | |
if grep -q "expo-dev-client" package.json; then | |
success "expo-dev-client is already installed ✓" | |
return 0 | |
fi | |
if [ "$PACKAGE_MANAGER" = "bun" ]; then | |
$EXPO_CMD install expo-dev-client | |
else | |
$EXPO_CMD install expo-dev-client | |
fi | |
success "expo-dev-client installed successfully ✓" | |
} | |
setup_eas_account() { | |
info "Setting up EAS account..." | |
if [[ "$INTERACTIVE_MODE" == "true" ]]; then | |
prompt "Do you have an Expo account? (y/n): " | |
read -r has_account | |
if [[ $has_account =~ ^[Nn]$ ]]; then | |
info "Please create a free Expo account:" | |
info "1. Visit: https://expo.dev" | |
info "2. Click 'Sign up' and create your account" | |
info "3. Verify your email address" | |
echo | |
prompt "After creating your account, press Enter to continue..." | |
wait_for_user | |
fi | |
info "Logging into EAS..." | |
$EAS_CMD login | |
else | |
info "Non-interactive mode: Checking existing EAS login..." | |
# In non-interactive mode, assume user is already logged in or skip if not | |
if ! $EAS_CMD whoami >/dev/null 2>&1; then | |
warn "Not logged into EAS. Please run '$EAS_CMD login' first." | |
info "Skipping EAS account setup in non-interactive mode." | |
return 0 | |
fi | |
fi | |
# Verify login | |
if $EAS_CMD whoami >/dev/null 2>&1; then | |
local username=$($EAS_CMD whoami) | |
success "Successfully logged in as: $username ✓" | |
else | |
if [[ "$INTERACTIVE_MODE" == "true" ]]; then | |
error "Failed to log in to EAS. Please try again." | |
exit 1 | |
else | |
warn "Not logged into EAS. Skipping account setup." | |
return 0 | |
fi | |
fi | |
} | |
initialize_eas_project() { | |
info "Initializing EAS project..." | |
cd "$PROJECT_ROOT" | |
# Check if already initialized | |
if [ -f "eas.json" ]; then | |
success "EAS project already initialized ✓" | |
return 0 | |
fi | |
$EAS_CMD init | |
success "EAS project initialized ✓" | |
} | |
select_platforms() { | |
if [[ "$INTERACTIVE_MODE" == "true" ]]; then | |
echo | |
info "Platform Selection" | |
echo "==================" | |
echo "1) iOS only" | |
echo "2) Android only" | |
echo "3) Both iOS and Android" | |
echo | |
prompt "Select platforms to build for (1-3): " | |
read -r platform_choice | |
case $platform_choice in | |
1) | |
PLATFORMS=("ios") | |
info "Selected: iOS only" | |
;; | |
2) | |
PLATFORMS=("android") | |
info "Selected: Android only" | |
;; | |
3) | |
PLATFORMS=("ios" "android") | |
info "Selected: Both iOS and Android" | |
;; | |
*) | |
warn "Invalid selection. Defaulting to both platforms." | |
PLATFORMS=("ios" "android") | |
;; | |
esac | |
else | |
# In non-interactive mode, default to Android only for faster builds | |
PLATFORMS=("android") | |
info "Non-interactive mode: Defaulting to Android only" | |
fi | |
} | |
setup_ios_device() { | |
if [[ ! " ${PLATFORMS[@]} " =~ " ios " ]]; then | |
return 0 | |
fi | |
info "Setting up iOS device for development..." | |
if [[ "$INTERACTIVE_MODE" == "true" ]]; then | |
prompt "Do you want to register an iOS device for development builds? (y/n): " | |
read -r register_device | |
if [[ $register_device =~ ^[Yy]$ ]]; then | |
info "Registering iOS device..." | |
info "You'll need to follow the prompts to register your device." | |
echo | |
$EAS_CMD device:create | |
info "Device registration completed." | |
info "Next, you'll need to set up provisioning profiles:" | |
info "Follow: https://docs.expo.dev/tutorial/eas/ios-development-build-for-devices/#provisioning-profile" | |
echo | |
prompt "After setting up provisioning profiles, press Enter to continue..." | |
wait_for_user | |
else | |
info "Skipping iOS device registration." | |
info "You can register devices later with: $EAS_CMD device:create" | |
fi | |
else | |
info "Non-interactive mode: Skipping iOS device registration." | |
info "You can register devices later with: $EAS_CMD device:create" | |
fi | |
} | |
build_development_builds() { | |
info "Building development builds..." | |
for platform in "${PLATFORMS[@]}"; do | |
info "Building $platform development build..." | |
echo | |
info "Starting $platform build. This may take 10-20 minutes..." | |
info "You can monitor progress at: https://expo.dev" | |
echo | |
if $EAS_CMD build --platform "$platform" --profile development; then | |
success "$platform development build completed ✓" | |
if [ "$platform" = "ios" ]; then | |
info "iOS Development Build Notes:" | |
info "- Install the build on your device by scanning the QR code" | |
info "- Enable Developer Mode: https://docs.expo.dev/guides/ios-developer-mode/" | |
info "- For simulator builds: https://docs.expo.dev/build-reference/simulators/" | |
elif [ "$platform" = "android" ]; then | |
info "Android Development Build Notes:" | |
info "- Download and install the APK file on your device" | |
info "- Enable 'Install from unknown sources' if prompted" | |
info "- Guide: https://docs.expo.dev/tutorial/eas/android-development-build/" | |
fi | |
else | |
error "Failed to build $platform development build" | |
error "Check the logs above for details" | |
fi | |
echo | |
done | |
} | |
start_development_server() { | |
info "Starting Expo development server..." | |
cd "$PROJECT_ROOT" | |
info "Development server will start momentarily..." | |
info "Scan the QR code with your development build to load your app" | |
echo | |
if [ "$PACKAGE_MANAGER" = "bun" ]; then | |
$EXPO_CMD start | |
else | |
$EXPO_CMD start | |
fi | |
} | |
# ============================================================================== | |
# Main Script Flow | |
# ============================================================================== | |
show_banner() { | |
echo | |
echo -e "${PURPLE}================================================================${NC}" | |
echo -e "${PURPLE} LaunchKit - Expo Development Build Setup${NC}" | |
echo -e "${PURPLE}================================================================${NC}" | |
echo | |
echo -e "${CYAN}This script will help you set up Expo Development Builds for your" | |
echo -e "LaunchKit React Native project. Development builds are FREE and" | |
echo -e "provide a much better development experience.${NC}" | |
echo | |
echo -e "${YELLOW}What this script will do:${NC}" | |
echo -e "• Install and configure expo-dev-client" | |
echo -e "• Set up EAS CLI and authentication" | |
echo -e "• Register iOS devices (if needed)" | |
echo -e "• Build development builds for your selected platforms" | |
echo -e "• Start the development server" | |
echo | |
echo -e "${YELLOW}Estimated time: 20-30 minutes (including build time)${NC}" | |
echo | |
if [[ "$INTERACTIVE_MODE" == "true" ]]; then | |
prompt "Ready to begin? Press Enter to continue or Ctrl+C to exit..." | |
wait_for_user | |
else | |
info "Running in non-interactive mode..." | |
fi | |
} | |
show_completion_message() { | |
echo | |
echo -e "${GREEN}================================================================${NC}" | |
echo -e "${GREEN} Setup Complete! 🎉${NC}" | |
echo -e "${GREEN}================================================================${NC}" | |
echo | |
echo -e "${CYAN}Your Expo Development Build setup is complete!${NC}" | |
echo | |
echo -e "${YELLOW}Next Steps:${NC}" | |
echo -e "1. Install the development build on your device" | |
echo -e "2. Scan the QR code to load your app" | |
echo -e "3. Start developing with instant updates!" | |
echo | |
echo -e "${YELLOW}Useful Commands:${NC}" | |
echo -e "• Start dev server: ${BLUE}$PKG_RUN start${NC}" | |
echo -e "• Build iOS dev: ${BLUE}$PKG_RUN dev:build:ios${NC}" | |
echo -e "• Build Android dev: ${BLUE}$PKG_RUN dev:build:android${NC}" | |
echo -e "• Register iOS device: ${BLUE}$EAS_CMD device:create${NC}" | |
echo | |
echo -e "${YELLOW}Documentation:${NC}" | |
echo -e "• Development Builds: ${BLUE}docs/development-builds.md${NC}" | |
echo -e "• Troubleshooting: ${BLUE}docs/troubleshooting-dev-builds.md${NC}" | |
echo | |
echo -e "${GREEN}Happy coding! 🚀${NC}" | |
echo | |
} | |
main() { | |
# Initialize log file | |
echo "=== LaunchKit Dev Build Setup - $(date) ===" > "$LOG_FILE" | |
show_banner | |
info "Starting pre-flight checks..." | |
check_node_version | |
check_package_manager | |
check_project_structure | |
check_expo_cli | |
check_eas_cli | |
success "All pre-flight checks passed! ✓" | |
echo | |
install_expo_dev_client | |
setup_eas_account | |
initialize_eas_project | |
select_platforms | |
setup_ios_device | |
build_development_builds | |
show_completion_message | |
if [[ "$INTERACTIVE_MODE" == "true" ]]; then | |
prompt "Would you like to start the development server now? (y/n): " | |
read -r start_server | |
if [[ $start_server =~ ^[Yy]$ ]]; then | |
start_development_server | |
else | |
info "You can start the development server later with: $PKG_RUN start" | |
fi | |
else | |
info "Non-interactive mode: Skipping automatic development server start" | |
info "You can start the development server with: $PKG_RUN start" | |
fi | |
} | |
# Error handling | |
trap 'error "Script interrupted. Check $LOG_FILE for details."; exit 1' INT TERM | |
# Run main function | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment