check-dependencies.js 2.76 KB
Newer Older
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
/**
 * Copyright (c) 2013-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

'use strict';

var PluginError = require('plugin-error');
var colors = require('ansi-colors');
var fancyLog = require('fancy-log');
var path = require('path');
var semver = require('semver');
var spawn = require('cross-spawn');
var through = require('through2');

var PLUGIN_NAME = 'check-dependencies';

module.exports = function(opts) {
  function read(file, enc, cb) {
    var cwd = path.dirname(file.path);
    var pkgData = JSON.parse(file.contents.toString());
    var outdated = spawn(
      'yarn',
      ['outdated', '--json'],
      { cwd: cwd }
    );
    var data = '';

    outdated.stdout.on('data', function(chunk) {
      data += chunk.toString();
    });

    outdated.on('exit', function(code) {
      try {
        // Parse the yarn outdated format (http://jsonlines.org/)
        var outdatedData = data
          .split('\n')
          .filter(Boolean)
          .map(d => JSON.parse(d))
          .filter(j => j.type === 'table')[0].data;
      } catch (e) {
        console.log('error', e)
        cb(new PluginError(PLUGIN_NAME, 'npm broke'));
      }

      // Convert ["Package", "Current",...] to {"Package": 0, ...}
      const name2idx = {};
      outdatedData.head.forEach((key, idx) => name2idx[key] = idx);
      const {
        Package: NAME,
        Current: CURRENT,
        "Package Type": TYPE
      } = name2idx;

      var failures = [];
      outdatedData.body.forEach(function(row) {
        var name = row[NAME];
        var current = row[CURRENT];
        var type = row[TYPE];
        var pkgDeps = pkgData[type];

        if (!pkgDeps) {
          fancyLog(`Found missing dependency category ${type}.`);
          return;
        }

        var requested = pkgDeps[name];

        if (!requested) {
          fancyLog('Found extraneous outdated dependency. Consider running `npm prune`');
          return;
        }

        if (!requested.startsWith('file:') && !semver.satisfies(current, requested)) {
          // Definitely wrong, so we should error
          failures.push({name, current, requested});
        }
      });

      if (failures.length) {
        failures.forEach((failure) => {
          fancyLog(
            `${colors.bold(failure.name)} is outdated ` +
            `(${colors.red(failure.current)} does not satisfy ` +
            `${colors.yellow(failure.requested)})`
          );
        });
        var msg =
          'Some of your dependencies are outdated. Please run ' +
          `${colors.bold('npm update')} to ensure you are up to date.`;
        cb(new PluginError(PLUGIN_NAME, msg));
        return;
      }

      cb();
    });
  }

  return through.obj(read);
};