"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isPlatformSupported = isPlatformSupported;
exports.getAdbOutputAsync = getAdbOutputAsync;
exports.downloadApkAsync = downloadApkAsync;
exports.installExpoAsync = installExpoAsync;
exports.uninstallExpoAsync = uninstallExpoAsync;
exports.upgradeExpoAsync = upgradeExpoAsync;
exports.assertDeviceReadyAsync = assertDeviceReadyAsync;
exports.openProjectAsync = openProjectAsync;
exports.openWebProjectAsync = openWebProjectAsync;
exports.startAdbReverseAsync = startAdbReverseAsync;
exports.stopAdbReverseAsync = stopAdbReverseAsync;
exports.checkSplashScreenImages = checkSplashScreenImages;
function _lodash() {
const data = _interopRequireDefault(require("lodash"));
_lodash = function () {
return data;
};
return data;
}
function _fsExtra() {
const data = _interopRequireDefault(require("fs-extra"));
_fsExtra = function () {
return data;
};
return data;
}
function _spawnAsync() {
const data = _interopRequireDefault(require("@expo/spawn-async"));
_spawnAsync = function () {
return data;
};
return data;
}
function _path() {
const data = _interopRequireDefault(require("path"));
_path = function () {
return data;
};
return data;
}
function _semver() {
const data = _interopRequireDefault(require("semver"));
_semver = function () {
return data;
};
return data;
}
function _chalk() {
const data = _interopRequireDefault(require("chalk"));
_chalk = function () {
return data;
};
return data;
}
function ConfigUtils() {
const data = _interopRequireWildcard(require("@expo/config"));
ConfigUtils = function () {
return data;
};
return data;
}
function Analytics() {
const data = _interopRequireWildcard(require("./Analytics"));
Analytics = function () {
return data;
};
return data;
}
function Binaries() {
const data = _interopRequireWildcard(require("./Binaries"));
Binaries = function () {
return data;
};
return data;
}
function _Api() {
const data = _interopRequireDefault(require("./Api"));
_Api = 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 ProjectUtils() {
const data = _interopRequireWildcard(require("./project/ProjectUtils"));
ProjectUtils = function () {
return data;
};
return data;
}
function ProjectSettings() {
const data = _interopRequireWildcard(require("./ProjectSettings"));
ProjectSettings = function () {
return data;
};
return data;
}
function _UserSettings() {
const data = _interopRequireDefault(require("./UserSettings"));
_UserSettings = function () {
return data;
};
return data;
}
function UrlUtils() {
const data = _interopRequireWildcard(require("./UrlUtils"));
UrlUtils = function () {
return data;
};
return data;
}
function Versions() {
const data = _interopRequireWildcard(require("./Versions"));
Versions = function () {
return data;
};
return data;
}
function _ImageUtils() {
const data = require("./tools/ImageUtils");
_ImageUtils = function () {
return data;
};
return data;
}
function _Webpack() {
const data = require("./Webpack");
_Webpack = function () {
return data;
};
return data;
}
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// @ts-ignore
let _lastUrl = null;
const BEGINNING_OF_ADB_ERROR_MESSAGE = 'error: ';
const CANT_START_ACTIVITY_ERROR = 'Activity not started, unable to resolve Intent';
function isPlatformSupported() {
return process.platform === 'darwin' || process.platform === 'win32' || process.platform === 'linux';
}
async function getAdbOutputAsync(args) {
await Binaries().addToPathAsync('adb');
try {
let result = await (0, _spawnAsync().default)('adb', args);
return result.stdout;
} catch (e) {
let errorMessage = _lodash().default.trim(e.stderr);
if (errorMessage.startsWith(BEGINNING_OF_ADB_ERROR_MESSAGE)) {
errorMessage = errorMessage.substring(BEGINNING_OF_ADB_ERROR_MESSAGE.length);
}
throw new Error(errorMessage);
}
} // Device attached
async function _isDeviceAttachedAsync() {
let devices = await getAdbOutputAsync(['devices']);
let lines = _lodash().default.trim(devices).split(/\r?\n/); // First line is "List of devices".
return lines.length > 1;
}
async function _isDeviceAuthorizedAsync() {
let devices = await getAdbOutputAsync(['devices']);
let lines = _lodash().default.trim(devices).split(/\r?\n/);
lines.shift();
let listOfDevicesWithoutFirstLine = lines.join('\n'); // result looks like "072c4cf200e333c7 device" when authorized
// and "072c4cf200e333c7 unauthorized" when not.
return listOfDevicesWithoutFirstLine.includes('device');
} // Expo installed
async function _isExpoInstalledAsync() {
let packages = await getAdbOutputAsync(['shell', 'pm', 'list', 'packages', '-f']);
let lines = packages.split(/\r?\n/);
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
if (line.includes('host.exp.exponent.test')) {
continue;
}
if (line.includes('host.exp.exponent')) {
return true;
}
}
return false;
}
async function _expoVersionAsync() {
let info = await getAdbOutputAsync(['shell', 'dumpsys', 'package', 'host.exp.exponent']);
let regex = /versionName=([0-9.]+)/;
let regexMatch = regex.exec(info);
if (!regexMatch || regexMatch.length < 2) {
return null;
}
return regexMatch[1];
}
async function _checkExpoUpToDateAsync() {
let versions = await Versions().versionsAsync();
let installedVersion = await _expoVersionAsync();
if (!installedVersion || _semver().default.lt(installedVersion, versions.androidVersion)) {
_Logger().default.notifications.warn({
code: _NotificationCode().default.OLD_ANDROID_APP_VERSION
}, 'This version of the Expo app is out of date. Uninstall the app and run again to upgrade.');
}
}
function _apkCacheDirectory() {
let dotExpoHomeDirectory = _UserSettings().default.dotExpoHomeDirectory();
let dir = _path().default.join(dotExpoHomeDirectory, 'android-apk-cache');
_fsExtra().default.mkdirpSync(dir);
return dir;
}
async function downloadApkAsync(url) {
let versions = await Versions().versionsAsync();
let apkPath = _path().default.join(_apkCacheDirectory(), `Exponent-${versions.androidVersion}.apk`);
if (await _fsExtra().default.pathExists(apkPath)) {
return apkPath;
}
await _Api().default.downloadAsync(url || versions.androidUrl, _path().default.join(_apkCacheDirectory(), `Exponent-${versions.androidVersion}.apk`));
return apkPath;
}
async function installExpoAsync(url) {
_Logger().default.global.info(`Downloading latest version of Expo`);
_Logger().default.notifications.info({
code: _NotificationCode().default.START_LOADING
});
let path = await downloadApkAsync(url);
_Logger().default.notifications.info({
code: _NotificationCode().default.STOP_LOADING
});
_Logger().default.global.info(`Installing Expo on device`);
_Logger().default.notifications.info({
code: _NotificationCode().default.START_LOADING
});
let result = await getAdbOutputAsync(['install', path]);
_Logger().default.notifications.info({
code: _NotificationCode().default.STOP_LOADING
});
return result;
}
async function uninstallExpoAsync() {
_Logger().default.global.info('Uninstalling Expo from Android device.');
return await getAdbOutputAsync(['uninstall', 'host.exp.exponent']);
}
async function upgradeExpoAsync() {
try {
await assertDeviceReadyAsync();
await uninstallExpoAsync();
await installExpoAsync();
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;
}
} // Open Url
async function assertDeviceReadyAsync() {
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.`;
if (!(await _isDeviceAttachedAsync())) {
throw new Error(`No Android device found. Please connect a device and follow the instructions here to enable USB debugging:\n${genymotionMessage}`);
}
if (!(await _isDeviceAuthorizedAsync())) {
throw new Error(`This computer is not authorized to debug the device. Please follow the instructions here to enable USB debugging:\n${genymotionMessage}`);
}
}
async function _openUrlAsync(url) {
let output = await getAdbOutputAsync(['shell', 'am', 'start', '-a', 'android.intent.action.VIEW', '-d', url]);
if (output.includes(CANT_START_ACTIVITY_ERROR)) {
throw new Error(output.substring(output.indexOf('Error: ')));
}
return output;
}
async function openUrlAsync(url, isDetached = false) {
try {
await assertDeviceReadyAsync();
let installedExpo = false;
if (!isDetached && !(await _isExpoInstalledAsync())) {
await installExpoAsync();
installedExpo = true;
}
if (!isDetached) {
_lastUrl = url;
_checkExpoUpToDateAsync(); // let this run in background
}
_Logger().default.global.info(`Opening on Android device`);
try {
await _openUrlAsync(url);
} 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;
}
Analytics().logEvent('Open Url on Device', {
platform: 'android',
installedExpo
});
} catch (e) {
e.message = `Error running adb: ${e.message}`;
throw e;
}
}
async function openProjectAsync(projectRoot) {
try {
await startAdbReverseAsync(projectRoot);
let projectUrl = await UrlUtils().constructManifestUrlAsync(projectRoot);
let {
exp
} = await ConfigUtils().readConfigJsonAsync(projectRoot);
await openUrlAsync(projectUrl, !!exp.isDetached);
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) {
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`
};
}
await openUrlAsync(projectUrl, true);
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 ProjectUtils().readExpRcAsync(projectRoot);
const userDefinedAdbReversePorts = expRc.extraAdbReversePorts || [];
let adbReversePorts = [packagerInfo.packagerPort, packagerInfo.expoServerPort, ...userDefinedAdbReversePorts];
for (let port of adbReversePorts) {
if (!(await adbReverse(port))) {
return false;
}
}
return true;
}
async function stopAdbReverseAsync(projectRoot) {
const packagerInfo = await ProjectSettings().readPackagerInfoAsync(projectRoot);
const expRc = await ProjectUtils().readExpRcAsync(projectRoot);
const userDefinedAdbReversePorts = expRc.extraAdbReversePorts || [];
let adbReversePorts = [packagerInfo.packagerPort, packagerInfo.expoServerPort, ...userDefinedAdbReversePorts];
for (let port of adbReversePorts) {
await adbReverseRemove(port);
}
}
async function adbReverse(port) {
if (!(await _isDeviceAuthorizedAsync())) {
return false;
}
try {
await getAdbOutputAsync(['reverse', `tcp:${port}`, `tcp:${port}`]);
return true;
} catch (e) {
_Logger().default.global.warn(`Couldn't adb reverse: ${e.message}`);
return false;
}
}
async function adbReverseRemove(port) {
if (!(await _isDeviceAuthorizedAsync())) {
return false;
}
try {
await getAdbOutputAsync(['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;
}
}
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) {
const {
exp
} = await ConfigUtils().readConfigJsonAsync(projectDir); // return before SDK33
if (!Versions().gteSdkVersion(exp, '33.0.0')) {
return;
}
const splashScreenMode = _lodash().default.get(exp, 'android.splash.resizeMode') || _lodash().default.get(exp, 'splash.resizeMode', 'contain'); // only mode `native` is handled by this check
if (splashScreenMode === 'contain' || splashScreenMode === 'cover') {
return;
}
const generalSplashImagePath = _lodash().default.get(exp, 'splash.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 = _lodash().default.get(exp, 'android.splash');
const androidSplashImages = [];
for (const {
dpi,
sizeMultiplier
} of splashScreenDPIConstraints) {
const imageRelativePath = _lodash().default.get(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/versions/latest/guides/splash-screens/#differences-between-environments---android for more information`);
return;
}
if (_lodash().default.some(androidSplashImages, ({
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/versions/latest/guides/splash-screens/#differences-between-environments---android for more information`);
}
}
//# sourceMappingURL=__sourcemaps__/Android.js.map