Skip to content

Instantly share code, notes, and snippets.

@flyingwebie
Created August 18, 2025 08:30
Show Gist options
  • Save flyingwebie/cede9992045ae7e66de639de9272108e to your computer and use it in GitHub Desktop.
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.
#!/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