1/** 2 * Copyright (c) Meta Platforms, Inc. and affiliates. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 * 7 * @format 8 */ 9 10'use strict'; 11 12const {exec} = require('shelljs'); 13const os = require('os'); 14const {spawn} = require('node:child_process'); 15 16/* 17 * Android related utils - leverages android tooling 18 */ 19 20// this code is taken from the CLI repo, slightly readapted to our needs 21// here's the reference folder: 22// https://github.com/react-native-community/cli/blob/main/packages/cli-platform-android/src/commands/runAndroid 23 24const emulatorCommand = process.env.ANDROID_HOME 25 ? `${process.env.ANDROID_HOME}/emulator/emulator` 26 : 'emulator'; 27 28const getEmulators = () => { 29 const emulatorsOutput = exec(`${emulatorCommand} -list-avds`).stdout; 30 return emulatorsOutput.split(os.EOL).filter(name => name !== ''); 31}; 32 33const launchEmulator = emulatorName => { 34 // we need both options 'cause reasons: 35 // from docs: "When using the detached option to start a long-running process, the process will not stay running in the background after the parent exits unless it is provided with a stdio configuration that is not connected to the parent. If the parent's stdio is inherited, the child will remain attached to the controlling terminal." 36 // here: https://nodejs.org/api/child_process.html#optionsdetached 37 38 const cp = spawn(emulatorCommand, [`@${emulatorName}`], { 39 detached: true, 40 stdio: 'ignore', 41 }); 42 43 cp.unref(); 44}; 45 46function tryLaunchEmulator() { 47 const emulators = getEmulators(); 48 if (emulators.length > 0) { 49 try { 50 launchEmulator(emulators[0]); 51 52 return {success: true}; 53 } catch (error) { 54 return {success: false, error}; 55 } 56 } 57 return { 58 success: false, 59 error: 'No emulators found as an output of `emulator -list-avds`', 60 }; 61} 62 63function launchAndroidEmulator() { 64 const result = tryLaunchEmulator(); 65 if (result.success) { 66 console.info('Successfully launched emulator.'); 67 } else { 68 console.error(`Failed to launch emulator. Reason: ${result.error || ''}.`); 69 console.warn( 70 'Please launch an emulator manually or connect a device. Otherwise app may fail to launch.', 71 ); 72 } 73} 74 75/* 76 * iOS related utils - leverages xcodebuild 77 */ 78 79/* 80 * Metro related utils 81 */ 82 83// inspired by CLI again https://github.com/react-native-community/cli/blob/main/packages/cli-tools/src/isPackagerRunning.ts 84 85function isPackagerRunning( 86 packagerPort = process.env.RCT_METRO_PORT || '8081', 87) { 88 try { 89 const status = exec(`curl http://localhost:${packagerPort}/status`, { 90 silent: true, 91 }).stdout; 92 93 return status === 'packager-status:running' ? 'running' : 'unrecognized'; 94 } catch (_error) { 95 return 'not_running'; 96 } 97} 98 99// this is a very limited implementation of how this should work 100// literally, this is macos only 101// a more robust implementation can be found here: 102// https://github.com/react-native-community/cli/blob/7c003f2b1d9d80ec5c167614ba533a004272c685/packages/cli-platform-android/src/commands/runAndroid/index.ts#L195 103function launchPackagerInSeparateWindow() { 104 exec("open -a 'Terminal' ./scripts/packager.sh"); 105} 106 107module.exports = { 108 launchAndroidEmulator, 109 isPackagerRunning, 110 launchPackagerInSeparateWindow, 111}; 112