UNPKG

@expo/xdl

Version:
1,274 lines (1,007 loc) 35.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAllAvailableDevicesAsync = getAllAvailableDevicesAsync; exports.getAttachedDevicesAsync = getAttachedDevicesAsync; exports.isPlatformSupported = isPlatformSupported; exports.getAdbOutputAsync = getAdbOutputAsync; exports.downloadApkAsync = downloadApkAsync; exports.installExpoAsync = installExpoAsync; exports.isDeviceBootedAsync = isDeviceBootedAsync; exports.uninstallExpoAsync = uninstallExpoAsync; exports.upgradeExpoAsync = upgradeExpoAsync; exports.openProjectAsync = openProjectAsync; exports.openWebProjectAsync = openWebProjectAsync; exports.startAdbReverseAsync = startAdbReverseAsync; exports.stopAdbReverseAsync = stopAdbReverseAsync; exports.checkSplashScreenImages = checkSplashScreenImages; exports.maybeStopAdbDaemonAsync = maybeStopAdbDaemonAsync; function _config() { const data = require("@expo/config"); _config = function () { return data; }; return data; } function _configPlugins() { const data = require("@expo/config-plugins"); _configPlugins = function () { return data; }; return data; } function _spawnAsync() { const data = _interopRequireDefault(require("@expo/spawn-async")); _spawnAsync = function () { return data; }; return data; } function _chalk() { const data = _interopRequireDefault(require("chalk")); _chalk = function () { return data; }; return data; } function _child_process() { const data = _interopRequireDefault(require("child_process")); _child_process = function () { return data; }; return data; } function _fsExtra() { const data = _interopRequireDefault(require("fs-extra")); _fsExtra = function () { return data; }; return data; } function _trim() { const data = _interopRequireDefault(require("lodash/trim")); _trim = function () { return data; }; return data; } function _os() { const data = _interopRequireDefault(require("os")); _os = function () { return data; }; return data; } function _path() { const data = _interopRequireDefault(require("path")); _path = function () { return data; }; return data; } function _progress() { const data = _interopRequireDefault(require("progress")); _progress = function () { return data; }; return data; } function _prompts() { const data = _interopRequireDefault(require("prompts")); _prompts = function () { return data; }; return data; } function _semver() { const data = _interopRequireDefault(require("semver")); _semver = function () { return data; }; return data; } function _Analytics() { const data = _interopRequireDefault(require("./Analytics")); _Analytics = function () { return data; }; return data; } function _Api() { const data = _interopRequireDefault(require("./Api")); _Api = function () { return data; }; return data; } function Binaries() { const data = _interopRequireWildcard(require("./Binaries")); Binaries = function () { return data; }; return data; } function _Logger() { const data = _interopRequireDefault(require("./Logger")); _Logger = function () { return data; }; return data; } function _NotificationCode() { const data = _interopRequireDefault(require("./NotificationCode")); _NotificationCode = function () { return data; }; return data; } function ProjectSettings() { const data = _interopRequireWildcard(require("./ProjectSettings")); ProjectSettings = function () { return data; }; return data; } function Prompts() { const data = _interopRequireWildcard(require("./Prompts")); Prompts = function () { return data; }; return data; } function UrlUtils() { const data = _interopRequireWildcard(require("./UrlUtils")); UrlUtils = function () { return data; }; return data; } function _UserSettings() { const data = _interopRequireDefault(require("./UserSettings")); _UserSettings = function () { return data; }; return data; } function Versions() { const data = _interopRequireWildcard(require("./Versions")); Versions = function () { return data; }; return data; } function _Webpack() { const data = require("./Webpack"); _Webpack = function () { return data; }; return data; } function _TerminalLink() { const data = require("./logs/TerminalLink"); _TerminalLink = function () { return data; }; return data; } function _ImageUtils() { const data = require("./tools/ImageUtils"); _ImageUtils = function () { return data; }; return data; } function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } let _lastUrl = null; let _isAdbOwner = null; const BEGINNING_OF_ADB_ERROR_MESSAGE = 'error: '; const CANT_START_ACTIVITY_ERROR = 'Activity not started, unable to resolve Intent'; const INSTALL_WARNING_TIMEOUT = 60 * 1000; const EMULATOR_MAX_WAIT_TIMEOUT = 60 * 1000 * 3; function whichEmulator() { if (process.env.ANDROID_HOME) { return `${process.env.ANDROID_HOME}/emulator/emulator`; } return 'emulator'; } function whichADB() { if (process.env.ANDROID_HOME) { return `${process.env.ANDROID_HOME}/platform-tools/adb`; } return 'adb'; } /** * Returns a list of emulator names. */ async function getEmulatorsAsync() { try { const { stdout } = await (0, _spawnAsync().default)(whichEmulator(), ['-list-avds']); return stdout.split(_os().default.EOL).filter(Boolean).map(name => ({ name, type: 'emulator', // unsure from this isBooted: false, isAuthorized: true })); } catch (_unused) { return []; } } /** * Return the Emulator name for an emulator ID, this can be used to determine if an emulator is booted. * * @param emulatorId a value like `emulator-5554` from `abd devices` */ async function getAbdNameForEmulatorIdAsync(emulatorId) { var _trim$split$shift; return (_trim$split$shift = (0, _trim().default)(await getAdbOutputAsync(['-s', emulatorId, 'emu', 'avd', 'name'])).split(/\r?\n/).shift()) !== null && _trim$split$shift !== void 0 ? _trim$split$shift : null; } async function getAllAvailableDevicesAsync() { const bootedDevices = await getAttachedDevicesAsync(); const data = await getEmulatorsAsync(); const connectedNames = bootedDevices.map(({ name }) => name); const offlineEmulators = data.filter(({ name }) => !connectedNames.includes(name)).map(({ name, type }) => { return { name, type, isBooted: false, // TODO: Are emulators always authorized? isAuthorized: true }; }); const allDevices = bootedDevices.concat(offlineEmulators); if (!allDevices.length) { const genymotionMessage = `https://developer.android.com/studio/run/device.html#developer-device-options. If you are using Genymotion go to Settings -> ADB, select "Use custom Android SDK tools", and point it at your Android SDK directory.`; throw new Error(`No Android connected device found, and no emulators could be started automatically.\nPlease connect a device or create an emulator (https://docs.expo.io/workflow/android-studio-emulator).\nThen follow the instructions here to enable USB debugging:\n${genymotionMessage}`); } return allDevices; } /** * Returns true when a device's splash screen animation has stopped. * This can be used to detect when a device is fully booted and ready to use. * * @param pid */ async function isBootAnimationCompleteAsync(pid) { try { const output = await getAdbOutputAsync(adbPidArgs(pid, 'shell', 'getprop', 'init.svc.bootanim')); return !!output.match(/stopped/); } catch (_unused2) { return false; } } async function startEmulatorAsync(device) { _Logger().default.global.info(`\u203A Attempting to open emulator: ${device.name}`); // Start a process to open an emulator const emulatorProcess = _child_process().default.spawn(whichEmulator(), [`@${device.name}` // disable animation for faster boot -- this might make it harder to detect if it mounted properly tho //'-no-boot-anim' // '-google-maps-key' -- TODO: Use from config ], { stdio: 'ignore', detached: true }); emulatorProcess.unref(); return new Promise((resolve, reject) => { const waitTimer = setInterval(async () => { const bootedDevices = await getAttachedDevicesAsync(); const connected = bootedDevices.find(({ name }) => name === device.name); if (connected) { const isBooted = await isBootAnimationCompleteAsync(connected.pid); if (isBooted) { stopWaiting(); resolve(connected); } } }, 1000); // Reject command after timeout const maxTimer = setTimeout(() => { const manualCommand = `${whichEmulator()} @${device.name}`; stopWaitingAndReject(`It took too long to start the Android emulator: ${device.name}. You can try starting the emulator manually from the terminal with: ${manualCommand}`); }, EMULATOR_MAX_WAIT_TIMEOUT); const stopWaiting = () => { clearTimeout(maxTimer); clearInterval(waitTimer); }; const stopWaitingAndReject = message => { stopWaiting(); reject(new Error(message)); clearInterval(waitTimer); }; emulatorProcess.on('error', ({ message }) => stopWaitingAndReject(message)); emulatorProcess.on('exit', () => { const manualCommand = `${whichEmulator()} @${device.name}`; stopWaitingAndReject(`The emulator (${device.name}) quit before it finished opening. You can try starting the emulator manually from the terminal with: ${manualCommand}`); }); }); } // TODO: This is very expensive for some operations. async function getAttachedDevicesAsync() { const output = await getAdbOutputAsync(['devices', '-l']); const splitItems = output.trim().replace(/\n$/, '').split(_os().default.EOL); // First line is `"List of devices attached"`, remove it // @ts-ignore: todo const attachedDevices = splitItems.slice(1, splitItems.length).map(line => { // unauthorized: ['FA8251A00719', 'unauthorized', 'usb:338690048X', 'transport_id:5'] // authorized: ['FA8251A00719', 'device', 'usb:336592896X', 'product:walleye', 'model:Pixel_2', 'device:walleye', 'transport_id:4'] // emulator: ['emulator-5554', 'offline', 'transport_id:1'] const props = line.split(' ').filter(Boolean); const isAuthorized = props[1] !== 'unauthorized'; const type = line.includes('emulator') ? 'emulator' : 'device'; return { props, type, isAuthorized }; }).filter(({ props: [pid] }) => !!pid); const devicePromises = attachedDevices.map(async props => { const { type, props: [pid, ...deviceInfo], isAuthorized } = props; let name = null; if (type === 'device') { if (isAuthorized) { // Possibly formatted like `model:Pixel_2` // Transform to `Pixel_2` const modelItem = deviceInfo.find(info => info.includes('model:')); if (modelItem) { name = modelItem.replace('model:', ''); } } // unauthorized devices don't have a name available to read if (!name) { // Device FA8251A00719 name = `Device ${pid}`; } } else { var _await$getAbdNameForE; // Given an emulator pid, get the emulator name which can be used to start the emulator later. name = (_await$getAbdNameForE = await getAbdNameForEmulatorIdAsync(pid)) !== null && _await$getAbdNameForE !== void 0 ? _await$getAbdNameForE : ''; } return { pid, name, type, isAuthorized, isBooted: true }; }); return Promise.all(devicePromises); } function isPlatformSupported() { return process.platform === 'darwin' || process.platform === 'win32' || process.platform === 'linux'; } async function adbAlreadyRunning(adb) { try { const result = await (0, _spawnAsync().default)(adb, ['start-server']); const lines = (0, _trim().default)(result.stderr).split(/\r?\n/); return lines.includes('* daemon started successfully') === false; } catch (e) { let errorMessage = (0, _trim().default)(e.stderr || e.stdout); if (errorMessage.startsWith(BEGINNING_OF_ADB_ERROR_MESSAGE)) { errorMessage = errorMessage.substring(BEGINNING_OF_ADB_ERROR_MESSAGE.length); } throw new Error(errorMessage); } } async function getAdbOutputAsync(args) { await Binaries().addToPathAsync('adb'); const adb = whichADB(); if (_isAdbOwner === null) { const alreadyRunning = await adbAlreadyRunning(adb); _isAdbOwner = alreadyRunning === false; } try { const result = await (0, _spawnAsync().default)(adb, args); return result.stdout; } catch (e) { let errorMessage = (e.stderr || e.stdout || e.message).trim(); if (errorMessage.startsWith(BEGINNING_OF_ADB_ERROR_MESSAGE)) { errorMessage = errorMessage.substring(BEGINNING_OF_ADB_ERROR_MESSAGE.length); } throw new Error(errorMessage); } } async function _isDeviceAuthorizedAsync(device) { // TODO: Get the latest version of the device in case isAuthorized changes. return device.isAuthorized; } async function isInstalledAsync(device, androidPackage) { const packages = await getAdbOutputAsync(adbPidArgs(device.pid, 'shell', 'pm', 'list', 'packages', androidPackage)); const lines = packages.split(/\r?\n/); for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line === `package:${androidPackage}`) { return true; } } return false; } // Expo installed async function _isExpoInstalledAsync(device) { return await isInstalledAsync(device, 'host.exp.exponent'); } async function ensureDevClientInstalledAsync(device, applicationId) { if (!(await isInstalledAsync(device, applicationId))) { throw new Error(`The development client (${applicationId}) for this project is not installed. ` + `Please build and install the client on the simulator first.\n${(0, _TerminalLink().learnMore)('https://docs.expo.io/clients/distribution-for-android/')}`); } } async function getExpoVersionAsync(device) { const info = await getAdbOutputAsync(adbPidArgs(device.pid, 'shell', 'dumpsys', 'package', 'host.exp.exponent')); const regex = /versionName=([0-9.]+)/; const regexMatch = regex.exec(info); if (!regexMatch || regexMatch.length < 2) { return null; } return regexMatch[1]; } async function isClientOutdatedAsync(device, sdkVersion) { var _clientForSdk$version; const versions = await Versions().versionsAsync(); const clientForSdk = await getClientForSDK(sdkVersion); const latestVersionForSdk = (_clientForSdk$version = clientForSdk === null || clientForSdk === void 0 ? void 0 : clientForSdk.version) !== null && _clientForSdk$version !== void 0 ? _clientForSdk$version : versions.androidVersion; const installedVersion = await getExpoVersionAsync(device); return !installedVersion || _semver().default.lt(installedVersion, latestVersionForSdk); } function _apkCacheDirectory() { const dotExpoHomeDirectory = _UserSettings().default.dotExpoHomeDirectory(); const dir = _path().default.join(dotExpoHomeDirectory, 'android-apk-cache'); _fsExtra().default.mkdirpSync(dir); return dir; } async function downloadApkAsync(url, downloadProgressCallback) { if (!url) { const versions = await Versions().versionsAsync(); url = versions.androidUrl; } const filename = _path().default.parse(url).name; const apkPath = _path().default.join(_apkCacheDirectory(), `${filename}.apk`); if (await _fsExtra().default.pathExists(apkPath)) { return apkPath; } await _Api().default.downloadAsync(url, apkPath, undefined, downloadProgressCallback); return apkPath; } async function installExpoAsync({ device, url, version }) { const bar = new (_progress().default)('Downloading the Expo Go app [:bar] :percent :etas', { total: 100, width: 64 }); let warningTimer; const setWarningTimer = () => { if (warningTimer) { clearTimeout(warningTimer); } return setTimeout(() => { _Logger().default.global.info(''); _Logger().default.global.info('This download is taking longer than expected. You can also try downloading the clients from the website at https://expo.io/tools'); }, INSTALL_WARNING_TIMEOUT); }; _Logger().default.notifications.info({ code: _NotificationCode().default.START_LOADING }); warningTimer = setWarningTimer(); const path = await downloadApkAsync(url, progress => bar.tick(1, progress)); _Logger().default.notifications.info({ code: _NotificationCode().default.STOP_LOADING }); if (version) { _Logger().default.global.info(`Installing Expo client ${version} on device`); } else { _Logger().default.global.info(`Installing Expo client on device`); } _Logger().default.notifications.info({ code: _NotificationCode().default.START_LOADING }); warningTimer = setWarningTimer(); const result = await getAdbOutputAsync(adbPidArgs(device.pid, 'install', path)); _Logger().default.notifications.info({ code: _NotificationCode().default.STOP_LOADING }); clearTimeout(warningTimer); return result; } async function isDeviceBootedAsync({ name } = {}) { var _devices$find; const devices = await getAttachedDevicesAsync(); if (!name) { var _devices$; return (_devices$ = devices[0]) !== null && _devices$ !== void 0 ? _devices$ : null; } return (_devices$find = devices.find(device => device.name === name)) !== null && _devices$find !== void 0 ? _devices$find : null; } async function uninstallExpoAsync(device) { _Logger().default.global.info('Uninstalling Expo client from Android device.'); // we need to check if its installed, else we might bump into "Failure [DELETE_FAILED_INTERNAL_ERROR]" const isInstalled = await _isExpoInstalledAsync(device); if (!isInstalled) { return; } try { return await getAdbOutputAsync(adbPidArgs(device.pid, 'uninstall', 'host.exp.exponent')); } catch (e) { _Logger().default.global.error('Could not uninstall Expo client from your device, please uninstall Expo client manually and try again.'); throw e; } } async function upgradeExpoAsync(options) { const { url, version } = options || {}; try { const devices = await getAttachedDevicesAsync(); if (!devices.length) { throw new Error('no devices connected'); } const device = await attemptToStartEmulatorOrAssertAsync(devices[0]); if (!device) { return false; } await uninstallExpoAsync(device); await installExpoAsync({ device, url, version }); if (_lastUrl) { _Logger().default.global.info(`Opening ${_lastUrl} in Expo.`); await getAdbOutputAsync(['shell', 'am', 'start', '-a', 'android.intent.action.VIEW', '-d', _lastUrl]); _lastUrl = null; } return true; } catch (e) { _Logger().default.global.error(e.message); return false; } } async function _openUrlAsync({ pid, url, applicationId }) { // NOTE(brentvatne): temporary workaround! launch expo client first, then // launch the project! // https://github.com/expo/expo/issues/7772 // adb shell monkey -p host.exp.exponent -c android.intent.category.LAUNCHER 1 const openClient = await getAdbOutputAsync(adbPidArgs(pid, 'shell', 'monkey', '-p', applicationId, '-c', 'android.intent.category.LAUNCHER', '1')); if (openClient.includes(CANT_START_ACTIVITY_ERROR)) { throw new Error(openClient.substring(openClient.indexOf('Error: '))); } const openProject = await getAdbOutputAsync(adbPidArgs(pid, 'shell', 'am', 'start', '-a', 'android.intent.action.VIEW', '-d', url)); if (openProject.includes(CANT_START_ACTIVITY_ERROR)) { throw new Error(openProject.substring(openProject.indexOf('Error: '))); } return openProject; } async function attemptToStartEmulatorOrAssertAsync(device) { // TODO: Add a light-weight method for checking since a device could disconnect. if (!(await isDeviceBootedAsync(device))) { device = await startEmulatorAsync(device); } if (!(await _isDeviceAuthorizedAsync(device))) { logUnauthorized(device); return null; } return device; } function logUnauthorized(device) { _Logger().default.global.warn(`\nThis computer is not authorized for developing on ${_chalk().default.bold(device.name)}. ${_chalk().default.dim((0, _TerminalLink().learnMore)('https://expo.fyi/authorize-android-device'))}`); } // Keep a list of simulator UDIDs so we can prevent asking multiple times if a user wants to upgrade. // This can prevent annoying interactions when they don't want to upgrade for whatever reason. const hasPromptedToUpgrade = {}; async function openUrlAsync({ url, device, isDetached = false, sdkVersion, devClient = false, exp, projectRoot }) { try { const bootedDevice = await attemptToStartEmulatorOrAssertAsync(device); if (!bootedDevice) { return; } device = bootedDevice; let installedExpo = false; let clientApplicationId = 'host.exp.exponent'; if (devClient) { const applicationId = await _configPlugins().AndroidConfig.Package.getApplicationIdAsync(projectRoot); if (!applicationId) { // TODO(ville): possibly need to compare Gradle project with app.json/config.ts // and show a helpful error message, if there's a mismatch. throw new Error(`Could not find applicationId in ${_configPlugins().AndroidConfig.Paths.getAppBuildGradle(projectRoot)}`); } else { clientApplicationId = applicationId; } await ensureDevClientInstalledAsync(device, clientApplicationId); } else if (!isDetached) { var _device$pid; let shouldInstall = !(await _isExpoInstalledAsync(device)); const promptKey = (_device$pid = device.pid) !== null && _device$pid !== void 0 ? _device$pid : 'unknown'; if (!shouldInstall && !hasPromptedToUpgrade[promptKey] && (await isClientOutdatedAsync(device, sdkVersion))) { // Only prompt once per device, per run. hasPromptedToUpgrade[promptKey] = true; const confirm = await Prompts().confirmAsync({ initial: true, message: `Expo client on ${device.name} (${device.type}) is outdated, would you like to upgrade?` }); if (confirm) { await uninstallExpoAsync(device); shouldInstall = true; } } if (shouldInstall) { const androidClient = await getClientForSDK(sdkVersion); await installExpoAsync({ device, ...androidClient }); installedExpo = true; } _lastUrl = url; // _checkExpoUpToDateAsync(); // let this run in background } _Logger().default.global.info(`Opening ${_chalk().default.underline(url)} on ${_chalk().default.bold(device.name)}`); try { await _openUrlAsync({ pid: device.pid, url, applicationId: clientApplicationId }); } catch (e) { if (isDetached) { e.message = `Error running app. Have you installed the app already using Android Studio? Since you are detached you must build manually. ${e.message}`; } else { e.message = `Error running app. ${e.message}`; } throw e; } if (device.type === 'emulator') {// TODO: Bring the emulator window to the front. } _Analytics().default.logEvent('Open Url on Device', { platform: 'android', installedExpo }); } catch (e) { e.message = `Error running adb: ${e.message}`; throw e; } } async function getClientForSDK(sdkVersionString) { if (!sdkVersionString) { return null; } const sdkVersion = (await Versions().sdkVersionsAsync())[sdkVersionString]; return { url: sdkVersion.androidClientUrl, version: sdkVersion.androidClientVersion }; } async function openProjectAsync({ projectRoot, shouldPrompt, devClient = false }) { try { await startAdbReverseAsync(projectRoot); const projectUrl = await UrlUtils().constructDeepLinkAsync(projectRoot); const { exp } = (0, _config().getConfig)(projectRoot, { skipSDKVersionRequirement: true }); const devices = await getAllAvailableDevicesAsync(); let device = devices[0]; if (shouldPrompt) { device = await promptForDeviceAsync(devices); } if (!device) { return { success: false, error: 'escaped' }; } await openUrlAsync({ url: projectUrl, device, isDetached: !!exp.isDetached, sdkVersion: exp.sdkVersion, devClient, exp, projectRoot }); return { success: true, url: projectUrl }; } catch (e) { _Logger().default.global.error(`Couldn't start project on Android: ${e.message}`); return { success: false, error: e }; } } async function openWebProjectAsync({ projectRoot, shouldPrompt }) { try { await startAdbReverseAsync(projectRoot); const projectUrl = await (0, _Webpack().getUrlAsync)(projectRoot); if (projectUrl === null) { return { success: false, error: `The web project has not been started yet` }; } const devices = await getAllAvailableDevicesAsync(); let device = devices[0]; if (shouldPrompt) { device = await promptForDeviceAsync(devices); } if (!device) { return { success: false, error: 'escaped' }; } await openUrlAsync({ url: projectUrl, device, isDetached: true, projectRoot }); return { success: true, url: projectUrl }; } catch (e) { _Logger().default.global.error(`Couldn't open the web project on Android: ${e.message}`); return { success: false, error: e }; } } // Adb reverse async function startAdbReverseAsync(projectRoot) { const packagerInfo = await ProjectSettings().readPackagerInfoAsync(projectRoot); const expRc = await (0, _config().readExpRcAsync)(projectRoot); const userDefinedAdbReversePorts = expRc.extraAdbReversePorts || []; const adbReversePorts = [packagerInfo.packagerPort, packagerInfo.expoServerPort, ...userDefinedAdbReversePorts].filter(Boolean); const devices = await getAttachedDevicesAsync(); for (const device of devices) { for (const port of adbReversePorts) { if (!(await adbReverse({ device, port }))) { return false; } } } return true; } async function stopAdbReverseAsync(projectRoot) { const packagerInfo = await ProjectSettings().readPackagerInfoAsync(projectRoot); const expRc = await (0, _config().readExpRcAsync)(projectRoot); const userDefinedAdbReversePorts = expRc.extraAdbReversePorts || []; const adbReversePorts = [packagerInfo.packagerPort, packagerInfo.expoServerPort, ...userDefinedAdbReversePorts].filter(Boolean); const devices = await getAttachedDevicesAsync(); for (const device of devices) { for (const port of adbReversePorts) { await adbReverseRemove({ device, port }); } } } async function adbReverse({ device, port }) { if (!(await _isDeviceAuthorizedAsync(device))) { return false; } try { await getAdbOutputAsync(adbPidArgs(device.pid, 'reverse', `tcp:${port}`, `tcp:${port}`)); return true; } catch (e) { _Logger().default.global.warn(`Couldn't adb reverse: ${e.message}`); return false; } } async function adbReverseRemove({ device, port }) { if (!(await _isDeviceAuthorizedAsync(device))) { return false; } try { await getAdbOutputAsync(adbPidArgs(device.pid, 'reverse', '--remove', `tcp:${port}`)); return true; } catch (e) { // Don't send this to warn because we call this preemptively sometimes _Logger().default.global.debug(`Couldn't adb reverse remove: ${e.message}`); return false; } } function adbPidArgs(pid, ...options) { const args = []; if (pid) { args.push('-s', pid); } return args.concat(options); } const splashScreenDPIConstraints = [{ dpi: 'mdpi', sizeMultiplier: 1 }, { dpi: 'hdpi', sizeMultiplier: 1.5 }, { dpi: 'xhdpi', sizeMultiplier: 2 }, { dpi: 'xxhdpi', sizeMultiplier: 3 }, { dpi: 'xxxhdpi', sizeMultiplier: 4 }]; /** * Checks whether `resizeMode` is set to `native` and if `true` analyzes provided images for splashscreen * providing `Logger` feedback upon problems. * @param projectDir - directory of the expo project * @since SDK33 */ async function checkSplashScreenImages(projectDir) { var _ref, _exp$android$splash$r, _exp$android, _exp$android$splash, _exp$splash, _exp$splash2, _exp$android2; const { exp } = (0, _config().getConfig)(projectDir); // return before SDK33 if (!Versions().gteSdkVersion(exp, '33.0.0')) { return; } const splashScreenMode = (_ref = (_exp$android$splash$r = (_exp$android = exp.android) === null || _exp$android === void 0 ? void 0 : (_exp$android$splash = _exp$android.splash) === null || _exp$android$splash === void 0 ? void 0 : _exp$android$splash.resizeMode) !== null && _exp$android$splash$r !== void 0 ? _exp$android$splash$r : (_exp$splash = exp.splash) === null || _exp$splash === void 0 ? void 0 : _exp$splash.resizeMode) !== null && _ref !== void 0 ? _ref : 'contain'; // only mode `native` is handled by this check if (splashScreenMode === 'contain' || splashScreenMode === 'cover') { return; } const generalSplashImagePath = (_exp$splash2 = exp.splash) === null || _exp$splash2 === void 0 ? void 0 : _exp$splash2.image; if (!generalSplashImagePath) { _Logger().default.global.warn(`Couldn't read '${_chalk().default.italic('splash.image')}' from ${_chalk().default.italic('app.json')}. Provide asset that would serve as baseline splash image.`); return; } const generalSplashImage = await (0, _ImageUtils().getImageDimensionsAsync)(projectDir, generalSplashImagePath); if (!generalSplashImage) { _Logger().default.global.warn(`Couldn't read dimensions of provided splash image '${_chalk().default.italic(generalSplashImagePath)}'. Does the file exist?`); return; } const androidSplash = (_exp$android2 = exp.android) === null || _exp$android2 === void 0 ? void 0 : _exp$android2.splash; const androidSplashImages = []; for (const { dpi, sizeMultiplier } of splashScreenDPIConstraints) { const imageRelativePath = androidSplash === null || androidSplash === void 0 ? void 0 : androidSplash[dpi]; if (imageRelativePath) { const splashImage = await (0, _ImageUtils().getImageDimensionsAsync)(projectDir, imageRelativePath); if (!splashImage) { _Logger().default.global.warn(`Couldn't read dimensions of provided splash image '${_chalk().default.italic(imageRelativePath)}'. Does the file exist?`); continue; } const { width, height } = splashImage; const expectedWidth = sizeMultiplier * generalSplashImage.width; const expectedHeight = sizeMultiplier * generalSplashImage.height; androidSplashImages.push({ dpi, width, height, expectedWidth, expectedHeight, sizeMatches: width === expectedWidth && height === expectedHeight }); } } if (androidSplashImages.length === 0) { _Logger().default.global.warn(`Splash resizeMode is set to 'native', but you haven't provided any images for different DPIs. Be aware that your splash image will be used as xxxhdpi asset and its ${_chalk().default.bold('actual size will be different')} depending on device's DPI. See https://docs.expo.io/guides/splash-screens/#splash-screen-api-limitations-on-android for more information`); return; } if (androidSplashImages.some(({ sizeMatches }) => !sizeMatches)) { _Logger().default.global.warn(`Splash resizeMode is set to 'native' and you've provided different images for different DPIs, but their sizes mismatch expected ones: [dpi: provided (expected)] ${androidSplashImages.map(({ dpi, width, height, expectedWidth, expectedHeight }) => `${dpi}: ${width}x${height} (${expectedWidth}x${expectedHeight})`).join(', ')} See https://docs.expo.io/guides/splash-screens/#splash-screen-api-limitations-on-android for more information`); } } async function maybeStopAdbDaemonAsync() { if (_isAdbOwner !== true) { return false; } try { await getAdbOutputAsync(['kill-server']); return true; } catch (_unused3) { return false; } } function nameStyleForDevice(device) { const isActive = device.isBooted; if (!isActive) { // Use no style changes for a disconnected device that is available to be opened. return text => text; } // A device that is connected and ready to be used should be bolded to match iOS. if (device.isAuthorized) { return _chalk().default.bold; } // Devices that are unauthorized and connected cannot be used, but they are connected so gray them out. return text => _chalk().default.bold(_chalk().default.gray(text)); } async function promptForDeviceAsync(devices) { // TODO: provide an option to add or download more simulators // Pause interactions on the TerminalUI Prompts().pauseInteractions(); const { value } = await (0, _prompts().default)({ type: 'autocomplete', name: 'value', limit: 11, message: 'Select a device/emulator', choices: devices.map(item => { const format = nameStyleForDevice(item); const type = item.isAuthorized ? item.type : 'unauthorized'; return { title: `${format(item.name)} ${_chalk().default.dim(`(${type})`)}`, value: item.name }; }), suggest: (input, choices) => { const regex = new RegExp(input, 'i'); return choices.filter(choice => regex.test(choice.title)); } }); // Resume interactions on the TerminalUI Prompts().resumeInteractions(); const device = value ? devices.find(({ name }) => name === value) : null; if ((device === null || device === void 0 ? void 0 : device.isAuthorized) === false) { logUnauthorized(device); return null; } return device; } //# sourceMappingURL=__sourcemaps__/Android.js.map