@expo/xdl
Version:
The Expo Development Library
427 lines (333 loc) • 12.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isSimulatorRunningAsync = isSimulatorRunningAsync;
exports.getDefaultSimulatorDeviceUDIDAsync = getDefaultSimulatorDeviceUDIDAsync;
exports.getContainerPathAsync = getContainerPathAsync;
exports.openURLAsync = openURLAsync;
exports.openBundleIdAsync = openBundleIdAsync;
exports.bootAsync = bootAsync;
exports.getBootedSimulatorsAsync = getBootedSimulatorsAsync;
exports.isSimulatorBootedAsync = isSimulatorBootedAsync;
exports.installAsync = installAsync;
exports.uninstallAsync = uninstallAsync;
exports.listAsync = listAsync;
exports.listDevicesAsync = listDevicesAsync;
exports.shutdownAsync = shutdownAsync;
exports.updatePermissionsAsync = updatePermissionsAsync;
exports.setAppearanceAsync = setAppearanceAsync;
exports.eraseAsync = eraseAsync;
exports.eraseAllAsync = eraseAllAsync;
exports.addMediaAsync = addMediaAsync;
exports.captureScreenAsync = captureScreenAsync;
exports.deleteUnavailableAsync = deleteUnavailableAsync;
exports.simctlAsync = simctlAsync;
exports.isSimulatorAppRunningAsync = isSimulatorAppRunningAsync;
exports.openSimulatorAppAsync = openSimulatorAppAsync;
exports.killAllAsync = killAllAsync;
exports.isLicenseOutOfDate = isLicenseOutOfDate;
exports.isXcrunInstalledAsync = isXcrunInstalledAsync;
exports.xcrunAsync = xcrunAsync;
exports.xcrunWithLogging = xcrunWithLogging;
function osascript() {
const data = _interopRequireWildcard(require("@expo/osascript"));
osascript = 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 _path() {
const data = _interopRequireDefault(require("path"));
_path = function () {
return data;
};
return data;
}
function _Logger() {
const data = _interopRequireDefault(require("./Logger"));
_Logger = function () {
return data;
};
return data;
}
function _XDLError() {
const data = _interopRequireDefault(require("./XDLError"));
_XDLError = function () {
return data;
};
return data;
}
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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; }
async function isSimulatorRunningAsync() {
try {
const zeroMeansNo = (await osascript().execAsync('tell app "System Events" to count processes whose name is "Simulator"')).trim();
if (zeroMeansNo === '0') {
return false;
}
} catch (_unused) {
return false;
}
return true;
}
async function getDefaultSimulatorDeviceUDIDAsync() {
try {
const {
stdout: defaultDeviceUDID
} = await (0, _spawnAsync().default)('defaults', ['read', 'com.apple.iphonesimulator', 'CurrentDeviceUDID']);
return defaultDeviceUDID.trim();
} catch (e) {
return null;
}
}
/**
* Returns the local path for the installed tar.app. Returns null when the app isn't installed.
*
* @param udid
* @param bundleIdentifier
*/
async function getContainerPathAsync(udid, bundleIdentifier) {
try {
const {
stdout
} = await xcrunAsync(['simctl', 'get_app_container', deviceUDIDOrBooted(udid), bundleIdentifier]);
return stdout.trim();
} catch (error) {
var _error$stderr;
if ((_error$stderr = error.stderr) === null || _error$stderr === void 0 ? void 0 : _error$stderr.match(/No such file or directory/)) {
return null;
}
throw error;
}
}
async function openURLAsync(options) {
return simctlAsync(['openurl', deviceUDIDOrBooted(options.udid), options.url]);
}
async function openBundleIdAsync(options) {
return simctlAsync(['launch', deviceUDIDOrBooted(options.udid), options.bundleIdentifier]);
} // This will only boot in headless mode if the Simulator app is not running.
async function bootAsync({
udid
}) {
try {
// Skip logging since this is likely to fail.
await xcrunAsync(['simctl', 'boot', udid]);
} catch (error) {
var _error$stderr2;
if (!((_error$stderr2 = error.stderr) === null || _error$stderr2 === void 0 ? void 0 : _error$stderr2.match(/Unable to boot device in current state: Booted/))) {
throw error;
}
}
return await isSimulatorBootedAsync({
udid
});
}
async function getBootedSimulatorsAsync() {
const simulatorDeviceInfo = await listAsync('devices');
return Object.values(simulatorDeviceInfo.devices).reduce((prev, runtime) => {
return prev.concat(runtime.filter(device => device.state === 'Booted'));
}, []);
}
async function isSimulatorBootedAsync({
udid
}) {
// Simulators can be booted even if the app isn't running :(
const devices = await getBootedSimulatorsAsync();
if (udid) {
var _devices$find;
return (_devices$find = devices.find(bootedDevice => bootedDevice.udid === udid)) !== null && _devices$find !== void 0 ? _devices$find : null;
} else {
var _devices$;
return (_devices$ = devices[0]) !== null && _devices$ !== void 0 ? _devices$ : null;
}
}
async function installAsync(options) {
return simctlAsync(['install', deviceUDIDOrBooted(options.udid), options.dir]);
}
async function uninstallAsync(options) {
return simctlAsync(['uninstall', deviceUDIDOrBooted(options.udid), options.bundleIdentifier]);
}
function parseSimControlJSONResults(input) {
try {
return JSON.parse(input);
} catch (error) {
// Nov 15, 2020: Observed this can happen when opening the simulator and the simulator prompts the user to update the XC command line tools.
// Unexpected token I in JSON at position 0
if (error.message.match('Unexpected token')) {
_Logger().default.global.error(`Apple's simctl returned malformed JSON:\n${input}`);
}
throw error;
}
} // TODO: Compare with
// const results = await SimControl.xcrunAsync(['instruments', '-s']);
async function listAsync(type, query) {
const result = await simctlAsync(['list', type, '--json', query]);
const info = parseSimControlJSONResults(result.stdout);
for (const runtime of Object.keys(info.devices)) {
// Given a string like 'com.apple.CoreSimulator.SimRuntime.tvOS-13-4'
const runtimeSuffix = runtime.split('com.apple.CoreSimulator.SimRuntime.').pop(); // Create an array [tvOS, 13, 4]
const [osType, ...osVersionComponents] = runtimeSuffix.split('-'); // Join the end components [13, 4] -> '13.4'
const osVersion = osVersionComponents.join('.');
const sims = info.devices[runtime];
for (const device of sims) {
device.runtime = runtime;
device.osVersion = osVersion;
device.windowName = `${device.name} (${osVersion})`;
device.osType = osType;
}
}
return info;
}
/**
* Get a list of all connected devices.
*/
async function listDevicesAsync() {
const {
output
} = await xcrunAsync(['xctrace', 'list', 'devices']);
const text = output.join('');
const devices = [];
if (!text.includes('== Simulators ==')) {
return [];
}
const lines = text.split('\n');
for (const line of lines) {
if (line === '== Simulators ==') {
break;
}
const device = line.match(/(.*?) (\(([0-9.]+)\) )?\(([0-9A-F-]+)\)/i);
if (device) {
const [, name,, osVersion, udid] = device;
const metadata = {
name,
udid,
osVersion: osVersion !== null && osVersion !== void 0 ? osVersion : '??',
deviceType: osVersion ? 'device' : 'catalyst'
};
devices.push(metadata);
}
}
return devices;
}
async function shutdownAsync(udid) {
try {
return simctlAsync(['shutdown', deviceUDIDOrBooted(udid)]);
} catch (e) {
var _e$message;
if (!((_e$message = e.message) === null || _e$message === void 0 ? void 0 : _e$message.includes('No devices are booted.'))) {
throw e;
}
}
return null;
} // Some permission changes will terminate the application if running
async function updatePermissionsAsync(udid, action, permission, bundleIdentifier) {
return simctlAsync(['privacy', deviceUDIDOrBooted(udid), action, permission, bundleIdentifier]);
}
async function setAppearanceAsync(udid, theme) {
return simctlAsync(['ui', deviceUDIDOrBooted(udid), theme]);
} // Cannot be invoked unless the simulator is `shutdown`
async function eraseAsync(udid) {
return simctlAsync(['erase', deviceUDIDOrBooted(udid)]);
}
async function eraseAllAsync() {
return simctlAsync(['erase', 'all']);
} // Add photos and videos to the simulator's gallery
async function addMediaAsync(udid, mediaPath) {
return simctlAsync(['addmedia', deviceUDIDOrBooted(udid), mediaPath]);
}
async function captureScreenAsync(udid, captureType, outputFilePath) {
return simctlAsync(['io', deviceUDIDOrBooted(udid), captureType, `—type=${_path().default.extname(outputFilePath)}`, outputFilePath]);
} // Clear all unused simulators
async function deleteUnavailableAsync() {
return simctlAsync(['delete', 'unavailable']);
}
async function simctlAsync([command, ...args], options) {
return xcrunWithLogging( // @ts-ignore
['simctl', command, ...args.filter(Boolean)], options);
}
function deviceUDIDOrBooted(udid) {
return udid ? udid : 'booted';
}
/**
* I think the app can be open while no simulators are booted.
*/
async function isSimulatorAppRunningAsync() {
try {
const zeroMeansNo = (await osascript().execAsync('tell app "System Events" to count processes whose name is "Simulator"')).trim();
if (zeroMeansNo === '0') {
return false;
}
} catch (error) {
if (error.message.includes('Application isn’t running')) {
return false;
}
throw error;
}
return true;
}
async function openSimulatorAppAsync({
udid
}) {
const args = ['-a', 'Simulator'];
if (udid) {
// This has no effect if the app is already running.
args.push('--args', '-CurrentDeviceUDID', udid);
}
return await (0, _spawnAsync().default)('open', args);
}
async function killAllAsync() {
return await (0, _spawnAsync().default)('killAll', ['Simulator']);
}
function isLicenseOutOfDate(text) {
if (!text) {
return false;
}
const lower = text.toLowerCase();
return lower.includes('xcode') && lower.includes('license');
}
async function isXcrunInstalledAsync() {
try {
await (0, _spawnAsync().default)('xcrun', ['--version']);
return true;
} catch (error) {
return false;
}
}
async function xcrunAsync(args, options) {
try {
return await (0, _spawnAsync().default)('xcrun', args, options);
} catch (e) {
var _e$stderr;
if (isLicenseOutOfDate(e.stdout) || isLicenseOutOfDate(e.stderr)) {
throw new (_XDLError().default)('XCODE_LICENSE_NOT_ACCEPTED', 'Xcode license is not accepted. Please run `sudo xcodebuild -license`.');
} else if ((_e$stderr = e.stderr) === null || _e$stderr === void 0 ? void 0 : _e$stderr.includes('not a developer tool or in PATH')) {
throw new (_XDLError().default)('SIMCTL_NOT_AVAILABLE', `You may need to run ${_chalk().default.bold('sudo xcode-select -s /Applications/Xcode.app')} and try again.`);
}
throw e;
}
}
async function xcrunWithLogging(args, options) {
try {
return await xcrunAsync(args, options);
} catch (e) {
_Logger().default.global.error(`Error running \`xcrun ${args.join(' ')}\`: ${e.stderr || e.message}`);
throw e;
}
}
//# sourceMappingURL=__sourcemaps__/SimControl.js.map