├── demornandroid.gif ├── package.json ├── README.md ├── LICENSE ├── .gitignore └── index.js /demornandroid.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danhnn/rn-run-android/HEAD/demornandroid.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rn-run-android", 3 | "version": "0.0.1-beta.5", 4 | "description": "Alternative for current painful `react-native run-android`, just rn-run-android for everything with advance options", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/harry-nguyen-88/rn-run-android" 12 | }, 13 | "bin": { 14 | "rn-run-android": "index.js" 15 | }, 16 | "keywords": [ 17 | "react-native", 18 | "run-android", 19 | "rn-run-android" 20 | ], 21 | "author": "HarryNguyen ", 22 | "homepage": "https://github.com/harry-nguyen-88/rn-run-android", 23 | "license": "MIT", 24 | "dependencies": { 25 | "chalk": "^2.4.2", 26 | "commander": "^2.20.0", 27 | "inquirer": "^6.3.1", 28 | "shelljs": "^0.8.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rn-run-android 2 | 3 | No more pain when running react-native app in android emulator. Very similar to `react-native run-ios`: just `rn-run-android` for everything with more advance options. 4 | 5 | ## Demo 6 | 7 | ![Demo](demornandroid.gif) 8 | 9 | ## Installation 10 | 11 | ```sh 12 | $ npm i -g rn-run-android 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```sh 18 | # Run in the directory of your react-native project 19 | $ rn-run-android 20 | 21 | # Available options: 22 | -d, --default Auto select first created emulator 23 | -w, --writable Enable -writable-system mode for emulator 24 | -n, --nosnapshot Enable -no-snapshot-load mode for emulator 25 | -h, --help output usage information 26 | 27 | # Example: 28 | # This command below will select first created emulator and run it with no-snapshot config: 29 | rn-run-android -d -n 30 | ``` 31 | 32 | Inspired by [react-native-run-android](https://github.com/nickpisacane/react-native-run-android) ( with fixed for emulator synchornization issue and more advance options ) 33 | 34 | MIT License 35 | Copyright (c) 2019 Harry Nguyễn 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Harry Nguyễn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | yarn.lock -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { spawn, execSync } = require("child_process") 3 | const path = require("path") 4 | const shell = require("shelljs") 5 | const inquirer = require("inquirer") 6 | const chalk = require("chalk") 7 | const program = require("commander") 8 | 9 | program 10 | .option("-d, --default", "Auto select first created emulator") 11 | .option("-w, --writable", "Enable -writable-system") 12 | .option("-n, --nosnapshot", "Enable -no-snapshot-load") 13 | 14 | program.parse(process.argv) 15 | 16 | const APP_NAME = "rn-run-android" 17 | const RN_URI = 18 | "https://facebook.github.io/react-native/docs/getting-started.html" 19 | const REQUIRED_BINS = ["react-native", "emulator", "adb"] 20 | 21 | const whichEmulator = shell.which("emulator") 22 | 23 | const showDepFailureMessgae = () => 24 | console.error( 25 | `${chalk.red(`Please refer to `) + 26 | chalk.blue(`${RN_URI}`)} for installation/setup instructions` 27 | ) 28 | 29 | const requireBin = bin => { 30 | if (!shell.which(bin)) { 31 | console.error(chalk.red(`${APP_NAME} requires ${bin}.`)) 32 | showDepFailureMessgae() 33 | process.exit(1) 34 | } 35 | } 36 | 37 | const getDevices = () => 38 | shell 39 | .exec(`emulator -list-avds`, { silent: true }) 40 | .stdout.split(/\n/) 41 | .filter(s => s !== "") 42 | .map(s => s.trim()) 43 | 44 | const adbDeviceIsRunning = () => 45 | /emulator-\d+\s+device/.test(execSync(`adb devices`).toString()) 46 | 47 | const adbDeviceIsBootCompleted = () => 48 | // We don't want to output error here when checking BOOT_COMPLETED 49 | /BOOT_COMPLETED/.test( 50 | execSync(`adb shell am broadcast -a android.intent.action.BOOT_COMPLETED`, { 51 | stdio: "pipe" 52 | }).toString() 53 | ) 54 | 55 | const pollADBBootcompleted = (maxTime = 60000) => 56 | new Promise((resolve, reject) => { 57 | console.log(chalk.blue("Checking boot completed...")) 58 | const timer = setInterval(() => { 59 | try { 60 | if (adbDeviceIsBootCompleted()) { 61 | clearInterval(timer) 62 | resolve() 63 | } 64 | } catch {} 65 | }) 66 | }) 67 | 68 | const pollADBDevice = (maxTime = 60000) => 69 | new Promise((resolve, reject) => { 70 | console.log(chalk.blue(`Checking device running...`)) 71 | const start = Date.now() 72 | const timer = setInterval(() => { 73 | if (adbDeviceIsRunning()) { 74 | clearInterval(timer) 75 | resolve() 76 | } 77 | if (Date.now() - start >= maxTime) { 78 | clearInterval(timer) 79 | reject(new Error("No device running")) 80 | } 81 | }) 82 | }) 83 | 84 | const emulateDevice = device => { 85 | console.log(chalk.blue(`Starting emulator for device ${device} ...`)) 86 | let options = [] 87 | if (program.nosnapshot) options.push("-no-snapshot-load") 88 | if (program.writable) options.push("-writable-system") 89 | 90 | spawn("emulator", [`@${device}`, ...options], { 91 | cwd: path.dirname(whichEmulator.stdout), 92 | stdio: ["pipe"], 93 | detached: true 94 | }) 95 | 96 | return pollADBDevice() 97 | .then(() => { 98 | console.log(chalk.green(`Device is running!`)) 99 | }) 100 | .then(() => { 101 | execSync("adb root") 102 | }) 103 | .then(pollADBBootcompleted) 104 | .then(() => { 105 | console.log(chalk.green(`Device is boot completed!`)) 106 | return true 107 | }) 108 | } 109 | 110 | const startReactNative = () => { 111 | console.log(chalk.blue(`Starting react-native ...`)) 112 | const child = spawn("react-native", ["run-android"], { 113 | stdio: ["pipe", process.stdout, process.stderr] 114 | }) 115 | child.on("close", function(code) { 116 | process.exit(0) 117 | }) 118 | } 119 | 120 | const main = () => { 121 | REQUIRED_BINS.forEach(requireBin) 122 | if (adbDeviceIsRunning()) { 123 | console.log( 124 | chalk.green(`* Device is already running! By pass all options.`) 125 | ) 126 | startReactNative() 127 | return 128 | } 129 | 130 | const devices = getDevices() 131 | if (!devices || devices.length == 0) { 132 | console.error(chalk.red(`Please create a virtual device`)) 133 | showDepFailureMessgae() 134 | process.exit(1) 135 | } 136 | 137 | if (program.writable) { 138 | console.log(chalk.green(`* Wiretable mode is seleted`)) 139 | } 140 | 141 | if (program.nosnapshot) { 142 | console.log(chalk.green(`* NoSnapshot mode is seleted`)) 143 | } 144 | 145 | if (program.default) { 146 | console.log( 147 | chalk.green( 148 | `* Default option is seleted. First created emulator will be choosen.` 149 | ) 150 | ) 151 | return emulateDevice(devices[0]).then(() => startReactNative()) 152 | } 153 | 154 | inquirer 155 | .prompt([ 156 | { 157 | type: "list", 158 | name: "device", 159 | message: "Which device would you like to use?", 160 | choices: devices 161 | } 162 | ]) 163 | .then(answer => { 164 | const { device } = answer 165 | return emulateDevice(device).then(() => startReactNative()) 166 | }) 167 | .catch(error => { 168 | console.error(chalk.red`Something failed:\n`, error) 169 | }) 170 | } 171 | 172 | main() 173 | --------------------------------------------------------------------------------