"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.validateWithSchemaFileAsync = validateWithSchemaFileAsync;
exports.validateWithSchema = validateWithSchema;
exports.validateLowLatencyAsync = validateLowLatencyAsync;
exports.validateWithNetworkAsync = validateWithNetworkAsync;
exports.getExpoSdkStatus = getExpoSdkStatus;
exports.EXPO_SDK_NOT_IMPORTED = exports.EXPO_SDK_NOT_INSTALLED = exports.EXPO_SDK_INSTALLED_AND_IMPORTED = exports.FATAL = exports.ERROR = exports.WARNING = exports.NO_ISSUES = void 0;

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

  _lodash = function () {
    return data;
  };

  return data;
}

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

  _semver = function () {
    return data;
  };

  return data;
}

function _fsExtra() {
  const data = _interopRequireDefault(require("fs-extra"));

  _fsExtra = function () {
    return data;
  };

  return data;
}

function _getenv() {
  const data = _interopRequireDefault(require("getenv"));

  _getenv = function () {
    return data;
  };

  return data;
}

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

  _path = function () {
    return data;
  };

  return data;
}

function _spawnAsync() {
  const data = _interopRequireDefault(require("@expo/spawn-async"));

  _spawnAsync = function () {
    return data;
  };

  return data;
}

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

  ConfigUtils = function () {
    return data;
  };

  return data;
}

function _schemer() {
  const data = _interopRequireWildcard(require("@expo/schemer"));

  _schemer = function () {
    return data;
  };

  return data;
}

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

  ExpSchema = function () {
    return data;
  };

  return data;
}

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

  ProjectUtils = function () {
    return data;
  };

  return data;
}

function _Config() {
  const data = _interopRequireDefault(require("../Config"));

  _Config = function () {
    return data;
  };

  return data;
}

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

  Versions = function () {
    return data;
  };

  return data;
}

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

  Watchman = 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 }; }

const NO_ISSUES = 0;
exports.NO_ISSUES = NO_ISSUES;
const WARNING = 1;
exports.WARNING = WARNING;
const ERROR = 2;
exports.ERROR = ERROR;
const FATAL = 3;
exports.FATAL = FATAL;
const MIN_WATCHMAN_VERSION = '4.6.0';
const MIN_NPM_VERSION = '3.0.0';
const CORRECT_NPM_VERSION = 'latest';
const WARN_NPM_VERSION_RANGES = ['>= 5.0.0 < 5.7.0'];
const BAD_NPM_VERSION_RANGES = ['>= 5.0.0 <= 5.0.3'];

function _isNpmVersionWithinRanges(npmVersion, ranges) {
  return _lodash().default.some(ranges, range => _semver().default.satisfies(npmVersion, range));
}

async function _checkNpmVersionAsync(projectRoot) {
  try {
    try {
      let yarnVersionResponse = await (0, _spawnAsync().default)('yarnpkg', ['--version']);

      if (yarnVersionResponse.status === 0) {
        return NO_ISSUES;
      }
    } catch (e) {}

    let npmVersionResponse = await (0, _spawnAsync().default)('npm', ['--version']);

    let npmVersion = _lodash().default.trim(npmVersionResponse.stdout);

    if (_semver().default.lt(npmVersion, MIN_NPM_VERSION) || _isNpmVersionWithinRanges(npmVersion, BAD_NPM_VERSION_RANGES)) {
      ProjectUtils().logError(projectRoot, 'expo', `Error: You are using npm version ${npmVersion}. We recommend the latest version ${CORRECT_NPM_VERSION}. To install it, run 'npm i -g npm@${CORRECT_NPM_VERSION}'.`, 'doctor-npm-version');
      return WARNING;
    } else if (_isNpmVersionWithinRanges(npmVersion, WARN_NPM_VERSION_RANGES)) {
      ProjectUtils().logWarning(projectRoot, 'expo', `Warning: You are using npm version ${npmVersion}. There may be bugs in this version, use it at your own risk. We recommend version ${CORRECT_NPM_VERSION}.`, 'doctor-npm-version');
    } else {
      ProjectUtils().clearNotification(projectRoot, 'doctor-npm-version');
    }
  } catch (e) {
    ProjectUtils().logWarning(projectRoot, 'expo', `Warning: Could not determine npm version. Make sure your version is >= ${MIN_NPM_VERSION} - we recommend ${CORRECT_NPM_VERSION}.`, 'doctor-npm-version');
    return WARNING;
  }

  return NO_ISSUES;
}

async function _checkWatchmanVersionAsync(projectRoot) {
  // There's no point in checking any of this stuff if watchman isn't supported on this platform
  if (!Watchman().isPlatformSupported()) {
    ProjectUtils().clearNotification(projectRoot, 'doctor-watchman-version');
    return;
  }

  let watchmanVersion = await Watchman().unblockAndGetVersionAsync(projectRoot); // If we can't get the watchman version, `getVersionAsync` will return `null`

  if (!watchmanVersion) {
    // watchman is probably just not installed
    ProjectUtils().clearNotification(projectRoot, 'doctor-watchman-version');
    return;
  }

  if (_semver().default.lt(watchmanVersion, MIN_WATCHMAN_VERSION)) {
    let warningMessage = `Warning: You are using an old version of watchman (v${watchmanVersion}). This may cause problems for you.\n\nWe recommend that you either uninstall watchman (and XDE will try to use a copy it is bundled with) or upgrade watchman to a newer version, at least v${MIN_WATCHMAN_VERSION}.`; // Add a note about homebrew if the user is on a Mac

    if (process.platform === 'darwin') {
      warningMessage += `\n\nIf you are using homebrew, try:\nbrew uninstall watchman; brew install watchman`;
    }

    ProjectUtils().logWarning(projectRoot, 'expo', warningMessage, 'doctor-watchman-version');
  } else {
    ProjectUtils().clearNotification(projectRoot, 'doctor-watchman-version');
  }
}

async function validateWithSchemaFileAsync(projectRoot, schemaPath) {
  let {
    exp
  } = await ProjectUtils().readConfigJsonAsync(projectRoot);
  let schema = JSON.parse((await _fsExtra().default.readFile(schemaPath, 'utf8')));
  return validateWithSchema(projectRoot, exp, schema.schema, 'exp.json', 'UNVERSIONED', true);
}

async function validateWithSchema(projectRoot, exp, schema, configName, sdkVersion, validateAssets) {
  let schemaErrorMessage;
  let assetsErrorMessage;
  let validator = new (_schemer().default)(schema, {
    rootDir: projectRoot
  }); // Validate the schema itself

  try {
    await validator.validateSchemaAsync(exp);
  } catch (e) {
    if (e instanceof _schemer().SchemerError) {
      schemaErrorMessage = `Error: Problem${e.errors.length > 1 ? 's' : ''} validating fields in ${configName}. See https://docs.expo.io/versions/v${sdkVersion}/workflow/configuration/`;
      schemaErrorMessage += e.errors.map(formatValidationError).join('');
    }
  }

  if (validateAssets) {
    try {
      await validator.validateAssetsAsync(exp);
    } catch (e) {
      if (e instanceof _schemer().SchemerError) {
        assetsErrorMessage = `Error: Problem${e.errors.length > 1 ? '' : 's'} validating asset fields in ${configName}. See ${_Config().default.helpUrl}`;
        assetsErrorMessage += e.errors.map(formatValidationError).join('');
      }
    }
  }

  return {
    schemaErrorMessage,
    assetsErrorMessage
  };
}

function formatValidationError(validationError) {
  return `\n • ${validationError.fieldPath ? 'Field: ' + validationError.fieldPath + ' - ' : ''}${validationError.message}.`;
}

async function _validateExpJsonAsync(exp, pkg, projectRoot, allowNetwork) {
  if (!exp || !pkg) {
    // readConfigJsonAsync already logged an error
    return FATAL;
  }

  try {
    await _checkWatchmanVersionAsync(projectRoot);
  } catch (e) {
    ProjectUtils().logWarning(projectRoot, 'expo', `Warning: Problem checking watchman version. ${e.message}.`, 'doctor-problem-checking-watchman-version');
  }

  ProjectUtils().clearNotification(projectRoot, 'doctor-problem-checking-watchman-version');
  const expJsonExists = await ConfigUtils().fileExistsAsync(_path().default.join(projectRoot, 'exp.json'));
  const appJsonExists = await ConfigUtils().fileExistsAsync(_path().default.join(projectRoot, 'app.json'));

  if (expJsonExists && appJsonExists) {
    ProjectUtils().logWarning(projectRoot, 'expo', `Warning: Both app.json and exp.json exist in this directory. Only one should exist for a single project.`, 'doctor-both-app-and-exp-json');
    return WARNING;
  }

  ProjectUtils().clearNotification(projectRoot, 'doctor-both-app-and-exp-json');
  let sdkVersion = exp.sdkVersion;
  const configName = await ConfigUtils().configFilenameAsync(projectRoot); // Warn if sdkVersion is UNVERSIONED

  if (sdkVersion === 'UNVERSIONED' && !process.env.EXPO_SKIP_MANIFEST_VALIDATION_TOKEN) {
    ProjectUtils().logError(projectRoot, 'expo', `Error: Using unversioned Expo SDK. Do not publish until you set sdkVersion in ${configName}`, 'doctor-unversioned');
    return ERROR;
  }

  ProjectUtils().clearNotification(projectRoot, 'doctor-unversioned');
  let sdkVersions = await Versions().sdkVersionsAsync();

  if (!sdkVersions) {
    ProjectUtils().logError(projectRoot, 'expo', `Error: Couldn't connect to SDK versions server`, 'doctor-versions-endpoint-failed');
    return ERROR;
  }

  ProjectUtils().clearNotification(projectRoot, 'doctor-versions-endpoint-failed');

  if (!sdkVersion || !sdkVersions[sdkVersion]) {
    ProjectUtils().logError(projectRoot, 'expo', `Error: Invalid sdkVersion. Valid options are ${_lodash().default.keys(sdkVersions).join(', ')}`, 'doctor-invalid-sdk-version');
    return ERROR;
  }

  ProjectUtils().clearNotification(projectRoot, 'doctor-invalid-sdk-version'); // Skip validation if the correct token is set in env

  if (sdkVersion !== 'UNVERSIONED') {
    try {
      let schema = await ExpSchema().getSchemaAsync(sdkVersion);
      let {
        schemaErrorMessage,
        assetsErrorMessage
      } = await validateWithSchema(projectRoot, exp, schema, configName, sdkVersion, allowNetwork);

      if (schemaErrorMessage) {
        ProjectUtils().logError(projectRoot, 'expo', schemaErrorMessage, 'doctor-schema-validation');
      } else {
        ProjectUtils().clearNotification(projectRoot, 'doctor-schema-validation');
      }

      if (assetsErrorMessage) {
        ProjectUtils().logError(projectRoot, 'expo', assetsErrorMessage, `doctor-validate-asset-fields`);
      } else {
        ProjectUtils().clearNotification(projectRoot, `doctor-validate-asset-fields`);
      }

      ProjectUtils().clearNotification(projectRoot, 'doctor-schema-validation-exception');
      if (schemaErrorMessage || assetsErrorMessage) return ERROR;
    } catch (e) {
      ProjectUtils().logWarning(projectRoot, 'expo', `Warning: Problem validating ${configName}: ${e.message}.`, 'doctor-schema-validation-exception');
    }
  }

  const reactNativeIssue = await _validateReactNativeVersionAsync(exp, pkg, projectRoot, sdkVersions, sdkVersion);

  if (reactNativeIssue !== NO_ISSUES) {
    return reactNativeIssue;
  } // TODO: Check any native module versions here


  return NO_ISSUES;
}

async function _validateReactNativeVersionAsync(exp, pkg, projectRoot, sdkVersions, sdkVersion) {
  if (_Config().default.validation.reactNativeVersionWarnings) {
    let reactNative = pkg.dependencies ? pkg.dependencies['react-native'] : null; // react-native is required

    if (!reactNative) {
      ProjectUtils().logError(projectRoot, 'expo', `Error: Can't find react-native in package.json dependencies`, 'doctor-no-react-native-in-package-json');
      return ERROR;
    }

    ProjectUtils().clearNotification(projectRoot, 'doctor-no-react-native-in-package-json');

    if (!exp.isDetached) {
      return NO_ISSUES; // (TODO-2017-07-20): Validate the react-native version if it uses
      // officially published package rather than Expo fork. Expo fork of
      // react-native was required before CRNA. We now only run the react-native
      // validation of the version if we are using the fork. We should probably
      // validate the version here as well such that it matches with the
      // react-native version compatible with the selected SDK.
    } // Expo fork of react-native is required


    if (!/expo\/react-native/.test(reactNative)) {
      ProjectUtils().logWarning(projectRoot, 'expo', `Warning: Not using the Expo fork of react-native. See ${_Config().default.helpUrl}.`, 'doctor-not-using-expo-fork');
      return WARNING;
    }

    ProjectUtils().clearNotification(projectRoot, 'doctor-not-using-expo-fork');

    try {
      let reactNativeTag = reactNative.match(/sdk-\d+\.\d+\.\d+/)[0];
      let sdkVersionObject = sdkVersions[sdkVersion]; // TODO: Want to be smarter about this. Maybe warn if there's a newer version.

      if (_semver().default.major(Versions().parseSdkVersionFromTag(reactNativeTag)) !== _semver().default.major(Versions().parseSdkVersionFromTag(sdkVersionObject['expoReactNativeTag']))) {
        ProjectUtils().logWarning(projectRoot, 'expo', `Warning: Invalid version of react-native for sdkVersion ${sdkVersion}. Use github:expo/react-native#${sdkVersionObject['expoReactNativeTag']}`, 'doctor-invalid-version-of-react-native');
        return WARNING;
      }

      ProjectUtils().clearNotification(projectRoot, 'doctor-invalid-version-of-react-native');
      ProjectUtils().clearNotification(projectRoot, 'doctor-malformed-version-of-react-native');
    } catch (e) {
      ProjectUtils().logWarning(projectRoot, 'expo', `Warning: ${reactNative} is not a valid version. Version must be in the form of sdk-x.y.z. Please update your package.json file.`, 'doctor-malformed-version-of-react-native');
      return WARNING;
    }
  }

  return NO_ISSUES;
}

async function _validateNodeModulesAsync(projectRoot) {
  let {
    exp
  } = await ConfigUtils().readConfigJsonAsync(projectRoot);
  let nodeModulesPath = projectRoot;

  if (exp.nodeModulesPath) {
    nodeModulesPath = _path().default.resolve(projectRoot, exp.nodeModulesPath);
  } // Check to make sure node_modules exists at all


  try {
    let result = _fsExtra().default.statSync(_path().default.join(nodeModulesPath, 'node_modules'));

    if (!result.isDirectory()) {
      ProjectUtils().logError(projectRoot, 'expo', `Error: node_modules directory is missing. Please run \`npm install\` in your project directory.`, 'doctor-node-modules-missing');
      return FATAL;
    }

    ProjectUtils().clearNotification(projectRoot, 'doctor-node-modules-missing');
  } catch (e) {
    ProjectUtils().logError(projectRoot, 'expo', `Error: node_modules directory is missing. Please run \`npm install\` in your project directory.`, 'doctor-node-modules-missing');
    return FATAL;
  } // Check to make sure react native is installed


  try {
    ConfigUtils().resolveModule('react-native/local-cli/cli.js', projectRoot, exp);
    ProjectUtils().clearNotification(projectRoot, 'doctor-react-native-not-installed');
  } catch (e) {
    if (e.code === 'MODULE_NOT_FOUND') {
      ProjectUtils().logError(projectRoot, 'expo', `Error: React Native is not installed. Please run \`npm install\` in your project directory.`, 'doctor-react-native-not-installed');
      return FATAL;
    } else {
      throw e;
    }
  }

  return NO_ISSUES;
}

async function validateLowLatencyAsync(projectRoot) {
  return validateAsync(projectRoot, false);
}

async function validateWithNetworkAsync(projectRoot) {
  return validateAsync(projectRoot, true);
}

async function validateAsync(projectRoot, allowNetwork) {
  if (_getenv().default.boolish('EXPO_NO_DOCTOR', false)) {
    return NO_ISSUES;
  }

  let {
    exp,
    pkg
  } = await ConfigUtils().readConfigJsonAsync(projectRoot);
  let status = await _checkNpmVersionAsync(projectRoot);

  if (status === FATAL) {
    return status;
  }

  const expStatus = await _validateExpJsonAsync(exp, pkg, projectRoot, allowNetwork);

  if (expStatus === FATAL) {
    return expStatus;
  }

  status = Math.max(status, expStatus);

  if (exp && !exp.ignoreNodeModulesValidation) {
    let nodeModulesStatus = await _validateNodeModulesAsync(projectRoot);

    if (nodeModulesStatus > status) {
      return nodeModulesStatus;
    }
  }

  return status;
}

const EXPO_SDK_INSTALLED_AND_IMPORTED = 0;
exports.EXPO_SDK_INSTALLED_AND_IMPORTED = EXPO_SDK_INSTALLED_AND_IMPORTED;
const EXPO_SDK_NOT_INSTALLED = 1;
exports.EXPO_SDK_NOT_INSTALLED = EXPO_SDK_NOT_INSTALLED;
const EXPO_SDK_NOT_IMPORTED = 2;
exports.EXPO_SDK_NOT_IMPORTED = EXPO_SDK_NOT_IMPORTED;

async function getExpoSdkStatus(projectRoot) {
  let {
    pkg
  } = await ConfigUtils().readConfigJsonAsync(projectRoot);

  try {
    let sdkPkg;

    if (pkg.dependencies['exponent']) {
      sdkPkg = 'exponent';
    } else if (pkg.dependencies['expo']) {
      sdkPkg = 'expo';
    } else {
      return EXPO_SDK_NOT_INSTALLED;
    }

    let mainFilePath = _path().default.join(projectRoot, pkg.main);

    let mainFile = await _fsExtra().default.readFile(mainFilePath, 'utf8'); // TODO: support separate .ios.js and .android.js files

    if (mainFile.includes(`from '${sdkPkg}'`) || mainFile.includes(`require('${sdkPkg}')`)) {
      return EXPO_SDK_INSTALLED_AND_IMPORTED;
    } else {
      return EXPO_SDK_NOT_IMPORTED;
    }
  } catch (e) {
    return EXPO_SDK_NOT_IMPORTED;
  }
}
//# sourceMappingURL=../__sourcemaps__/project/Doctor.js.map