"use strict";

var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

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

function _bluebird() {
  const data = require("bluebird");

  _bluebird = function () {
    return data;
  };

  return data;
}

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

  _execa = function () {
    return data;
  };

  return data;
}

function _pMapSeries() {
  const data = _interopRequireDefault(require("p-map-series"));

  _pMapSeries = function () {
    return data;
  };

  return data;
}

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

  _semver = function () {
    return data;
  };

  return data;
}

function _ramda() {
  const data = _interopRequireWildcard(require("ramda"));

  _ramda = function () {
    return data;
  };

  return data;
}

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

  _chalk = function () {
    return data;
  };

  return data;
}

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

  _fsExtra = function () {
    return data;
  };

  return data;
}

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

  _path = function () {
    return data;
  };

  return data;
}

function _logger() {
  const data = _interopRequireDefault(require("../logger/logger"));

  _logger = function () {
    return data;
  };

  return data;
}

function _constants() {
  const data = require("../constants");

  _constants = function () {
    return data;
  };

  return data;
}

function _generalError() {
  const data = _interopRequireDefault(require("../error/general-error"));

  _generalError = function () {
    return data;
  };

  return data;
}

function _analytics() {
  const data = require("../analytics/analytics");

  _analytics = function () {
    return data;
  };

  return data;
}

const objectToArray = obj => (0, _ramda().map)((0, _ramda().join)('@'), (0, _ramda().toPairs)(obj));

const rejectNils = _ramda().default.reject(_ramda().isNil);

const defaultNpmArgs = [];
const defaultYarnArgs = [];
const defaultPackageManagerArgs = {
  npm: defaultNpmArgs,
  yarn: defaultYarnArgs
};
const defaultPackageManagerProcessOptions = {
  cwd: process.cwd
};

const warningPrefix = packageManager => {
  return packageManager === 'npm' ? 'npm WARN' : 'warning';
};

const errorPrefix = packageManager => {
  return packageManager === 'npm' ? 'npm ERR!' : 'error';
};

const peerDependenciesMissing = packageManager => {
  return packageManager === 'npm' ? 'requires a peer' : 'unmet peer';
};

const stripNonNpmErrors = (errors, packageManager) => {
  // a workaround to remove all 'npm warn' and 'npm notice'.
  // NPM itself returns them even when --loglevel = error or when --silent/--quiet flags are set
  const prefix = errorPrefix(packageManager);
  return errors.split('\n').filter(error => error.startsWith(prefix)).join('\n');
};

const stripNonPeerDependenciesWarnings = (errors, packageManager) => {
  const prefix = warningPrefix(packageManager);
  const peer = peerDependenciesMissing(packageManager);
  return errors.split('\n').filter(error => error.startsWith(prefix) && error.includes(peer)).join('\n');
};
/**
 * Pick only allowed to be overridden options
 * @param {Object} userOptions
 */


const getAllowdPackageManagerProcessOptions = userOptions => {
  const allowdOptions = ['shell', 'env', 'extendEnv', 'uid', 'gid', 'preferLocal', 'localDir', 'timeout'];
  return _ramda().default.pick(allowdOptions, userOptions);
};

/**
 * Install packages in specific directory
 */
const _installInOneDirectory = ({
  modules = [],
  packageManager = _constants().DEFAULT_PACKAGE_MANAGER,
  packageManagerArgs = [],
  packageManagerProcessOptions = {},
  dir,
  verbose = false
}) => {
  // Handle process options
  const allowedPackageManagerProcessOptions = getAllowdPackageManagerProcessOptions(packageManagerProcessOptions);
  const concretePackageManagerProcessOptions = (0, _ramda().merge)(defaultPackageManagerProcessOptions, allowedPackageManagerProcessOptions);
  concretePackageManagerProcessOptions.cwd = dir || concretePackageManagerProcessOptions.cwd;
  const cwd = concretePackageManagerProcessOptions.cwd; // taking care of object case

  const processedModules = (0, _ramda().is)(Object, modules) && !Array.isArray(modules) ? objectToArray(modules) : modules; // Handle process args

  const concretePackageManagerDefaultArgs = ['install', ...processedModules, ...defaultPackageManagerArgs[packageManager]];
  const concretePackageManagerArgs = rejectNils(_ramda().default.concat(concretePackageManagerDefaultArgs, packageManagerArgs)); // Add npm verbose flag

  if (verbose && packageManager === 'npm') {// we may want to use it later. For now, it print too much information
    // concretePackageManagerArgs.push('--verbose');
  }

  _fsExtra().default.ensureDirSync(_path().default.join(cwd, 'node_modules'));

  _logger().default.debug(`installing npm packages using ${packageManager} at ${cwd} with options:`, concretePackageManagerProcessOptions, `and args: ${concretePackageManagerArgs}`); // Set the shell to true to prevent problems with post install scripts when running as root


  const packageManagerClientName = packageManager;
  const childProcess = (0, _execa().default)(packageManagerClientName, concretePackageManagerArgs, concretePackageManagerProcessOptions); // Remove the install from args since it's always there

  const printArgs = concretePackageManagerArgs.filter(arg => arg !== 'install');
  const argsString = printArgs && printArgs.length > 0 ? `with args: ${printArgs}` : '';
  return childProcess.then(({
    stdout,
    stderr
  }) => {
    const successMessage = `\nsuccessfully ran ${packageManager} install at ${cwd} ${argsString}`;
    const peerWarnings = stripNonPeerDependenciesWarnings(stderr, packageManager);
    stdout = verbose ? stdout + successMessage : _chalk().default.white(peerWarnings) + successMessage;
    stderr = verbose ? stderr : '';
    return {
      stdout,
      stderr
    };
  }).catch(err => {
    let stderr = `failed running ${packageManager} install at ${cwd} ${argsString}  \n`;
    stderr += verbose ? err.stderr : stripNonNpmErrors(err.stderr, packageManager);
    return Promise.reject(new (_generalError().default)(`${stderr}\n\n${_chalk().default.yellow(`see troubleshooting at https://${_constants().BASE_DOCS_DOMAIN}/docs/install-components.html`)}`));
  });
};
/**
 * Get peer dependencies for directory
 * you should run this after you run npm install
 * internally it uses npm list -j
 */


const _getPeerDeps =
/*#__PURE__*/
function () {
  var _ref = (0, _bluebird().coroutine)(function* (dir) {
    const packageManager = _constants().DEFAULT_PACKAGE_MANAGER;

    let npmList;

    try {
      npmList = yield (0, _execa().default)(packageManager, ['list', '-j'], {
        cwd: dir
      });
    } catch (err) {
      if (err.stdout && err.stdout.startsWith('{')) {
        // it's probably a valid json with errors, that's fine, parse it.
        npmList = err;
      } else {
        _logger().default.error(err);

        throw new Error(`failed running ${err.cmd} to find the peer dependencies due to an error: ${err.message}`);
      }
    }

    const peerDepsObject = yield getPeerDepsFromNpmList(npmList.stdout, packageManager);
    return objectToArray(peerDepsObject);
  });

  return function _getPeerDeps(_x) {
    return _ref.apply(this, arguments);
  };
}();

function getPeerDepsFromNpmList(_x2, _x3) {
  return _getPeerDepsFromNpmList.apply(this, arguments);
}

function _getPeerDepsFromNpmList() {
  _getPeerDepsFromNpmList = (0, _bluebird().coroutine)(function* (npmList, packageManager) {
    const parsePeers = deps => {
      const result = {};

      _ramda().default.forEachObjIndexed(dep => {
        if (dep.peerMissing) {
          const name = dep.required.name;
          const version = dep.required.version;
          result[name] = version;
        }
      }, deps);

      return result;
    };

    const npmListObject = yield parseNpmListJsonGracefully(npmList, packageManager);
    return parsePeers(npmListObject.dependencies);
  });
  return _getPeerDepsFromNpmList.apply(this, arguments);
}

function parseNpmListJsonGracefully(_x4, _x5) {
  return _parseNpmListJsonGracefully.apply(this, arguments);
}
/**
 * A wrapper function to call the install
 * then get the peers
 * then install the peers
 */


function _parseNpmListJsonGracefully() {
  _parseNpmListJsonGracefully = (0, _bluebird().coroutine)(function* (str, packageManager) {
    try {
      const json = JSON.parse(str);
      return json;
    } catch (err) {
      _logger().default.error(err);

      if (packageManager === 'npm') {
        const version = yield getNpmVersion();

        _analytics().Analytics.setExtraData('npmVersion', version);

        if (version && _semver().default.gte(version, '5.0.0') && _semver().default.lt(version, '5.1.0')) {
          // see here for more info about this issue with npm 5.0.0
          // https://github.com/npm/npm/issues/17331
          throw new Error(`error: your npm version "${version}" has issues returning json, please upgrade to 5.1.0 or above (npm install -g npm@5.1.0)`);
        }
      }

      throw new Error(`failed parsing the output of npm list due to an error: ${err.message}`);
    }
  });
  return _parseNpmListJsonGracefully.apply(this, arguments);
}

const _installInOneDirectoryWithPeerOption =
/*#__PURE__*/
function () {
  var _ref2 = (0, _bluebird().coroutine)(function* ({
    modules = [],
    packageManager = _constants().DEFAULT_PACKAGE_MANAGER,
    packageManagerArgs = [],
    packageManagerProcessOptions = {},
    dir,
    installPeerDependencies = false,
    verbose = false
  }) {
    const rootDirResults = yield _installInOneDirectory({
      modules,
      packageManager,
      packageManagerArgs,
      packageManagerProcessOptions,
      dir,
      installPeerDependencies,
      verbose
    });

    if (installPeerDependencies) {
      const peers = yield _getPeerDeps(dir);
      const peerResults = yield _installInOneDirectory({
        modules: peers,
        packageManager,
        packageManagerArgs,
        packageManagerProcessOptions,
        dir,
        verbose
      });
      return [rootDirResults, peerResults];
    }

    return rootDirResults;
  });

  return function _installInOneDirectoryWithPeerOption(_x6) {
    return _ref2.apply(this, arguments);
  };
}();
/**
 * when modules is empty, it runs 'npm install' without any package, which installs according to package.json file
 */


const installAction =
/*#__PURE__*/
function () {
  var _ref3 = (0, _bluebird().coroutine)(function* ({
    modules,
    packageManager = _constants().DEFAULT_PACKAGE_MANAGER,
    packageManagerArgs = [],
    packageManagerProcessOptions = {},
    useWorkspaces = false,
    dirs = [],
    rootDir,
    installRootPackageJson = false,
    installPeerDependencies = false,
    verbose = false
  }) {
    if (useWorkspaces && packageManager === 'yarn') {
      yield _installInOneDirectoryWithPeerOption({
        modules,
        packageManager,
        packageManagerArgs,
        packageManagerProcessOptions,
        dir: rootDir,
        installPeerDependencies,
        verbose
      });
    }

    const results = [];

    if (installRootPackageJson) {
      // installation of the root package.json has to be completed before installing the sub-directories package.json.
      const rootDirResults = yield _installInOneDirectoryWithPeerOption({
        modules,
        packageManager,
        packageManagerArgs,
        packageManagerProcessOptions,
        dir: rootDir,
        installPeerDependencies,
        verbose
      });

      if (Array.isArray(rootDirResults)) {
        results.concat(rootDirResults);
      } else {
        results.push(rootDirResults);
      }
    }

    const installInDir = dir => _installInOneDirectoryWithPeerOption({
      modules,
      packageManager,
      packageManagerArgs,
      packageManagerProcessOptions,
      dir,
      installPeerDependencies,
      verbose
    }); // run npm install for each one of the directories serially, not in parallel. Don’t use Promise.all() here.
    // running them in parallel result in race condition and random NPM errors. (see https://github.com/teambit/bit/issues/1617)


    const promisesResults = yield (0, _pMapSeries().default)(dirs, installInDir);
    return results.concat(_ramda().default.flatten(promisesResults));
  });

  return function installAction(_x7) {
    return _ref3.apply(this, arguments);
  };
}();

const printResults = ({
  stdout,
  stderr
}) => {
  console.log(_chalk().default.yellow(stdout)); // eslint-disable-line

  console.log(_chalk().default.yellow(stderr)); // eslint-disable-line
};

function getNpmVersion() {
  return _getNpmVersion.apply(this, arguments);
}

function _getNpmVersion() {
  _getNpmVersion = (0, _bluebird().coroutine)(function* () {
    try {
      const {
        stdout,
        stderr
      } = yield (0, _execa().default)('npm', ['--version']);
      if (stdout && !stderr) return stdout;
    } catch (err) {
      _logger().default.debugAndAddBreadCrumb('npm-client', `got an error when executing "npm --version". ${err.message}`);
    }

    return null;
  });
  return _getNpmVersion.apply(this, arguments);
}

function getYarnVersion() {
  return _getYarnVersion.apply(this, arguments);
}
/**
 * a situation where rootDir and subDir have package.json, some of the packages may be shared
 * and some may be conflicted. And the "npm/yarn install" is done from the root dir.
 * package managers install the shared packages only once in the rootDir.
 * however, as to the conflicted packages, only npm@5 and above install it in the subDir.
 * others, install it in the root, which, result in an incorrect package resolution for the subDir.
 */


function _getYarnVersion() {
  _getYarnVersion = (0, _bluebird().coroutine)(function* () {
    try {
      const {
        stdout
      } = yield (0, _execa().default)('yarn', ['-v']);
      return stdout;
    } catch (e) {
      _logger().default.debugAndAddBreadCrumb('npm-client', `can't find yarn version by running yarn -v. ${e.message}`);
    }

    return null;
  });
  return _getYarnVersion.apply(this, arguments);
}

function isSupportedInstallationOfSubDirFromRoot(_x8) {
  return _isSupportedInstallationOfSubDirFromRoot.apply(this, arguments);
}

function _isSupportedInstallationOfSubDirFromRoot() {
  _isSupportedInstallationOfSubDirFromRoot = (0, _bluebird().coroutine)(function* (packageManager) {
    if (packageManager === 'npm') {
      const version = yield getNpmVersion();

      if (version && _semver().default.gte(version, '5.0.0')) {
        return true;
      }
    }

    return false;
  });
  return _isSupportedInstallationOfSubDirFromRoot.apply(this, arguments);
}

function getPackageLatestVersion(_x9) {
  return _getPackageLatestVersion.apply(this, arguments);
}

function _getPackageLatestVersion() {
  _getPackageLatestVersion = (0, _bluebird().coroutine)(function* (packageName) {
    try {
      const {
        stdout
      } = yield (0, _execa().default)('npm', ['show', packageName, 'version']);
      return stdout;
    } catch (e) {
      _logger().default.debugAndAddBreadCrumb('npm-client', `can't find ${packageName} version by running npm show ${packageName} version. ${e.message}`);
    }

    return null;
  });
  return _getPackageLatestVersion.apply(this, arguments);
}

var _default = {
  install: installAction,
  printResults,
  isSupportedInstallationOfSubDirFromRoot,
  getNpmVersion,
  getYarnVersion,
  getPeerDepsFromNpmList,
  getPackageLatestVersion
};
exports.default = _default;