All files / bundle-phobia-cli/src install.js

97.06% Statements 66/68
69.23% Branches 27/39
100% Functions 17/17
97.01% Lines 65/67
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 1392x 2x 2x 2x 2x 2x 2x 2x   2x 7x     6x 6x       7x   2x 3x 3x     2x             5x 5x 5x   5x 1x 1x 1x 1x 1x 1x     5x 5x   5x 3x     5x 5x 5x 5x     4x 4x 3x           1x         4x 4x 1x     1x   3x 1x   1x     1x 1x           1x 2x 1x 1x   1x 1x             1x               1x 1x 1x           1x 1x 1x     1x           1x     5x     2x  
const c = require('chalk');
const Bromise = require('bluebird');
const _ = require('lodash');
const ora = require('ora');
const shelljs = require('shelljs');
const inquirer = require('inquirer');
const {fetchPackageStats} = require('./fetch-package-stats');
const fakeSpinner = require('./fake-spinner');
 
const npmOptionsFromArgv = argv => {
  const output = _.reduce(
    _.omit(argv, ['i', 'interactive', '$0', 'warn', 'w', '_']),
    (memo, value, key) => {
      const val = _.isBoolean(value) ? '' : ` ${value}`;
      return [...memo, (_.size(key) === 1 ? '-' : '--') + key + val];
    },
    []
  );
  return output.join(' ');
};
const installCommand = argv => {
  const options = npmOptionsFromArgv(argv);
  return `npm install ${argv._.join(' ')}${(options && ` ${options}`) || ''}`;
};
 
const main = ({
  argv,
  stream = process.stdout,
  noOra = false,
  exec = shelljs.exec,
  prompt = inquirer.prompt
}) => {
  const noSpin = noOra;
  const Spinner = noSpin ? fakeSpinner : ora;
  const spinner = Spinner({stream});
 
  const handleError = (paquage, forceThrow) => err => {
    Eif (!noSpin)
      spinner.fail(c.red(`resolving ${c.bold.underline(paquage)} failed: `) + err.message);
    Eif (forceThrow || noSpin) {
      const wrapError = new Error(`${paquage}: ${err.message}`);
      wrapError.error = err;
      throw wrapError;
    }
  };
  const packages = argv._;
  const pluralSuffix = packages.lenght > 1 ? 's' : '';
 
  const performInstall = () => {
    return exec(installCommand(argv));
  };
 
  spinner.text = `Fetching stats for package${pluralSuffix} ${packages}`;
  spinner.start();
  return Bromise.map(packages, paquage => {
    return fetchPackageStats(paquage)
      .then(stats => {
        // PREDICATE HERE
        const THRESOLD = 10000;
        if (stats.size > THRESOLD)
          return {
            package: paquage,
            canInstall: false,
            reason: 'size over threshold',
            details: `${stats.size} > ${THRESOLD}`
          };
        return {package: paquage, canInstall: true};
      })
      .catch(handleError(paquage, true));
  })
    .then(statuses => {
      spinner.clear();
      if (_.every(statuses, {canInstall: true})) {
        spinner.info(
          `Proceed to installation of package${pluralSuffix} ${c.bold.dim(packages.join(', '))}`
        );
        return performInstall();
        // §TODO: handle failure exit. and eventually add status message .succeed
      } else if (argv.warn) {
        spinner.info(
          `Proceed to installation of packages ${packages
            .map(p => c.bold.dim(p))
            .join(', ')} despite following warnings:`
        );
        _.forEach(_.filter(statuses, {canInstall: false}), status => {
          spinner.warn(
            `${c.red.yellow(status.package)}: ${status.reason}${
              status.details ? ` (${c.dim(status.details)})` : ''
            }`
          );
        });
        return performInstall();
      } else if (argv.interactive) {
        spinner.info(
          `Packages ${packages.map(p => c.bold.dim(p)).join(', ')} raised following warnings:`
        );
        _.forEach(_.filter(statuses, {canInstall: false}), status => {
          spinner.warn(
            `${c.red.yellow(status.package)}: ${status.reason}${
              status.details ? ` (${c.dim(status.details)})` : ''
            }`
          );
        });
        // eslint-disable-next-line promise/no-nesting
        return prompt([
          {
            type: 'confirm',
            name: 'proceed',
            message: 'Do you still want to proceed with installation?',
            default: 'N'
          }
        ]).then(answer => {
          Eif (answer.proceed) {
            spinner.succeed('Proceeding with installation as you requested');
            return performInstall();
          } else {
            return spinner.fail('Installation is canceled on your demand');
          }
        });
      } else {
        spinner.info('Could not install for following reasons:');
        _.forEach(statuses, status => {
          Iif (status.canInstall)
            spinner.succeed(`${c.green.bold(status.package)}: was ok to install`);
          else
            spinner.fail(
              `${c.red.bold(status.package)}: ${status.reason}${
                status.details ? ` (${c.dim(status.details)})` : ''
              }`
            );
        });
        throw new Error('Install was canceled.');
      }
    })
    .finally(() => spinner.stop());
};
 
module.exports = {main, npmOptionsFromArgv, installCommand};