@react-native-community/cli-platform-ios
Version:
This package is part of the [React Native CLI](../../README.md). It contains commands for managing iOS part of React Native app.
396 lines (388 loc) • 17 kB
JavaScript
;
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 _buildOptions = require("../buildIOS/buildOptions");
var _getConfiguration = require("../buildIOS/getConfiguration");
var _listIOSDevices = _interopRequireDefault(require("../../tools/listIOSDevices"));
var _prompts = require("../../tools/prompts");
var _getSimulators = _interopRequireDefault(require("../../tools/getSimulators"));
var _getXcodeProjectAndDir = require("../buildIOS/getXcodeProjectAndDir");
var _pods = _interopRequireDefault(require("../../tools/pods"));
var _getArchitecture = _interopRequireDefault(require("../../tools/getArchitecture"));
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) {
var _ctx$project$ios;
_cliTools().link.setPlatform('ios');
let {
packager,
port
} = args;
// check if pods need to be installed
if (((_ctx$project$ios = ctx.project.ios) === null || _ctx$project$ios === void 0 ? void 0 : _ctx$project$ios.automaticPodsInstallation) || args.forcePods) {
var _ctx$project$ios2, _ctx$project$ios3;
const isAppRunningNewArchitecture = ((_ctx$project$ios2 = ctx.project.ios) === null || _ctx$project$ios2 === void 0 ? void 0 : _ctx$project$ios2.sourceDir) ? await (0, _getArchitecture.default)((_ctx$project$ios3 = ctx.project.ios) === null || _ctx$project$ios3 === void 0 ? void 0 : _ctx$project$ios3.sourceDir) : undefined;
await (0, _pods.default)(ctx.root, ctx.dependencies, {
forceInstall: args.forcePods,
newArchEnabled: isAppRunningNewArchitecture
});
}
if (packager) {
const {
port: newPort,
startPackager
} = await (0, _cliTools().findDevServerPort)(port, ctx.root);
if (startPackager) {
await (0, _cliTools().startServerInNewWindow)(newPort, ctx.root, ctx.reactNativePath, args.terminal);
}
}
if (ctx.reactNativeVersion !== 'unknown') {
_cliTools().link.setVersion(ctx.reactNativeVersion);
}
const {
xcodeProject,
sourceDir
} = (0, _getXcodeProjectAndDir.getXcodeProjectAndDir)(ctx.project.ios);
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.');
}
}
const {
mode,
scheme
} = await (0, _getConfiguration.getConfiguration)(xcodeProject, sourceDir, args);
const devices = await (0, _listIOSDevices.default)();
const availableDevices = devices.filter(({
isAvailable
}) => isAvailable === true);
if (availableDevices.length === 0) {
return _cliTools().logger.error('iOS devices or simulators not detected. Install simulators via Xcode or connect a physical iOS device');
}
if (args.listDevices || args.interactive) {
if (args.device || args.udid) {
_cliTools().logger.warn(`Both ${args.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, mode, scheme, args, selectedDevice);
} else {
return runOnDevice(selectedDevice, mode, scheme, xcodeProject, args);
}
}
if (!args.device && !args.udid && !args.simulator) {
const bootedDevices = availableDevices.filter(({
type
}) => type === 'device');
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, mode, scheme, args);
}
_cliTools().logger.info(`Found booted ${booted.map(({
name
}) => name).join(', ')}`);
return runOnBootedDevicesSimulators(mode, scheme, xcodeProject, args, bootedDevices, bootedSimulators);
}
if (args.device && args.udid) {
return _cliTools().logger.error('The `device` and `udid` options are mutually exclusive.');
}
if (args.udid) {
const device = availableDevices.find(d => d.udid === args.udid);
if (!device) {
return _cliTools().logger.error(`Could not find a device with udid: "${_chalk().default.bold(args.udid)}". ${printFoundDevices(availableDevices)}`);
}
if (device.type === 'simulator') {
return runOnSimulator(xcodeProject, mode, scheme, args);
} else {
return runOnDevice(device, mode, scheme, xcodeProject, args);
}
} else if (args.device) {
const physicalDevices = availableDevices.filter(({
type
}) => type !== 'simulator');
const device = matchingDevice(physicalDevices, args.device);
if (device) {
return runOnDevice(device, mode, scheme, xcodeProject, args);
}
} else {
runOnSimulator(xcodeProject, mode, scheme, args);
}
}
async function runOnBootedDevicesSimulators(mode, scheme, xcodeProject, args, devices, simulators) {
for (const device of devices) {
await runOnDevice(device, mode, scheme, xcodeProject, args);
}
for (const simulator of simulators) {
await runOnSimulator(xcodeProject, mode, scheme, args, simulator);
}
}
async function runOnSimulator(xcodeProject, mode, scheme, args, simulator) {
/**
* 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.state !== 'Booted') {
bootSimulator(selectedSimulator);
}
let buildOutput, appPath;
if (!args.binaryPath) {
buildOutput = await (0, _buildProject.buildProject)(xcodeProject, selectedSimulator.udid, mode, scheme, args);
appPath = await getBuildPath(xcodeProject, mode, buildOutput, scheme, args.target);
} 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, mode, 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, mode, scheme, args);
const appPath = await getBuildPath(xcodeProject, mode, buildOutput, scheme, args.target, 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, mode, scheme, args);
appPath = await getBuildPath(xcodeProject, mode, buildOutput, scheme, args.target);
} 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]);
}
async function getTargetPaths(buildSettings, scheme, target) {
const settings = JSON.parse(buildSettings);
const targets = settings.map(({
target: settingsTarget
}) => settingsTarget);
let selectedTarget = targets[0];
if (target) {
if (!targets.includes(target)) {
_cliTools().logger.info(`Target ${_chalk().default.bold(target)} not found for scheme ${_chalk().default.bold(scheme)}, automatically selected target ${_chalk().default.bold(selectedTarget)}`);
} else {
selectedTarget = target;
}
}
// Find app in all building settings - look for WRAPPER_EXTENSION: 'app',
const targetIndex = targets.indexOf(selectedTarget);
const wrapperExtension = settings[targetIndex].buildSettings.WRAPPER_EXTENSION;
if (wrapperExtension === 'app') {
return {
targetBuildDir: settings[targetIndex].buildSettings.TARGET_BUILD_DIR,
executableFolderPath: settings[targetIndex].buildSettings.EXECUTABLE_FOLDER_PATH
};
}
return {};
}
async function getBuildPath(xcodeProject, mode, buildOutput, scheme, target, 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
} = await getTargetPaths(buildSettings, scheme, target);
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: 'npx react-native run-ios --simulator "iPhone SE (2nd generation)"'
}, {
desc: "Run on a connected device, e.g. Max's iPhone",
cmd: 'npx react-native run-ios --device "Max\'s iPhone"'
}, {
desc: 'Run on the AppleTV simulator',
cmd: 'npx react-native run-ios --simulator "Apple TV" --scheme "helloworld-tvOS"'
}],
options: [..._buildOptions.buildOptions, {
name: '--no-packager',
description: 'Do not launch packager while running the app'
}, {
name: '--port <number>',
default: process.env.RCT_METRO_PORT || 8081,
parse: Number
}, {
name: '--terminal <string>',
description: 'Launches the Metro Bundler in a new window using the specified terminal path.',
default: (0, _cliTools().getDefaultUserTerminal)()
}, {
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: '--simulator <string>',
description: 'Explicitly set the simulator to use. Optionally set the iOS version ' + 'between parentheses at the end to match an exact version: ' + '"iPhone 15 (17.0)"'
}, {
name: '--device <string>',
description: 'Explicitly set the device to use by name. The value is not required ' + 'if you have a single device connected.'
}, {
name: '--udid <string>',
description: 'Explicitly set the device to use by UDID'
}]
};
exports.default = _default;
//# sourceMappingURL=index.ts.map