text: //
  // Dependencies
  //
  
  var cint = require('cint');
  var path = require('path');
  var findUp = require('find-up');
  var _ = require('lodash');
  var Promise = require('bluebird');
  var getstdin = require('get-stdin');
  var Table = require('cli-table');
  var chalk = require('chalk');
  var fs = Promise.promisifyAll(require('fs'));
  var vm = require('./versionmanager');
  var versionUtil = require('./version-util');
  var jph = require('json-parse-helpfulerror');
  
  // maps package managers to package file names
  var packageFileNames = {
      npm: 'package.json',
      bower: 'bower.json'
  };
  
  // maps string levels to numeric levels
  var logLevels = {
      silent: 0,
      error: 1,
      minimal: 2,
      warn: 3,
      info: 4,
      verbose: 5,
      silly: 6
  };
  
  // time to wait for stdin before printing a warning
  var stdinWarningTime = 5000;
  var stdinWarningMessage = 'Hmmmmm... this is taking a long time. Your console is telling me to wait for input \non stdin, but maybe that is not what you want.\nTry specifying a package file explicitly with ' + chalk.cyan('--packageFile package.json') + '. \nSee https://github.com/tjunnone/npm-check-updates/issues/136#issuecomment-155721102';
  
  //
  // Helper functions
  //
  
  function print(options, message, loglevel, method) {
  
      method = method || 'log';
  
      // not in json mode
      // not silent
      // not at a loglevel under minimum specified
      if (!options.json && options.loglevel !== 'silent' && (loglevel == null || logLevels[options.loglevel] >= logLevels[loglevel])) {
          console[method](message);
      }
  }
  
  function programError(options, message) {
      if (options.cli) {
          print(options, message, null, 'error');
          process.exit(1);
      } else {
          throw new Error(message);
      }
  }
  
  function printJson(options, message) {
      if (options.loglevel !== 'silent') {
          console.log(message);
      }
  }
  
  /**
   * Gets the name of the package file based on --packageFile or --packageManager.
   */
  function getPackageFileName(options) {
      return options.packageFile ? options.packageFile :
          packageFileNames[options.packageManager];
  }
  
  function createDependencyTable() {
      return new Table({
          colAligns: ['left', 'right', 'right', 'right'],
          chars: {
              'top': '',
              'top-mid': '',
              'top-left': '',
              'top-right': '',
              'bottom': '',
              'bottom-mid': '',
              'bottom-left': '',
              'bottom-right': '',
              'left': '',
              'left-mid': '',
              'mid': '',
              'mid-mid': '',
              'right': '',
              'right-mid': '',
              'middle': ''
          }
      });
  }
  
  /**
   * @param args.from
   * @param args.to
   * @param options.greatest
   */
  function toDependencyTable(options, args) {
  
      options = options || {};
      var table = createDependencyTable();
      var rows = Object.keys(args.to).map(function (dep) {
          var from = args.from[dep] || '';
          var to = versionUtil.colorizeDiff(args.to[dep] || '', args.from[dep]);
          return [dep, from, '→', to];
      });
      rows.forEach(function (row) {
          table.push(row);
      });
      return table;
  }
  
  var readPackageFile = cint.partialAt(fs.readFileAsync, 1, 'utf8');
  var writePackageFile = fs.writeFileAsync;
  
  //
  // Main functions
  //
  
  function analyzeGlobalPackages(options) {
      print(options, 'Getting installed packages...', 'verbose');
  
      return vm.getInstalledPackages({
          filter: options.filter,
          reject: options.reject
      })
          .then(function (globalPackages) {
              print(options, globalPackages, 'silly');
              print(options, '', 'silly');
              print(options, 'Fetching ' + vm.getVersionTarget(options) + ' versions...', 'verbose');
  
              return vm.upgradePackageDefinitions(globalPackages, options)
                  .spread(function (upgraded, latest) {
                      print(options, latest, 'silly');
  
                      var upgradePromise = printUpgrades(options, {
                          current: globalPackages,
                          upgraded: upgraded,
                          latest: latest
                      });
  
                      if (options.global) {
                      	var instruction = '[package]';
                      	if (upgraded) {
                          	instruction = Object.keys(upgraded).map(function (package) {
                          		return package + '@' + upgraded[package];
                          	}).join(' ');
                          }
                          print(options, chalk.cyan('ncu') + ' itself cannot upgrade global packages. Run the following to upgrade all global packages: \n\n' + chalk.cyan('npm -g install ' + instruction) + '\n');
                      }
  
                      return upgradePromise;
                  });
          });
  }
  
  function analyzeProjectDependencies(options, pkgData, pkgFile) {
  
      var pkg;
  
      try {
          pkg = jph.parse(pkgData);
      } catch (e) {
          programError(options, chalk.red('Invalid package file' + (pkgFile ? ': ' + pkgFile : ' from stdin') + '. Error details:\n' + e.message));
      }
  
      var current = vm.getCurrentDependencies(pkg, {
          prod: options.prod,
          dev: options.dev,
          optional: options.optional,
          peer: options.peer,
          filter: options.filter,
          reject: options.reject
      });
  
      print(options, 'Getting installed packages...', 'verbose');
      print(options, 'Fetching ' + vm.getVersionTarget(options) + ' versions...', 'verbose');
  
      return Promise.all([
          current,
          // only search for installed dependencies if a pkgFile is specified
          pkgFile ? vm.getInstalledPackages({
              cwd: options.packageFileDir ? path.dirname(path.resolve(pkgFile)) : null
          }) : Promise.resolve(),
          vm.upgradePackageDefinitions(current, options)
      ])
          .spread(function (current, installed, upgradedAndLatest) {
  
              var output;
              var newPkgData;
  
              var upgraded = upgradedAndLatest[0];
              var latest = upgradedAndLatest[1];
  
              if (options.json) {
                  newPkgData = vm.upgradePackageData(pkgData, current, upgraded, latest, options);
                  // don't need try-catch here because pkgData has already been parsed as valid JSON, and vm.upgradePackageData simply does a find-and-replace on that
                  output = options.jsonAll ? jph.parse(newPkgData) :
                      options.jsonDeps ?
                      // exclude peerDependencies by default
                          _.pick(jph.parse(newPkgData), 'dependencies', 'devDependencies', 'optionalDependencies') :
                          upgraded;
  
                  printJson(options, JSON.stringify(output));
                  return output;
              } else {
                  return printUpgrades(options, {
                      installed: installed,
                      current: current,
                      upgraded: upgraded,
                      latest: latest,
                      pkgData: pkgData,
                      pkgFile: pkgFile,
                      isUpgrade: options.upgrade
                  });
              }
          });
  }
  
  // TODO: printUpgrades and analyzeProjectDependencies need to be refactored. They are tightly coupled and monolithic.
  /**
   * @param args.current
   * @param args.upgraded
   * @param args.installed (optional)
   * @param args.latest (optional)
   * @param args.pkgData
   * @param args.pkgFile (optional)
   * @param args.isUpgrade (optional)
   */
  function printUpgrades(options, args) {
  
      // split the deps into satisfied and unsatisfied to display in two separate tables
      var deps = Object.keys(args.upgraded);
      var satisfied = cint.toObject(deps, function (dep) {
          return cint.keyValue(dep, vm.isSatisfied(args.latest[dep], args.current[dep]));
      });
  
      var isSatisfied = _.propertyOf(satisfied);
      var satisfiedUpgraded = cint.filterObject(args.upgraded, function (dep) {
          return isSatisfied(dep) && (!args.latest || !args.installed || args.latest[dep] !== args.installed[dep]);
      });
      var unsatisfiedUpgraded = cint.filterObject(args.upgraded, cint.not(isSatisfied));
      var numSatisfied =   Object.keys(satisfiedUpgraded).length;
      var numUnsatisfied = Object.keys(unsatisfiedUpgraded).length;
  
      print(options, '');
  
      // print everything is up-to-date
      if (numSatisfied === 0 && numUnsatisfied === 0) {
          var smiley = chalk.green.bold(':)');
          if (options.global) {
              print(options, 'All global packages are up-to-date ' + smiley);
          } else {
              print(options, 'All dependencies match the ' + vm.getVersionTarget(options) + ' package versions ' +
                  smiley);
          }
      }
  
      // print unsatisfied table
      if (numUnsatisfied > 0) {
          var unsatisfiedTable = toDependencyTable(options, {
              from: args.current,
              to: unsatisfiedUpgraded
          }, {
              greatest: options.greatest,
              newest: options.newest
          });
          print(options, unsatisfiedTable.toString());
      }
  
      // print satisfied table
      if (numSatisfied > 0) {
          var satisfiedTable = toDependencyTable(options, {
              from: args.current,
              to: satisfiedUpgraded
          }, {
              greatest: options.greatest,
              newest: options.newest
          });
          if (!options.upgrade) {
              print(options, (numUnsatisfied > 0 ? '\n' : '') + 'The following dependenc' + (numSatisfied === 1 ? 'y is' : 'ies are') + ' satisfied by ' + (numSatisfied === 1 ? 'its' : 'their') + ' declared version range, but the installed version' + (numSatisfied === 1 ? ' is' : 's are') + ' behind. You can install the latest version' + (numSatisfied === 1 ? '' : 's') + ' without modifying your package file by using ' + chalk.cyan(options.packageManager + ' update') + '. If you want to update the dependenc' + (numSatisfied === 1 ? 'y' : 'ies') + ' in your package file anyway, run ' + chalk.cyan('ncu -a') + '.\n', 'warn');
          }
          print(options, satisfiedTable.toString(), 'warn');
      }
  
      var numToUpgrade = numUnsatisfied + (options.upgradeAll ? numSatisfied : 0);
  
      if (args.pkgFile && numToUpgrade > 0) {
          if (options.errorLevel >= 2) {
              programError(options, 'Dependencies not up-to-date');
          } else if (args.isUpgrade) {
              var newPkgData = vm.upgradePackageData(args.pkgData, args.current, args.upgraded, args.latest, options);
              return writePackageFile(args.pkgFile, newPkgData)
                  .then(function () {
                      print(options, 'Upgraded ' + args.pkgFile + '\n');
                  });
          } else {
              print(options, '\nRun ' + chalk.cyan('ncu') + ' with ' + chalk.cyan(numUnsatisfied > 0 ? '-u' : '-a') + ' to upgrade ' + getPackageFileName(options));
          }
      }
  
      print(options, '');
      return Promise.resolve();
  }
  
  //
  // Program
  //
  
  
  /** Initializes and consolidates options from the cli. */
  function initOptions(options) {
  
      return _.assign({}, options, {
          // 'upgradeAll' is a type of an upgrade so if it's set, we set 'upgrade' as well
          upgrade: options.upgrade || options.upgradeAll,
          // convert silent option to loglevel silent
          loglevel: options.silent ? 'silent' : options.loglevel,
          // add shortcut for any keys that start with 'json'
          json: _(options)
              .keys()
              .filter(_.partial(_.startsWith, _, 'json', 0))
              .some(_.propertyOf(options))
      });
  }
  
  /** Finds the package file and data.
      @returns Promise [pkgFile, pkgData]
  
  Searches as follows:
   --packageData flag
   --packageFile flag
   --stdin
   --findUp
  */
  function findPackage(options) {
  
      var pkgData;
      var pkgFile;
      var stdinTimer;
  
      print(options, 'Running in local mode...', 'verbose');
      print(options, 'Finding package file data...', 'verbose');
  
      var pkgFileName = getPackageFileName(options);
  
      /*
  
       // if pkgFile was set, make sure it exists and read it into pkgData
       if (pkgFile) {
       // print a message if we are using a descendant package file
       var relPathToPackage = path.resolve(pkgFile);
       if (relPathToPackage !== pkgFileName) {
       print(options, 'Using ' + relPathToPackage);
       }
       if (!fs.existsSync(pkgFile)) {
       programError(options, chalk.red(relPathToPackage + ' not found'));
       }
  
       pkgData = readPackageFile(pkgFile, null, false);
       }
  
       // no package data!
       if (!pkgData) {
       }
  
       return pkgData.then(_.partial(analyzeProjectDependencies, options, _, pkgFile));
  
       */
  
      function getPackageDataFromFile(pkgFile, pkgFileName) {
          // exit if no pkgFile to read from fs
          if (pkgFile !== null) {
              // print a message if we are using a descendant package file
              var relPathToPackage = path.resolve(pkgFile);
              if (relPathToPackage !== pkgFileName) {
                  print(options, 'Using ' + relPathToPackage);
              }
          } else {
              programError(options, chalk.red('No ' + pkgFileName) + '\n\nPlease add a ' + pkgFileName + ' to the current directory, specify the ' + chalk.cyan('--packageFile') + ' or ' + chalk.cyan('--packageData') + ' options, or pipe a ' + pkgFileName + ' to stdin.');
          }
  
          return readPackageFile(pkgFile);
      }
  
      // get the package data from the various input possibilities
      if (options.packageData) {
          pkgData = Promise.resolve(options.packageData);
      } else if (options.packageFile) {
          pkgFile = options.packageFile;
          pkgData = getPackageDataFromFile(pkgFile, pkgFileName);
      } else if (!process.stdin.isTTY) {
          print(options, 'Waiting for package data on stdin...', 'verbose');
  
          // warn the user after a while if still waiting for stdin
          // this is a way to mitigate #136 where Windows unexpectedly waits for stdin
          stdinTimer = setTimeout(function () {
              console.log(stdinWarningMessage);
          }, stdinWarningTime);
  
          // clear the warning timer once stdin returns and fallback to scanning pwd if no content from stdin
          pkgData = getstdin().then(function (_pkgData) {
              clearTimeout(stdinTimer);
  
              var isEmptyStdin = _pkgData.length === 0 || (_pkgData.length === 1 && _pkgData.charCodeAt(0) === 10);
              // if no stdin content fall back to searching for package.json from pwd and up to root
              if (isEmptyStdin) {
                  pkgFile = findUp.sync(pkgFileName);
                  return getPackageDataFromFile(pkgFile, pkgFileName);
              } else {
                  return _pkgData;
              }
          });
      } else {
          // find the closest package starting from the current working directory and going up to the root
          pkgFile = findUp.sync(pkgFileName);
          pkgData = getPackageDataFromFile(pkgFile, pkgFileName);
      }
  
      return Promise.all([pkgData, pkgFile]);
  }
  
  /** main entry point */
  function run(opts) {
      var options = opts || {};
  
      // if not executed on the command-line (i.e. executed as a node module), set some defaults
      if (!options.cli) {
          options = _.defaults({}, options, {
              // if they want to modify the package file, we must disable jsonUpgraded
              // otherwise the write operation will not happen
              jsonUpgraded: !options.upgrade,
              // should not suggest upgrades to versions within the specified version range if upgradeAll is explicitly set to false. Will become the default in the next major version.
              upgradeAll: options.upgradeAll === undefined ? true : options.upgradeAll,
              loglevel: 'silent',
              args: []
          });
      }
  
      // get filter from arguments
      options.filter = options.args.join(' ') || options.filter;
  
      print(options, 'Initializing...', 'verbose');
  
      return vm.initialize({
          global: options.global,
          packageManager: options.packageManager,
          registry: options.registry
      })
          .then(function () {
  
              options = initOptions(options);
  
              var timeout;
              var timeoutPromise = Promise.resolve();
  
              if (options.timeout) {
                  var timeoutMs = _.isString(options.timeout) ? parseInt(options.timeout, 10) : options.timeout;
                  timeoutPromise = new Promise(function (resolve, reject) {
                      timeout = setTimeout(function () {
                          // must catch the error and reject expicitly since we are in a setTimeout
                          try {
                              programError(options, chalk.red('Exceeded global timeout of ' + timeoutMs + 'ms'));
                          } catch (e) {
                              reject(e);
                          }
                      }, timeoutMs);
                  });
              }
  
              var pendingAnalysis = options.global ?
                  analyzeGlobalPackages(options) :
                  findPackage(options).spread(_.partial(analyzeProjectDependencies, options));
  
              // let the timeout and pendingAnalysis race
              return new Promise(function (resolve, reject) {
                  timeoutPromise.catch(reject);
                  pendingAnalysis.then(function (result) {
                      clearTimeout(timeout);
                      resolve(result);
                  });
              });
          });
  }
  
  module.exports = _.assign({
      run: run
  }, vm);
  