379 lines
16 KiB
JavaScript
379 lines
16 KiB
JavaScript
|
"use strict";
|
||
|
|
||
|
Object.defineProperty(exports, "__esModule", {
|
||
|
value: true
|
||
|
});
|
||
|
exports.default = void 0;
|
||
|
function _child_process() {
|
||
|
const data = _interopRequireDefault(require("child_process"));
|
||
|
_child_process = function () {
|
||
|
return data;
|
||
|
};
|
||
|
return data;
|
||
|
}
|
||
|
function _path() {
|
||
|
const data = _interopRequireDefault(require("path"));
|
||
|
_path = function () {
|
||
|
return data;
|
||
|
};
|
||
|
return data;
|
||
|
}
|
||
|
function _fs() {
|
||
|
const data = _interopRequireDefault(require("fs"));
|
||
|
_fs = function () {
|
||
|
return data;
|
||
|
};
|
||
|
return data;
|
||
|
}
|
||
|
function _chalk() {
|
||
|
const data = _interopRequireDefault(require("chalk"));
|
||
|
_chalk = function () {
|
||
|
return data;
|
||
|
};
|
||
|
return data;
|
||
|
}
|
||
|
var _getDestinationSimulator = require("../../tools/getDestinationSimulator");
|
||
|
function _cliTools() {
|
||
|
const data = require("@react-native-community/cli-tools");
|
||
|
_cliTools = function () {
|
||
|
return data;
|
||
|
};
|
||
|
return data;
|
||
|
}
|
||
|
var _buildProject = require("../buildIOS/buildProject");
|
||
|
var _buildIOS = require("../buildIOS");
|
||
|
var _listIOSDevices = _interopRequireDefault(require("../../tools/listIOSDevices"));
|
||
|
var _checkIfConfigurationExists = require("../../tools/checkIfConfigurationExists");
|
||
|
var _getProjectInfo = require("../../tools/getProjectInfo");
|
||
|
var _getConfigurationScheme = require("../../tools/getConfigurationScheme");
|
||
|
var _selectFromInteractiveMode = require("../../tools/selectFromInteractiveMode");
|
||
|
var _prompts = require("../../tools/prompts");
|
||
|
var _getSimulators = _interopRequireDefault(require("../../tools/getSimulators"));
|
||
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||
|
/**
|
||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||
|
*
|
||
|
* This source code is licensed under the MIT license found in the
|
||
|
* LICENSE file in the root directory of this source tree.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
async function runIOS(_, ctx, args) {
|
||
|
_cliTools().link.setPlatform('ios');
|
||
|
if (ctx.reactNativeVersion !== 'unknown') {
|
||
|
_cliTools().link.setVersion(ctx.reactNativeVersion);
|
||
|
}
|
||
|
if (!ctx.project.ios) {
|
||
|
throw new (_cliTools().CLIError)('iOS project folder not found. Are you sure this is a React Native project?');
|
||
|
}
|
||
|
const {
|
||
|
xcodeProject,
|
||
|
sourceDir
|
||
|
} = ctx.project.ios;
|
||
|
if (!xcodeProject) {
|
||
|
throw new (_cliTools().CLIError)(`Could not find Xcode project files in "${sourceDir}" folder`);
|
||
|
}
|
||
|
process.chdir(sourceDir);
|
||
|
if (args.binaryPath) {
|
||
|
args.binaryPath = _path().default.isAbsolute(args.binaryPath) ? args.binaryPath : _path().default.join(ctx.root, args.binaryPath);
|
||
|
if (!_fs().default.existsSync(args.binaryPath)) {
|
||
|
throw new (_cliTools().CLIError)('binary-path was specified, but the file was not found.');
|
||
|
}
|
||
|
}
|
||
|
if (args.configuration) {
|
||
|
_cliTools().logger.warn('--configuration has been deprecated. Use --mode instead.');
|
||
|
_cliTools().logger.warn('Parameters were automatically reassigned to --mode on this run.');
|
||
|
args.mode = args.configuration;
|
||
|
}
|
||
|
const projectInfo = (0, _getProjectInfo.getProjectInfo)();
|
||
|
if (args.mode) {
|
||
|
(0, _checkIfConfigurationExists.checkIfConfigurationExists)(projectInfo, args.mode);
|
||
|
}
|
||
|
const inferredSchemeName = _path().default.basename(xcodeProject.name, _path().default.extname(xcodeProject.name));
|
||
|
let scheme = args.scheme || inferredSchemeName;
|
||
|
let mode = args.mode;
|
||
|
if (args.interactive) {
|
||
|
const selection = await (0, _selectFromInteractiveMode.selectFromInteractiveMode)({
|
||
|
scheme,
|
||
|
mode
|
||
|
});
|
||
|
if (selection.scheme) {
|
||
|
scheme = selection.scheme;
|
||
|
}
|
||
|
if (selection.mode) {
|
||
|
mode = selection.mode;
|
||
|
}
|
||
|
}
|
||
|
const modifiedArgs = {
|
||
|
...args,
|
||
|
scheme,
|
||
|
mode
|
||
|
};
|
||
|
modifiedArgs.mode = (0, _getConfigurationScheme.getConfigurationScheme)({
|
||
|
scheme: modifiedArgs.scheme,
|
||
|
mode: modifiedArgs.mode
|
||
|
}, sourceDir);
|
||
|
_cliTools().logger.info(`Found Xcode ${xcodeProject.isWorkspace ? 'workspace' : 'project'} "${_chalk().default.bold(xcodeProject.name)}"`);
|
||
|
const availableDevices = await (0, _listIOSDevices.default)();
|
||
|
if (modifiedArgs.listDevices || modifiedArgs.interactive) {
|
||
|
if (modifiedArgs.device || modifiedArgs.udid) {
|
||
|
_cliTools().logger.warn(`Both ${modifiedArgs.device ? 'device' : 'udid'} and "list-devices" parameters were passed to "run" command. We will list available devices and let you choose from one.`);
|
||
|
}
|
||
|
const selectedDevice = await (0, _prompts.promptForDeviceSelection)(availableDevices);
|
||
|
if (!selectedDevice) {
|
||
|
throw new (_cliTools().CLIError)(`Failed to select device, please try to run app without ${args.listDevices ? 'list-devices' : 'interactive'} command.`);
|
||
|
}
|
||
|
if (selectedDevice.type === 'simulator') {
|
||
|
return runOnSimulator(xcodeProject, scheme, modifiedArgs, selectedDevice);
|
||
|
} else {
|
||
|
return runOnDevice(selectedDevice, scheme, xcodeProject, modifiedArgs);
|
||
|
}
|
||
|
}
|
||
|
if (!modifiedArgs.device && !modifiedArgs.udid && !modifiedArgs.simulator) {
|
||
|
const bootedDevices = availableDevices.filter(({
|
||
|
type,
|
||
|
isAvailable
|
||
|
}) => type === 'device' && isAvailable);
|
||
|
const simulators = (0, _getSimulators.default)();
|
||
|
const bootedSimulators = Object.keys(simulators.devices).map(key => simulators.devices[key]).reduce((acc, val) => acc.concat(val), []).filter(({
|
||
|
state
|
||
|
}) => state === 'Booted');
|
||
|
const booted = [...bootedDevices, ...bootedSimulators];
|
||
|
if (booted.length === 0) {
|
||
|
_cliTools().logger.info('No booted devices or simulators found. Launching first available simulator...');
|
||
|
return runOnSimulator(xcodeProject, scheme, modifiedArgs);
|
||
|
}
|
||
|
_cliTools().logger.info(`Found booted ${booted.map(({
|
||
|
name
|
||
|
}) => name).join(', ')}`);
|
||
|
return runOnBootedDevicesSimulators(scheme, xcodeProject, modifiedArgs, bootedDevices, bootedSimulators);
|
||
|
}
|
||
|
if (modifiedArgs.device && modifiedArgs.udid) {
|
||
|
return _cliTools().logger.error('The `device` and `udid` options are mutually exclusive.');
|
||
|
}
|
||
|
if (modifiedArgs.udid) {
|
||
|
const device = availableDevices.find(d => d.udid === modifiedArgs.udid);
|
||
|
if (!device) {
|
||
|
return _cliTools().logger.error(`Could not find a device with udid: "${_chalk().default.bold(modifiedArgs.udid)}". ${printFoundDevices(availableDevices)}`);
|
||
|
}
|
||
|
if (device.type === 'simulator') {
|
||
|
return runOnSimulator(xcodeProject, scheme, modifiedArgs);
|
||
|
} else {
|
||
|
return runOnDevice(device, scheme, xcodeProject, modifiedArgs);
|
||
|
}
|
||
|
} else if (modifiedArgs.device) {
|
||
|
const physicalDevices = availableDevices.filter(({
|
||
|
type
|
||
|
}) => type !== 'simulator');
|
||
|
const device = matchingDevice(physicalDevices, modifiedArgs.device);
|
||
|
if (device) {
|
||
|
return runOnDevice(device, scheme, xcodeProject, modifiedArgs);
|
||
|
}
|
||
|
} else {
|
||
|
runOnSimulator(xcodeProject, scheme, modifiedArgs);
|
||
|
}
|
||
|
}
|
||
|
async function runOnBootedDevicesSimulators(scheme, xcodeProject, args, devices, simulators) {
|
||
|
for (const device of devices) {
|
||
|
await runOnDevice(device, scheme, xcodeProject, args);
|
||
|
}
|
||
|
for (const simulator of simulators) {
|
||
|
await runOnSimulator(xcodeProject, scheme, args, simulator);
|
||
|
}
|
||
|
}
|
||
|
async function runOnSimulator(xcodeProject, scheme, args, simulator) {
|
||
|
// let selectedSimulator;
|
||
|
/**
|
||
|
* If provided simulator does not exist, try simulators in following order
|
||
|
* - iPhone 14
|
||
|
* - iPhone 13
|
||
|
* - iPhone 12
|
||
|
* - iPhone 11
|
||
|
*/
|
||
|
|
||
|
let selectedSimulator;
|
||
|
if (simulator) {
|
||
|
selectedSimulator = simulator;
|
||
|
} else {
|
||
|
const fallbackSimulators = ['iPhone 14', 'iPhone 13', 'iPhone 12', 'iPhone 11'];
|
||
|
selectedSimulator = (0, _getDestinationSimulator.getDestinationSimulator)(args, fallbackSimulators);
|
||
|
}
|
||
|
if (!selectedSimulator) {
|
||
|
throw new (_cliTools().CLIError)(`No simulator available with ${args.simulator ? `name "${args.simulator}"` : `udid "${args.udid}"`}`);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Booting simulator through `xcrun simctl boot` will boot it in the `headless` mode
|
||
|
* (running in the background).
|
||
|
*
|
||
|
* In order for user to see the app and the simulator itself, we have to make sure
|
||
|
* that the Simulator.app is running.
|
||
|
*
|
||
|
* We also pass it `-CurrentDeviceUDID` so that when we launch it for the first time,
|
||
|
* it will not boot the "default" device, but the one we set. If the app is already running,
|
||
|
* this flag has no effect.
|
||
|
*/
|
||
|
const activeDeveloperDir = _child_process().default.execFileSync('xcode-select', ['-p'], {
|
||
|
encoding: 'utf8'
|
||
|
}).trim();
|
||
|
_child_process().default.execFileSync('open', [`${activeDeveloperDir}/Applications/Simulator.app`, '--args', '-CurrentDeviceUDID', selectedSimulator.udid]);
|
||
|
if (!selectedSimulator.booted) {
|
||
|
bootSimulator(selectedSimulator);
|
||
|
}
|
||
|
let buildOutput, appPath;
|
||
|
if (!args.binaryPath) {
|
||
|
buildOutput = await (0, _buildProject.buildProject)(xcodeProject, selectedSimulator.udid, scheme, args);
|
||
|
appPath = getBuildPath(xcodeProject, args.mode || args.configuration, buildOutput, scheme);
|
||
|
} else {
|
||
|
appPath = args.binaryPath;
|
||
|
}
|
||
|
_cliTools().logger.info(`Installing "${_chalk().default.bold(appPath)} on ${selectedSimulator.name}"`);
|
||
|
_child_process().default.spawnSync('xcrun', ['simctl', 'install', selectedSimulator.udid, appPath], {
|
||
|
stdio: 'inherit'
|
||
|
});
|
||
|
const bundleID = _child_process().default.execFileSync('/usr/libexec/PlistBuddy', ['-c', 'Print:CFBundleIdentifier', _path().default.join(appPath, 'Info.plist')], {
|
||
|
encoding: 'utf8'
|
||
|
}).trim();
|
||
|
_cliTools().logger.info(`Launching "${_chalk().default.bold(bundleID)}"`);
|
||
|
const result = _child_process().default.spawnSync('xcrun', ['simctl', 'launch', selectedSimulator.udid, bundleID]);
|
||
|
if (result.status === 0) {
|
||
|
_cliTools().logger.success('Successfully launched the app on the simulator');
|
||
|
} else {
|
||
|
_cliTools().logger.error('Failed to launch the app on simulator', result.stderr.toString());
|
||
|
}
|
||
|
}
|
||
|
async function runOnDevice(selectedDevice, scheme, xcodeProject, args) {
|
||
|
if (args.binaryPath && selectedDevice.type === 'catalyst') {
|
||
|
throw new (_cliTools().CLIError)('binary-path was specified for catalyst device, which is not supported.');
|
||
|
}
|
||
|
const isIOSDeployInstalled = _child_process().default.spawnSync('ios-deploy', ['--version'], {
|
||
|
encoding: 'utf8'
|
||
|
});
|
||
|
if (isIOSDeployInstalled.error) {
|
||
|
throw new (_cliTools().CLIError)(`Failed to install the app on the device because we couldn't execute the "ios-deploy" command. Please install it by running "${_chalk().default.bold('brew install ios-deploy')}" and try again.`);
|
||
|
}
|
||
|
if (selectedDevice.type === 'catalyst') {
|
||
|
const buildOutput = await (0, _buildProject.buildProject)(xcodeProject, selectedDevice.udid, scheme, args);
|
||
|
const appPath = getBuildPath(xcodeProject, args.mode || args.configuration, buildOutput, scheme, true);
|
||
|
const appProcess = _child_process().default.spawn(`${appPath}/${scheme}`, [], {
|
||
|
detached: true,
|
||
|
stdio: 'ignore'
|
||
|
});
|
||
|
appProcess.unref();
|
||
|
} else {
|
||
|
let buildOutput, appPath;
|
||
|
if (!args.binaryPath) {
|
||
|
buildOutput = await (0, _buildProject.buildProject)(xcodeProject, selectedDevice.udid, scheme, args);
|
||
|
appPath = getBuildPath(xcodeProject, args.mode || args.configuration, buildOutput, scheme);
|
||
|
} else {
|
||
|
appPath = args.binaryPath;
|
||
|
}
|
||
|
const iosDeployInstallArgs = ['--bundle', appPath, '--id', selectedDevice.udid, '--justlaunch'];
|
||
|
_cliTools().logger.info(`Installing and launching your app on ${selectedDevice.name}`);
|
||
|
const iosDeployOutput = _child_process().default.spawnSync('ios-deploy', iosDeployInstallArgs, {
|
||
|
encoding: 'utf8'
|
||
|
});
|
||
|
if (iosDeployOutput.error) {
|
||
|
throw new (_cliTools().CLIError)(`Failed to install the app on the device. We've encountered an error in "ios-deploy" command: ${iosDeployOutput.error.message}`);
|
||
|
}
|
||
|
}
|
||
|
return _cliTools().logger.success('Installed the app on the device.');
|
||
|
}
|
||
|
function bootSimulator(selectedSimulator) {
|
||
|
const simulatorFullName = formattedDeviceName(selectedSimulator);
|
||
|
_cliTools().logger.info(`Launching ${simulatorFullName}`);
|
||
|
_child_process().default.spawnSync('xcrun', ['simctl', 'boot', selectedSimulator.udid]);
|
||
|
}
|
||
|
function getTargetPaths(buildSettings) {
|
||
|
const settings = JSON.parse(buildSettings);
|
||
|
|
||
|
// Find app in all building settings - look for WRAPPER_EXTENSION: 'app',
|
||
|
for (const i in settings) {
|
||
|
const wrapperExtension = settings[i].buildSettings.WRAPPER_EXTENSION;
|
||
|
if (wrapperExtension === 'app') {
|
||
|
return {
|
||
|
targetBuildDir: settings[i].buildSettings.TARGET_BUILD_DIR,
|
||
|
executableFolderPath: settings[i].buildSettings.EXECUTABLE_FOLDER_PATH
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
return {};
|
||
|
}
|
||
|
function getBuildPath(xcodeProject, mode, buildOutput, scheme, isCatalyst = false) {
|
||
|
const buildSettings = _child_process().default.execFileSync('xcodebuild', [xcodeProject.isWorkspace ? '-workspace' : '-project', xcodeProject.name, '-scheme', scheme, '-sdk', getPlatformName(buildOutput), '-configuration', mode, '-showBuildSettings', '-json'], {
|
||
|
encoding: 'utf8'
|
||
|
});
|
||
|
const {
|
||
|
targetBuildDir,
|
||
|
executableFolderPath
|
||
|
} = getTargetPaths(buildSettings);
|
||
|
if (!targetBuildDir) {
|
||
|
throw new (_cliTools().CLIError)('Failed to get the target build directory.');
|
||
|
}
|
||
|
if (!executableFolderPath) {
|
||
|
throw new (_cliTools().CLIError)('Failed to get the app name.');
|
||
|
}
|
||
|
return `${targetBuildDir}${isCatalyst ? '-maccatalyst' : ''}/${executableFolderPath}`;
|
||
|
}
|
||
|
function getPlatformName(buildOutput) {
|
||
|
// Xcode can sometimes escape `=` with a backslash or put the value in quotes
|
||
|
const platformNameMatch = /export PLATFORM_NAME\\?="?(\w+)"?$/m.exec(buildOutput);
|
||
|
if (!platformNameMatch) {
|
||
|
throw new (_cliTools().CLIError)('Couldn\'t find "PLATFORM_NAME" variable in xcodebuild output. Please report this issue and run your project with Xcode instead.');
|
||
|
}
|
||
|
return platformNameMatch[1];
|
||
|
}
|
||
|
function matchingDevice(devices, deviceName) {
|
||
|
if (deviceName === true) {
|
||
|
const firstIOSDevice = devices.find(d => d.type === 'device');
|
||
|
if (firstIOSDevice) {
|
||
|
_cliTools().logger.info(`Using first available device named "${_chalk().default.bold(firstIOSDevice.name)}" due to lack of name supplied.`);
|
||
|
return firstIOSDevice;
|
||
|
} else {
|
||
|
_cliTools().logger.error('No iOS devices connected.');
|
||
|
return undefined;
|
||
|
}
|
||
|
}
|
||
|
const deviceByName = devices.find(device => device.name === deviceName || formattedDeviceName(device) === deviceName);
|
||
|
if (!deviceByName) {
|
||
|
_cliTools().logger.error(`Could not find a device named: "${_chalk().default.bold(String(deviceName))}". ${printFoundDevices(devices)}`);
|
||
|
}
|
||
|
return deviceByName;
|
||
|
}
|
||
|
function formattedDeviceName(simulator) {
|
||
|
return simulator.version ? `${simulator.name} (${simulator.version})` : simulator.name;
|
||
|
}
|
||
|
function printFoundDevices(devices) {
|
||
|
return ['Available devices:', ...devices.map(device => ` - ${device.name} (${device.udid})`)].join('\n');
|
||
|
}
|
||
|
var _default = {
|
||
|
name: 'run-ios',
|
||
|
description: 'builds your app and starts it on iOS simulator',
|
||
|
func: runIOS,
|
||
|
examples: [{
|
||
|
desc: 'Run on a different simulator, e.g. iPhone SE (2nd generation)',
|
||
|
cmd: 'react-native run-ios --simulator "iPhone SE (2nd generation)"'
|
||
|
}, {
|
||
|
desc: "Run on a connected device, e.g. Max's iPhone",
|
||
|
cmd: 'react-native run-ios --device "Max\'s iPhone"'
|
||
|
}, {
|
||
|
desc: 'Run on the AppleTV simulator',
|
||
|
cmd: 'react-native run-ios --simulator "Apple TV" --scheme "helloworld-tvOS"'
|
||
|
}],
|
||
|
options: [..._buildIOS.iosBuildOptions, {
|
||
|
name: '--no-packager',
|
||
|
description: 'Do not launch packager while building'
|
||
|
}, {
|
||
|
name: '--binary-path <string>',
|
||
|
description: 'Path relative to project root where pre-built .app binary lives.'
|
||
|
}, {
|
||
|
name: '--list-devices',
|
||
|
description: 'List all available iOS devices and simulators and let you choose one to run the app. '
|
||
|
}, {
|
||
|
name: '--interactive',
|
||
|
description: 'Explicitly select which scheme and configuration to use before running a build and select device to run the application.'
|
||
|
}]
|
||
|
};
|
||
|
exports.default = _default;
|
||
|
|
||
|
//# sourceMappingURL=index.ts.map
|