Last active
February 18, 2025 22:14
-
-
Save masonwolters/927c926d6adec5e9ca9762e4d38f48c5 to your computer and use it in GitHub Desktop.
React native iOS with pre-built binaries
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
# | |
# This script is run in CI on master commits. It builds a debug binary | |
# for the iOS simulator and uploads it to the mobile-binaries repo. | |
# | |
# Then, when developer run `yarn ios` to develop locally, instead of | |
# having to build all of the native code, we just download the pre-built | |
# binary and install it on the simulator. | |
# | |
REPO_URL="{YOUR_GIT_URL}" | |
REPO_FOLDER="dev-binaries" | |
BINARY_NAME="My App.app" # replace | |
BINARY_PATH="/var/tmp/Debug-iphonesimulator/$BINARY_NAME" | |
BUILD_NUM=$(git rev-list --count HEAD) | |
GIT_COMMIT_HASH=$(git rev-parse HEAD) | |
# exit when any command fails | |
set -e | |
# build debug binary for simulator | |
bundle exec fastlane build_simulator_debug | |
rm -rf $REPO_FOLDER | |
mkdir $REPO_FOLDER | |
# zip binary | |
mv "$BINARY_PATH" . | |
zip -r $REPO_FOLDER/ios.zip "$BINARY_NAME" | |
rm -rf "$BINARY_NAME" | |
cd $REPO_FOLDER | |
git init | |
git remote add origin $REPO_URL | |
git add . | |
git commit -m "Build #$BUILD_NUM, for master commit $GIT_COMMIT_HASH" | |
# wipe out all git history because the binaries are big | |
git push origin master -f | |
echo "Successfully built iOS simulator binary and uploaded to GitHub!" |
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
/* | |
This should be invoked from package.json like: | |
"scripts": { | |
"ios": "babel-node ios.js", | |
} | |
*/ | |
import chalk from 'chalk'; | |
import fs from 'fs'; | |
const { program } = require('commander'); | |
const { execSync, exec: execAsync, spawn } = require('child_process'); | |
const { getIsPackagerRunning } = require('./utils/packager'); | |
const BINARIES_REPO_URL = '{YOUR_GIT_URL}'; | |
const BINARIES_FOLDER = 'dev-binaries'; | |
const SIMULATOR_PATH = '/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app'; | |
const BUNDLE_ID = '{YOUR_APP_BUNDLE_IDENTIFIER'; | |
const BINARY_NAME = `My\\ App.app`; // replace | |
const PACKAGER_TIMEOUT_SECONDS = 1; // Give packager time to start before launching app | |
const exec = (command) => { | |
const result = execSync(command); | |
if (result) { | |
return result.toString(); | |
} | |
return result; | |
}; | |
/** | |
* Download and unzip pre-built binaries from github repo. | |
*/ | |
const pullBinariesRepo = () => { | |
console.log('Downloading pre-built binaries from GitHub...'); | |
const folderExists = fs.existsSync(BINARIES_FOLDER); | |
if (folderExists) { | |
exec(`rm -rf ${BINARIES_FOLDER}`); | |
} | |
exec(`git clone ${BINARIES_REPO_URL} ${BINARIES_FOLDER}`); | |
exec(`cd ${BINARIES_FOLDER} && unzip -o ios.zip && cd ..`); | |
console.log(chalk.green('Done downloading and unzipping pre-built binaries')); | |
}; | |
/** | |
* Find the UUID of the simulator we want by using xcrun simctl list and | |
* searching through the output. | |
*/ | |
const findSimulatorId = (name, ipad) => { | |
const output = exec('xcrun simctl list'); | |
const [, devices] = output.split('== Devices =='); | |
if (name) { | |
const match = devices.match(new RegExp(`${name}.+\\((.+?)\\) \\(.+?\\) $`, 'm')); | |
const id = match && match[1]; | |
if (!id) { | |
throw new Error(`No simulator found with name ${name}`); | |
} | |
return id; | |
} else if (ipad) { | |
// Find last iPad | |
const matches = [...devices.matchAll(/iPad.+Air.+ \((.+?)\) \(.+?\) $/gm)]; | |
return matches[matches.length - 1][1]; | |
} else { | |
// Find last iPhone | |
const matches = [...devices.matchAll(/iPhone.+Pro \((.+?)\) \(.+?\) $/gm)]; | |
return matches[matches.length - 1][1]; | |
} | |
}; | |
const bootSimulator = (simulatorId) => { | |
console.log('Booting simulator...'); | |
try { | |
exec(`xcrun simctl boot ${simulatorId}`); | |
} catch (e) { | |
// this will throw an error if the simulator is already booted, so we just ignore it | |
} | |
exec(`open ${SIMULATOR_PATH}`); | |
console.log(chalk.green('Done booting iOS simulator')); | |
}; | |
const installApp = (simulatorId) => { | |
console.log('Installing app on simulator...'); | |
exec(`xcrun simctl install ${simulatorId} ${BINARIES_FOLDER}/${BINARY_NAME}`); | |
console.log(chalk.green('Done installing app on simulator')); | |
}; | |
const launchApp = (simulatorId, afterSeconds) => { | |
execAsync(`(sleep ${afterSeconds}; xcrun simctl launch ${simulatorId} ${BUNDLE_ID}) &`); | |
}; | |
/** | |
* Start metro bundler for JS bundle. | |
*/ | |
const startPackager = () => { | |
console.log(`Packager is not running yet, starting it...`); | |
spawn('yarn', ['start'], { stdio: 'inherit' }); | |
}; | |
const main = () => { | |
const options = program.opts(); | |
pullBinariesRepo(); | |
const simulatorId = findSimulatorId(options.simulator, options.ipad); | |
bootSimulator(simulatorId); | |
installApp(simulatorId); | |
const isPackagerRunning = getIsPackagerRunning(); | |
launchApp(simulatorId, isPackagerRunning ? 0 : PACKAGER_TIMEOUT_SECONDS); | |
if (!isPackagerRunning) { | |
startPackager(); | |
} | |
}; | |
program | |
.option('--ipad', 'Run on iPad simulator (defaults to iPhone)') | |
.option('-s, --simulator <name>', 'Simulator name to run'); | |
program.parse(); | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment