all files / lib/plugin/ index.js

97.22% Statements 105/108
76.36% Branches 42/55
94.74% Functions 18/19
93.62% Lines 44/47
3 statements, 1 function, 1 branch Ignored     
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 139 140 141 142 143 144                                           26×       103× 103×       16×                           87×   87× 87× 13× 13×   13×         13×           13×             11× 14×                 10×                         24× 24×   24×                     16×       16× 16×                    
import glob from 'glob';
import isUndefined from 'lodash/isUndefined';
import merge from 'lodash/merge';
 
import {yarnPackageNameRegex, getYarnPackageNames} from '../json';
import Event from './event';
import API from './api';
 
 
class Plugin {
  constructor() {
    this._handlers = {};
 
    this._constructAPIEventHandlers(API);
  }
 
  _constructAPIEventHandlers(API) {
    let addEventHandler = this.addEventHandler.bind(this);
 
    // Dynamically create publicly accessible event functions.
    for (let eventCategory in Event) {
      API.event[eventCategory] = {};
 
      for (let eventName in Event[eventCategory]) {
        let eventKey = Event[eventCategory][eventName];
 
        API.event[eventCategory][eventName] = function(callback) {
          addEventHandler(eventKey, callback);
        };
      }
    }
 
    // Expose utility merge function.
    API.merge = merge;
  }
 
  _reset() {
    this._handlers = {};
  }
 
  _getHandlers(eventName) {
    this._handlers[eventName] = this._handlers[eventName] || [];
    return this._handlers[eventName];
  }
 
  addEventHandler(eventName, handler) {
    this._getHandlers(eventName).push(handler);
  }
 
  /**
   * Processes our queue of event handlers for a given eventName.
   * If a handler returns 'undefined' then we give the next handler
   * the original arguments so that every handler gets to touch the
   * data.
   * @param {string} eventName Event key where the handlers live.
   * @param {*} args Arguments.
   * @return {Promise} Promise that resolves to the ending value of the
   *   promise chain.
   */
  async processEventHandlers(eventName, ...args) {
    let handlers = this._getHandlers(eventName);
 
    let processedValue = args;
    for (let i = 0; i < handlers.length; i++) {
      let handler = handlers[i];
      let newVal;
 
      newVal = await handler.apply(undefined, processedValue);
 
      // We allow a plugin to return undefined or null to implicitly
      // allow us to use the original value that was passed into the
      // plugin handler.
      if (isUndefined(newVal) || newVal === null) {
        newVal = processedValue;
      }
 
      // If our original value was an array of 1 and is now not an
      // array because a plugin just returned the value stick it back
      // into an array to keep its type consistent.
      if (processedValue.length === 1 && !Array.isArray(newVal)) {
        newVal = [newVal];
      }
 
      if (newVal.length !== processedValue.length) {
        throw new Error(`Plugin handler for '${eventName}' did not` +
          ' return all arguments given to it.');
      } else {
        let allTypesMatch = newVal.every((val, index) => {
          return typeof val === typeof processedValue[index];
        });
 
        if (!allTypesMatch) {
          throw new Error(`Plugin handler for '${eventName}' did not` +
            ' return all arguments in given order.');
        }
      }
 
      processedValue = newVal;
    }
 
    // If we have only one value then unpack it.
    return processedValue.length === 1 ?
      processedValue[0] : processedValue;
  }
 
  _loadPlugin(pluginPath = '', options = {}) {
    require(pluginPath)(API, options);
  }
 
  loadFromPackageJson(directory = '', pluginConfigs) {
    getYarnPackageNames(directory).forEach(name => {
      let configName = name.replace(yarnPackageNameRegex, '');
      let pluginConfig = pluginConfigs[configName];
 
      Iif (pluginConfig && pluginConfig.enabled) {
        this._loadPlugin(name, pluginConfig.options);
      }
    });
  }
 
  /**
   * Load all .js files from a directory and load them as a plugin.
   * @param {string} directory Path to directory where plugin files exist.
   */
  loadFromDirectory(directory = '') {
    let files = glob.sync(directory + '/**/*.js', {
      nodir: true
    });
 
    Eif (!files.length) {
      return;
    }
 
    files.forEach(filePath => this._loadPlugin(filePath));
  }
}
 
const instance = new Plugin();
 
instance.Event = Event;
instance.API = API;
 
export default instance;