You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
409 lines
13 KiB
409 lines
13 KiB
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.addMediaAsync = addMediaAsync;
|
|
exports.bootAsync = bootAsync;
|
|
exports.captureScreenAsync = captureScreenAsync;
|
|
exports.deleteUnavailableAsync = deleteUnavailableAsync;
|
|
exports.eraseAllAsync = eraseAllAsync;
|
|
exports.eraseAsync = eraseAsync;
|
|
exports.getContainerPathAsync = getContainerPathAsync;
|
|
exports.getDefaultSimulatorDeviceUDIDAsync = getDefaultSimulatorDeviceUDIDAsync;
|
|
exports.installAsync = installAsync;
|
|
exports.isLicenseOutOfDate = isLicenseOutOfDate;
|
|
exports.isXcrunInstalledAsync = isXcrunInstalledAsync;
|
|
exports.listAsync = listAsync;
|
|
exports.listDevicesAsync = listDevicesAsync;
|
|
exports.listSimulatorDevicesAsync = listSimulatorDevicesAsync;
|
|
exports.openBundleIdAsync = openBundleIdAsync;
|
|
exports.openURLAsync = openURLAsync;
|
|
exports.parseXcrunError = parseXcrunError;
|
|
exports.runBootAsync = runBootAsync;
|
|
exports.setAppearanceAsync = setAppearanceAsync;
|
|
exports.shutdownAsync = shutdownAsync;
|
|
exports.simctlAsync = simctlAsync;
|
|
exports.uninstallAsync = uninstallAsync;
|
|
exports.updatePermissionsAsync = updatePermissionsAsync;
|
|
exports.waitForDeviceToBootAsync = waitForDeviceToBootAsync;
|
|
exports.xcrunAsync = xcrunAsync;
|
|
exports.xcrunWithLogging = xcrunWithLogging;
|
|
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 = require("child_process");
|
|
_child_process = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _path() {
|
|
const data = _interopRequireDefault(require("path"));
|
|
_path = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _waitForActionAsync() {
|
|
const data = require("./apple/utils/waitForActionAsync");
|
|
_waitForActionAsync = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _internal() {
|
|
const data = require("./internal");
|
|
_internal = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _profileMethod() {
|
|
const data = require("./utils/profileMethod");
|
|
_profileMethod = function () {
|
|
return data;
|
|
};
|
|
return data;
|
|
}
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
async function getDefaultSimulatorDeviceUDIDAsync() {
|
|
try {
|
|
const {
|
|
stdout: defaultDeviceUDID
|
|
} = await (0, _spawnAsync().default)('defaults', ['read', 'com.apple.iphonesimulator', 'CurrentDeviceUDID']);
|
|
return defaultDeviceUDID.trim();
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the local path for the installed tar.app. Returns null when the app isn't installed.
|
|
*
|
|
* @param props.udid device udid.
|
|
* @param props.bundleIdentifier bundle identifier for app
|
|
* @returns local file path to installed app binary, e.g. '/Users/evanbacon/Library/Developer/CoreSimulator/Devices/EFEEA6EF-E3F5-4EDE-9B72-29EAFA7514AE/data/Containers/Bundle/Application/FA43A0C6-C2AD-442D-B8B1-EAF3E88CF3BF/Exponent-2.21.3.tar.app'
|
|
*/
|
|
async function getContainerPathAsync({
|
|
udid,
|
|
bundleIdentifier
|
|
}) {
|
|
if (_internal().CoreSimulator.isEnabled()) {
|
|
return _internal().CoreSimulator.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 && _error$stderr.match(/No such file or directory/)) {
|
|
return null;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
async function waitForDeviceToBootAsync({
|
|
udid
|
|
}) {
|
|
return (0, _waitForActionAsync().waitForActionAsync)({
|
|
action: () => bootAsync({
|
|
udid
|
|
})
|
|
});
|
|
}
|
|
async function openURLAsync(options) {
|
|
try {
|
|
// Skip logging since this is likely to fail.
|
|
await xcrunAsync(['simctl', 'openurl', deviceUDIDOrBooted(options.udid), options.url]);
|
|
} catch (error) {
|
|
var _error$stderr2;
|
|
if (!((_error$stderr2 = error.stderr) !== null && _error$stderr2 !== void 0 && _error$stderr2.match(/Unable to lookup in current state: Shut/))) {
|
|
throw error;
|
|
}
|
|
// If the device was in a weird in-between state ("Shutting Down" or "Shutdown"), then attempt to reboot it and try again.
|
|
// This can happen when quitting the Simulator app, and immediately pressing `i` to reopen the project.
|
|
|
|
// First boot the simulator
|
|
await runBootAsync({
|
|
udid: deviceUDIDOrBooted(options.udid)
|
|
});
|
|
|
|
// Finally, try again...
|
|
return await openURLAsync(options);
|
|
}
|
|
}
|
|
async function openBundleIdAsync(options) {
|
|
return xcrunAsync(['simctl', 'launch', deviceUDIDOrBooted(options.udid), options.bundleIdentifier]);
|
|
}
|
|
|
|
// This will only boot in headless mode if the Simulator app is not running.
|
|
async function bootAsync({
|
|
udid
|
|
}) {
|
|
if (_internal().CoreSimulator.isEnabled()) {
|
|
const device = await _internal().CoreSimulator.getDeviceInfoAsync({
|
|
udid
|
|
}).catch(() => null);
|
|
if ((device === null || device === void 0 ? void 0 : device.state) === 'Booted') {
|
|
return device;
|
|
}
|
|
await runBootAsync({
|
|
udid
|
|
});
|
|
return await (0, _profileMethod().profileMethod)(_internal().CoreSimulator.getDeviceInfoAsync)({
|
|
udid
|
|
});
|
|
}
|
|
|
|
// TODO: Deprecate
|
|
await runBootAsync({
|
|
udid
|
|
});
|
|
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 runBootAsync({
|
|
udid
|
|
}) {
|
|
try {
|
|
// Skip logging since this is likely to fail.
|
|
await xcrunAsync(['simctl', 'boot', udid]);
|
|
} catch (error) {
|
|
var _error$stderr3;
|
|
if (!((_error$stderr3 = error.stderr) !== null && _error$stderr3 !== void 0 && _error$stderr3.match(/Unable to boot device in current state: Booted/))) {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
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')) {
|
|
_internal().Logger.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;
|
|
}
|
|
async function listSimulatorDevicesAsync() {
|
|
if (_internal().CoreSimulator.isEnabled()) {
|
|
return _internal().CoreSimulator.listDevicesAsync();
|
|
}
|
|
const simulatorDeviceInfo = await listAsync('devices');
|
|
return Object.values(simulatorDeviceInfo.devices).reduce((prev, runtime) => {
|
|
return prev.concat(runtime);
|
|
}, []);
|
|
}
|
|
|
|
/**
|
|
* Get a list of all connected devices.
|
|
*/
|
|
async function listDevicesAsync() {
|
|
if (_internal().AppleDevice.isEnabled()) {
|
|
const results = await _internal().AppleDevice.getConnectedDevices();
|
|
// TODO: Add support for osType (ipad, watchos, etc)
|
|
return results.map(device => {
|
|
var _ref, _device$DeviceName;
|
|
return {
|
|
// TODO: Better name
|
|
name: (_ref = (_device$DeviceName = device.DeviceName) !== null && _device$DeviceName !== void 0 ? _device$DeviceName : device.ProductType) !== null && _ref !== void 0 ? _ref : 'unknown ios device',
|
|
model: device.ProductType,
|
|
osVersion: device.ProductVersion,
|
|
deviceType: 'device',
|
|
udid: device.UniqueDeviceID
|
|
};
|
|
});
|
|
}
|
|
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 && _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';
|
|
}
|
|
function isLicenseOutOfDate(text) {
|
|
if (!text) {
|
|
return false;
|
|
}
|
|
const lower = text.toLowerCase();
|
|
return lower.includes('xcode') && lower.includes('license');
|
|
}
|
|
async function isXcrunInstalledAsync() {
|
|
try {
|
|
(0, _child_process().execSync)('xcrun --version', {
|
|
stdio: 'ignore'
|
|
});
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
async function xcrunAsync(args, options) {
|
|
_internal().Logger.global.debug('Running: xcrun ' + args.join(' '));
|
|
try {
|
|
return await (0, _spawnAsync().default)('xcrun', args, options);
|
|
} catch (e) {
|
|
throw parseXcrunError(e);
|
|
}
|
|
}
|
|
function parseXcrunError(e) {
|
|
var _e$stderr;
|
|
if (isLicenseOutOfDate(e.stdout) || isLicenseOutOfDate(e.stderr)) {
|
|
return new (_internal().XDLError)('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 && _e$stderr.includes('not a developer tool or in PATH')) {
|
|
return new (_internal().XDLError)('SIMCTL_NOT_AVAILABLE', `You may need to run ${_chalk().default.bold('sudo xcode-select -s /Applications/Xcode.app')} and try again.`);
|
|
}
|
|
// Attempt to craft a better error message...
|
|
if (Array.isArray(e.output)) {
|
|
e.message += '\n' + e.output.join('\n').trim();
|
|
} else if (e.stderr) {
|
|
e.message += '\n' + e.stderr;
|
|
}
|
|
return e;
|
|
}
|
|
async function xcrunWithLogging(args, options) {
|
|
try {
|
|
return await xcrunAsync(args, options);
|
|
} catch (e) {
|
|
_internal().Logger.global.error(`Error running \`xcrun ${args.join(' ')}\`: ${e.stderr || e.message}`);
|
|
throw e;
|
|
}
|
|
}
|
|
//# sourceMappingURL=SimControl.js.map
|