@expo/xdl
Version:
The Expo Development Library
1,274 lines (1,007 loc) • 35.4 kB
JavaScript
;
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