"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 _fs() {
  const data = _interopRequireDefault(require("fs"));

  _fs = function () {
    return data;
  };

  return data;
}

function _path() {
  const data = _interopRequireDefault(require("path"));

  _path = function () {
    return data;
  };

  return data;
}

function _chalk() {
  const data = _interopRequireDefault(require("chalk"));

  _chalk = function () {
    return data;
  };

  return data;
}

var _findXcodeProject = _interopRequireDefault(require("./findXcodeProject"));

var _parseIOSDevicesList = _interopRequireDefault(require("./parseIOSDevicesList"));

var _parseXctraceIOSDevicesList = _interopRequireDefault(require("./parseXctraceIOSDevicesList"));

var _findMatchingSimulator = _interopRequireDefault(require("./findMatchingSimulator"));

var _warnAboutManuallyLinkedLibs = _interopRequireDefault(require("../../link/warnAboutManuallyLinkedLibs"));

var _warnAboutPodInstall = _interopRequireDefault(require("../../link/warnAboutPodInstall"));

function _cliTools() {
  const data = require("@react-native-community/cli-tools");

  _cliTools = function () {
    return data;
  };

  return data;
}

function _ora() {
  const data = _interopRequireDefault(require("ora"));

  _ora = function () {
    return data;
  };

  return data;
}

function _execa() {
  const data = _interopRequireDefault(require("execa"));

  _execa = function () {
    return data;
  };

  return data;
}

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.
 *
 */
function runIOS(_, ctx, args) {
  if (!_fs().default.existsSync(args.projectPath)) {
    throw new (_cliTools().CLIError)('iOS project folder not found. Are you sure this is a React Native project?');
  }

  (0, _warnAboutManuallyLinkedLibs.default)(ctx);
  (0, _warnAboutPodInstall.default)(ctx);
  process.chdir(args.projectPath);
  const xcodeProject = (0, _findXcodeProject.default)(_fs().default.readdirSync('.'));

  if (!xcodeProject) {
    throw new (_cliTools().CLIError)(`Could not find Xcode project files in "${args.projectPath}" folder`);
  }

  const inferredSchemeName = _path().default.basename(xcodeProject.name, _path().default.extname(xcodeProject.name));

  const scheme = args.scheme || inferredSchemeName;

  _cliTools().logger.info(`Found Xcode ${xcodeProject.isWorkspace ? 'workspace' : 'project'} "${_chalk().default.bold(xcodeProject.name)}"`); // No need to load all available devices


  if (!args.device && !args.udid) {
    return runOnSimulator(xcodeProject, scheme, args);
  }

  if (args.device && args.udid) {
    return _cliTools().logger.error('The `device` and `udid` options are mutually exclusive.');
  }

  let devices;

  try {
    const out = _execa().default.sync('xcrun', ['xctrace', 'list', 'devices']);

    devices = (0, _parseXctraceIOSDevicesList.default)( // Xcode 12.5 introduced a change to output the list to stdout instead of stderr
    out.stderr === '' ? out.stdout : out.stderr);
  } catch (e) {
    _cliTools().logger.warn('Support for Xcode 11 and older is deprecated. Please upgrade to Xcode 12.');

    devices = (0, _parseIOSDevicesList.default)(_execa().default.sync('xcrun', ['instruments', '-s']).stdout);
  }

  if (args.udid) {
    const device = devices.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(devices)}`);
    }

    if (device.type === 'simulator') {
      return runOnSimulator(xcodeProject, scheme, args);
    } else {
      return runOnDevice(device, scheme, xcodeProject, args);
    }
  } else {
    const physicalDevices = devices.filter(d => d.type !== 'simulator');
    const device = matchingDevice(physicalDevices, args.device);

    if (device) {
      return runOnDevice(device, scheme, xcodeProject, args);
    }
  }
}

async function runOnSimulator(xcodeProject, scheme, args) {
  let simulators;

  try {
    simulators = JSON.parse(_child_process().default.execFileSync('xcrun', ['simctl', 'list', '--json', 'devices'], {
      encoding: 'utf8'
    }));
  } catch (error) {
    throw new (_cliTools().CLIError)('Could not get the simulator list from Xcode. Please open Xcode and try running project directly from there to resolve the remaining issues.', error);
  }
  /**
   * If provided simulator does not exist, try simulators in following order
   * - iPhone 12
   * - iPhone 11
   * - iPhone 8
   */


  const fallbackSimulators = ['iPhone 12', 'iPhone 11', 'iPhone 8'];
  const selectedSimulator = fallbackSimulators.reduce((simulator, fallback) => {
    return simulator || (0, _findMatchingSimulator.default)(simulators, {
      simulator: fallback
    });
  }, (0, _findMatchingSimulator.default)(simulators, args));

  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);
  }

  const buildOutput = await buildProject(xcodeProject, selectedSimulator.udid, scheme, args);
  const appPath = getBuildPath(xcodeProject, args.configuration, buildOutput, scheme);

  _cliTools().logger.info(`Installing "${_chalk().default.bold(appPath)}"`);

  _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);
  }
}

async function runOnDevice(selectedDevice, scheme, xcodeProject, args) {
  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('npm install -g ios-deploy')}" and try again.`);
  }

  const buildOutput = await buildProject(xcodeProject, selectedDevice.udid, scheme, args);
  const iosDeployInstallArgs = ['--bundle', getBuildPath(xcodeProject, args.configuration, buildOutput, scheme), '--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 buildProject(xcodeProject, udid, scheme, args) {
  return new Promise((resolve, reject) => {
    const xcodebuildArgs = [xcodeProject.isWorkspace ? '-workspace' : '-project', xcodeProject.name, '-configuration', args.configuration, '-scheme', scheme, '-destination', `id=${udid}`];
    const loader = (0, _ora().default)();

    _cliTools().logger.info(`Building ${_chalk().default.dim(`(using "xcodebuild ${xcodebuildArgs.join(' ')}")`)}`);

    let xcpretty;

    if (!args.verbose) {
      xcpretty = xcprettyAvailable() && _child_process().default.spawn('xcpretty', [], {
        stdio: ['pipe', process.stdout, process.stderr]
      });
    }

    const buildProcess = _child_process().default.spawn('xcodebuild', xcodebuildArgs, getProcessOptions(args));

    let buildOutput = '';
    let errorOutput = '';
    buildProcess.stdout.on('data', data => {
      const stringData = data.toString();
      buildOutput += stringData;

      if (xcpretty) {
        xcpretty.stdin.write(data);
      } else {
        if (_cliTools().logger.isVerbose()) {
          _cliTools().logger.debug(stringData);
        } else {
          loader.start(`Building the app${'.'.repeat(buildOutput.length % 10)}`);
        }
      }
    });
    buildProcess.stderr.on('data', data => {
      errorOutput += data;
    });
    buildProcess.on('close', code => {
      if (xcpretty) {
        xcpretty.stdin.end();
      } else {
        loader.stop();
      }

      if (code !== 0) {
        reject(new (_cliTools().CLIError)(`
            Failed to build iOS project.

            We ran "xcodebuild" command but it exited with error code ${code}. To debug build
            logs further, consider building your app with Xcode.app, by opening
            ${xcodeProject.name}.
          `, buildOutput + '\n' + errorOutput));
        return;
      }

      _cliTools().logger.success('Successfully built the app');

      resolve(buildOutput);
    });
  });
}

function bootSimulator(selectedSimulator) {
  const simulatorFullName = formattedDeviceName(selectedSimulator);

  _cliTools().logger.info(`Launching ${simulatorFullName}`);

  try {
    _child_process().default.spawnSync('xcrun', ['instruments', '-w', selectedSimulator.udid]);
  } catch (_ignored) {// instruments always fail with 255 because it expects more arguments,
    // but we want it to only launch the simulator
  }
}

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, configuration, buildOutput, scheme) {
  const buildSettings = _child_process().default.execFileSync('xcodebuild', [xcodeProject.isWorkspace ? '-workspace' : '-project', xcodeProject.name, '-scheme', scheme, '-sdk', getPlatformName(buildOutput), '-configuration', configuration, '-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}/${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 xcprettyAvailable() {
  try {
    _child_process().default.execSync('xcpretty --version', {
      stdio: [0, 'pipe', 'ignore']
    });
  } catch (error) {
    return false;
  }

  return true;
}

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');
}

function getProcessOptions({
  packager,
  terminal,
  port
}) {
  if (packager) {
    return {
      env: { ...process.env,
        RCT_TERMINAL: terminal,
        RCT_METRO_PORT: port.toString()
      }
    };
  }

  return {
    env: { ...process.env,
      RCT_TERMINAL: terminal,
      RCT_NO_LAUNCH_PACKAGER: 'true'
    }
  };
}

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 (1st generation)',
    cmd: 'react-native run-ios --simulator "iPhone SE (1st generation)"'
  }, {
    desc: 'Pass a non-standard location of iOS directory',
    cmd: 'react-native run-ios --project-path "./app/ios"'
  }, {
    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: [{
    name: '--simulator <string>',
    description: 'Explicitly set simulator to use. Optionally include iOS version between ' + 'parenthesis at the end to match an exact version: "iPhone 6 (10.0)"',
    default: 'iPhone 12'
  }, {
    name: '--configuration <string>',
    description: 'Explicitly set the scheme configuration to use',
    default: 'Debug'
  }, {
    name: '--scheme <string>',
    description: 'Explicitly set Xcode scheme to use'
  }, {
    name: '--project-path <string>',
    description: 'Path relative to project root where the Xcode project ' + '(.xcodeproj) lives.',
    default: 'ios'
  }, {
    name: '--device [string]',
    description: 'Explicitly set device to use by name.  The value is not required if you have a single device connected.'
  }, {
    name: '--udid <string>',
    description: 'Explicitly set device to use by udid'
  }, {
    name: '--no-packager',
    description: 'Do not launch packager while building'
  }, {
    name: '--verbose',
    description: 'Do not use xcpretty even if installed'
  }, {
    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: _cliTools().getDefaultUserTerminal
  }]
};
exports.default = _default;

//# sourceMappingURL=index.js.map