"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = _default;

function _xdl() {
  const data = require("@expo/xdl");

  _xdl = function () {
    return data;
  };

  return data;
}

function _jsonFile() {
  const data = _interopRequireDefault(require("@expo/json-file"));

  _jsonFile = function () {
    return data;
  };

  return data;
}

function ConfigUtils() {
  const data = _interopRequireWildcard(require("@expo/config"));

  ConfigUtils = function () {
    return data;
  };

  return data;
}

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

  _chalk = function () {
    return data;
  };

  return data;
}

function _semver() {
  const data = _interopRequireDefault(require("semver"));

  _semver = function () {
    return data;
  };

  return data;
}

function _lodash() {
  const data = _interopRequireDefault(require("lodash"));

  _lodash = function () {
    return data;
  };

  return data;
}

function PackageManager() {
  const data = _interopRequireWildcard(require("../PackageManager"));

  PackageManager = function () {
    return data;
  };

  return data;
}

function _CommandError() {
  const data = _interopRequireDefault(require("../CommandError"));

  _CommandError = function () {
    return data;
  };

  return data;
}

function _prompt() {
  const data = _interopRequireDefault(require("../prompt"));

  _prompt = function () {
    return data;
  };

  return data;
}

function _log() {
  const data = _interopRequireDefault(require("../log"));

  _log = function () {
    return data;
  };

  return data;
}

function _ProjectUtils() {
  const data = require("./utils/ProjectUtils");

  _ProjectUtils = function () {
    return data;
  };

  return data;
}

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function maybeFormatSdkVersion(sdkVersionString) {
  if (typeof sdkVersionString !== 'string') {
    return sdkVersionString;
  } // semver.valid type doesn't accept null, so need to ensure we pass a string


  return _semver().default.valid(_semver().default.coerce(sdkVersionString) || '');
}

/**
 * Produce a list of dependencies used by the project that need to be updated
 */
async function getUpdatedDependenciesAsync(projectRoot, workflow, targetSdkVersion) {
  let result = {}; // Get the updated version for any bundled modules

  let {
    exp,
    pkg
  } = await ConfigUtils().readConfigJsonAsync(projectRoot);
  let bundledNativeModules = await _jsonFile().default.readAsync(ConfigUtils().resolveModule('expo/bundledNativeModules.json', projectRoot, exp)); // Smoosh regular and dev dependencies together for now

  let dependencies = { ...pkg.dependencies,
    ...pkg.devDependencies
  };
  Object.keys(bundledNativeModules).forEach(name => {
    if (dependencies[name]) {
      result[name] = bundledNativeModules[name];
    }
  });

  if (!targetSdkVersion) {
    _log().default.warn(`Supported React, React Native, and React DOM versions are unknown because we don't have version information for the target SDK, please update them manually.`);

    return result;
  }

  if (dependencies['jest-expo']) {
    result['jest-expo'] = `^${exp.sdkVersion}`;
  } // Get the supported react/react-native/react-dom versions and other related packages


  if (workflow === 'managed' || dependencies['expokit']) {
    result['react-native'] = `https://github.com/expo/react-native/archive/${targetSdkVersion.expoReactNativeTag}.tar.gz`;
  } else {
    result['react-native'] = targetSdkVersion.facebookReactNativeVersion;
  } // React version apparently is optional in SDK version data


  if (targetSdkVersion.facebookReactVersion) {
    result['react'] = targetSdkVersion.facebookReactVersion; // react-dom version is always the same as the react version

    if (dependencies['react-dom']) {
      result['react-dom'] = targetSdkVersion.facebookReactVersion;
    }
  } // Update any related packages


  if (targetSdkVersion.relatedPackages) {
    Object.keys(targetSdkVersion.relatedPackages).forEach(name => {
      if (dependencies[name]) {
        result[name] = targetSdkVersion.relatedPackages[name];
      }
    });
  }

  return result;
}

async function upgradeAsync(requestedSdkVersion, options) {
  let {
    projectRoot,
    workflow
  } = await (0, _ProjectUtils().findProjectRootAsync)(process.cwd());
  let {
    exp,
    pkg
  } = await ConfigUtils().readConfigJsonAsync(projectRoot);
  let isGitStatusClean = await (0, _ProjectUtils().validateGitStatusAsync)();

  _log().default.newLine(); // Give people a chance to bail out if git working tree is dirty


  if (!isGitStatusClean) {
    let answer = await (0, _prompt().default)({
      type: 'confirm',
      name: 'ignoreDirtyGit',
      message: `Would you like to proceed?`
    });

    if (!answer.ignoreDirtyGit) {
      return;
    }

    _log().default.newLine();
  } // Give people a chance to bail out if they're updating from a super old version because YMMV


  if (!_xdl().Versions.gteSdkVersion(exp, '33.0.0')) {
    let answer = await (0, _prompt().default)({
      type: 'confirm',
      name: 'attemptOldUpdate',
      message: `This command works best on SDK 33 and higher. We can try updating for you, but you will likely need to follow up with the instructions from https://docs.expo.io/versions/latest/workflow/upgrading-expo-sdk-walkthrough/. Continue anyways?`
    });

    if (!answer.attemptOldUpdate) {
      return;
    }

    _log().default.newLine();
  } // Can't upgrade if we don't have a SDK version (tapping on head meme)


  if (!exp.sdkVersion) {
    if (workflow === 'bare') {
      _log().default.error('This command only works for bare workflow projects that also have the expo package installed and sdkVersion configured in app.json.');

      throw new (_CommandError().default)('SDK_VERSION_REQUIRED_FOR_UPGRADE_COMMAND_IN_BARE');
    } else {
      _log().default.error('No sdkVersion field is present in app.json, cannot upgrade project.');

      throw new (_CommandError().default)('SDK_VERSION_REQUIRED_FOR_UPGRADE_COMMAND_IN_MANAGED');
    }
  }

  let currentSdkVersionString = exp.sdkVersion;
  let sdkVersions = await _xdl().Versions.sdkVersionsAsync();
  let latestSdkVersion = await _xdl().Versions.newestSdkVersionAsync();
  let latestSdkVersionString = latestSdkVersion.version;
  let targetSdkVersionString = maybeFormatSdkVersion(requestedSdkVersion) || latestSdkVersion.version;
  let targetSdkVersion = sdkVersions[targetSdkVersionString]; // Maybe bail out early if people are trying to update to the current version

  if (targetSdkVersionString === currentSdkVersionString) {
    let answer = await (0, _prompt().default)({
      type: 'confirm',
      name: 'attemptUpdateAgain',
      message: `You are already using the latest SDK version. Do you want to run the update anyways? This may be useful to ensure that all of your packages are set to the correct version.`
    });

    if (!answer.attemptUpdateAgain) {
      (0, _log().default)('Follow the Expo blog at https://blog.expo.io for new release information!');
      return;
    }
  }

  if (targetSdkVersionString === latestSdkVersionString && currentSdkVersionString !== targetSdkVersionString) {
    let answer = await (0, _prompt().default)({
      type: 'confirm',
      name: 'updateToLatestSdkVersion',
      message: `You are currently using SDK ${currentSdkVersionString}. Would you like to update to the latest version, ${latestSdkVersion.version}?`
    });

    _log().default.newLine();

    if (!answer.updateToLatestSdkVersion) {
      let sdkVersionStringOptions = Object.keys(sdkVersions).filter(v => !_xdl().Versions.gteSdkVersion(exp, v) && _semver().default.gte('33.0.0', v));
      let {
        selectedSdkVersionString
      } = await (0, _prompt().default)({
        type: 'list',
        name: 'selectedSdkVersionString',
        message: 'Choose a SDK version to upgrade to:',
        pageSize: 20,
        choices: sdkVersionStringOptions.map(sdkVersionString => ({
          value: sdkVersionString,
          name: _chalk().default.bold(sdkVersionString)
        }))
      }); // This has to exist because it's based on keys already present in sdkVersions

      targetSdkVersion = sdkVersions[selectedSdkVersionString];
      targetSdkVersionString = selectedSdkVersionString;

      _log().default.newLine();
    }
  } else if (!targetSdkVersion) {
    // If they provide an apparently unsupported sdk version then let people try
    // anyways, maybe we want to use this for testing alpha versions or
    // something...
    let answer = await (0, _prompt().default)({
      type: 'confirm',
      name: 'attemptUnknownUpdate',
      message: `You provided the target SDK version value of ${targetSdkVersionString} which does not seem to exist. But hey, I'm just a program, what do I know. Do you want to try to upgrade to it anyways?`
    });

    if (!answer.attemptUnknownUpdate) {
      return;
    }
  }

  let packageManager = PackageManager().createForProject(projectRoot, {
    npm: options.npm,
    yarn: options.yarn
  });

  _log().default.addNewLineIfNone();

  (0, _log().default)(_chalk().default.underline.bold('Installing the expo package...'));

  _log().default.addNewLineIfNone();

  await packageManager.addAsync(`expo@^${targetSdkVersionString}`);

  _log().default.addNewLineIfNone();

  (0, _log().default)(_chalk().default.underline.bold('Updating sdkVersion in app.json...'));
  await ConfigUtils().writeConfigJsonAsync(projectRoot, {
    sdkVersion: targetSdkVersionString
  });
  (0, _log().default)(_chalk().default.bold.underline('Updating packages to compatible versions (where known)...'));

  _log().default.addNewLineIfNone(); // Get all updated packages


  let updates = await getUpdatedDependenciesAsync(projectRoot, workflow, targetSdkVersion); // Split updated packages by dependencies and devDependencies

  let devDependencies = _lodash().default.pickBy(updates, (_version, name) => _lodash().default.has(pkg.devDependencies, name));

  let devDependenciesAsStringArray = Object.keys(devDependencies).map(name => `${name}@${updates[name]}`);

  let dependencies = _lodash().default.pickBy(updates, (_version, name) => _lodash().default.has(pkg.dependencies, name));

  let dependenciesAsStringArray = Object.keys(dependencies).map(name => `${name}@${updates[name]}`); // Install dev dependencies

  if (devDependenciesAsStringArray.length) {
    await packageManager.addDevAsync(...devDependenciesAsStringArray);
  } // Install dependencies


  if (dependenciesAsStringArray.length) {
    await packageManager.addAsync(...dependenciesAsStringArray);
  }

  _log().default.addNewLineIfNone();

  (0, _log().default)(_chalk().default.underline.bold.green(`Automated upgrade steps complete.`));
  (0, _log().default)(_chalk().default.bold.grey(`...but this doesn't mean everything is done yet!`));

  _log().default.newLine(); // List packages that were updated


  (0, _log().default)(_chalk().default.bold(`The following packages were updated:`));
  (0, _log().default)(_chalk().default.grey.bold([...Object.keys(updates), ...['expo']].join(', ')));

  _log().default.addNewLineIfNone(); // List packages that were not updated


  let allDependencies = { ...pkg.dependencies,
    ...pkg.devDependencies
  };

  let untouchedDependencies = _lodash().default.difference(Object.keys(allDependencies), [...Object.keys(updates), 'expo']);

  if (untouchedDependencies.length) {
    _log().default.addNewLineIfNone();

    (0, _log().default)(_chalk().default.bold(`The following packages were ${_chalk().default.underline('not')} updated. You should check the READMEs for those repositories to determine what version is compatible with your new set of packages:`));
    (0, _log().default)(_chalk().default.grey.bold(untouchedDependencies.join(', ')));

    _log().default.addNewLineIfNone();
  } // Add some basic additional instructions for bare workflow


  if (workflow === 'bare') {
    _log().default.addNewLineIfNone();

    (0, _log().default)(_chalk().default.bold(`It will be necessary to re-build your native projects to compile the updated dependencies. You will need to run ${_chalk().default.grey('pod install')} in your ios directory before re-building the iOS project.`));

    _log().default.addNewLineIfNone();
  }

  if (targetSdkVersion && targetSdkVersion.releaseNoteUrl) {
    (0, _log().default)(`Please refer to the release notes for information on any further required steps to update and information about breaking changes:`);
    (0, _log().default)(_chalk().default.bold(targetSdkVersion.releaseNoteUrl));
  } else {
    _log().default.gray(`Unable to find release notes for ${targetSdkVersionString}, please try to find them on https://blog.expo.io to learn more about other potentially important upgrade steps and breaking changes.`);
  }

  let skippedSdkVersions = _lodash().default.pickBy(sdkVersions, (_data, sdkVersionString) => {
    return _semver().default.lt(sdkVersionString, targetSdkVersionString) && _semver().default.gt(sdkVersionString, currentSdkVersionString);
  });

  let skippedSdkVersionKeys = Object.keys(skippedSdkVersions);

  if (skippedSdkVersionKeys.length) {
    _log().default.newLine();

    let releaseNotesUrls = Object.values(skippedSdkVersions).map(data => data.releaseNoteUrl).filter(releaseNotesUrl => releaseNotesUrl).reverse();

    if (releaseNotesUrls.length === 1) {
      (0, _log().default)(`You should also look at the breaking changes from a release that you skipped:`);
      (0, _log().default)(_chalk().default.bold(`- ${releaseNotesUrls[0]}`));
    } else {
      (0, _log().default)(`In addition to the most recent release notes, you should go over the breaking changes from skipped releases:`);
      releaseNotesUrls.forEach(url => {
        (0, _log().default)(_chalk().default.bold(`- ${url}`));
      });
    }
  }

  _log().default.addNewLineIfNone();
}

function _default(program) {
  program.command('upgrade [targetSdkVersion]').alias('update').option('--npm', 'Use npm to install dependencies. (default when package-lock.json exists)').option('--yarn', 'Use Yarn to install dependencies. (default when yarn.lock exists)').description('Upgrades your project dependencies and app.json and to the given SDK version').asyncAction(upgradeAsync);
}
//# sourceMappingURL=../__sourcemaps__/commands/upgrade.js.map