<commit_before>/* ---------------------------------------------------------------------------
         d8888 8888888 8888888b.  888      .d88888b.   .d8888b.  888    d8P
        d88888   888   888   Y88b 888     d88P" "Y88b d88P  Y88b 888   d8P
       d88P888   888   888    888 888     888     888 888    888 888  d8P
      d88P 888   888   888   d88P 888     888     888 888        888d88K
     d88P  888   888   8888888P"  888     888     888 888        8888888b
    d88P   888   888   888 T88b   888     888     888 888    888 888  Y88b
   d8888888888   888   888  T88b  888     Y88b. .d88P Y88b  d88P 888   Y88b
  d88P     888 8888888 888   T88b 88888888 "Y88888P"   "Y8888P"  888    Y88b
------------------------------------------------------------------------------
Airlock v0.1.2
(c) 2013 by Search Discovery <http://searchdiscovery.com/>

airlock.js may be freely distributed under the MIT license.

For all details and documentation: http://www.searchdiscovery.com/airlock
----------------------------------------------------------------------------*/
(function (window, document, undefined) {

  // First thing's first; load the new hotness.
  (function(a, i, r, l, o, c, k) {
    a.GoogleAnalyticsObject = o;
    a[o] = a[o] || (function() {
      (a[o].q = a[o].q || []).push(arguments);
    });
    a[o].l = 1 * (new Date());
    c = i.createElement(r);
    k = i.getElementsByTagName(r)[0];
    c.async = 1;
    c.src = l;
    k.parentNode.insertBefore(c, k);
  })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

  var _gaq = window._gaq,
      rx = {
        actions: /^([\w\d_-]+)?\.?(_track(Event|Pageview|Trans|Social|Timing)|_add(Item|Trans)|_set(CustomVar|Account|DomainName|AllowLinker|SampleRate|CookiePath)?|_link|_require)$/,
        setupActions: /^(.+\.)?_(set(Account|CustomVar|DomainName|AllowLinker|SampleRate|CookiePath)?)$/,
        ecommerceActions: /^(.+\.)?_(add(Trans|Item)|trackTrans)$/,
        writeableSet: /^page|title$/
      }, i, ln;

  var Store = function () {
    this._contents = {};
    this._defaultKey = '__default__';
  };
  Store.prototype.get = function (key) {
    key = key || this._defaultKey;
    return this._contents[key];
  };
  Store.prototype.set = function (key, val) {
    key = key || this._defaultKey;
    this._contents[key] = val;
    return this;
  };
  Store.prototype.has = function (key) {
    key = key || this._defaultKey;
    return !!this._contents[key];
  };
  Store.prototype.each = function (func, context) {
    for (var key in this._contents) {
      if (this._contents.hasOwnProperty(key)) {
        func.call(context, this._contents[key], key, this._contents);
      }
    }
  };

  // Each spaceship represents a tracker namespace.
  var SpaceShip = function (namespace) {
    this.settings = {};
    this._settings = {};
    this.namespace = namespace;
    this.setupQueue = [];
    this.initialized = false;
    this.settings.name = this.namespace;
  };

  SpaceShip.prototype.setAccount = function (uaCode) {
    this.account = uaCode;
  };

  SpaceShip.prototype.initialize = function () {
    for (i = 0, ln = this.setupQueue.length; i < ln; i++) {
      this.setupQueue[i]();
    }
    this.initialized = true;
  };

  var Airlock = {};

  Airlock.spaceships = new Store();

  Airlock.readAction = function (actionName) {
    var match = actionName.match(rx.actions);
    return {
      namespace: match[1],
      action: match[2]
    };
  };

  Airlock.initialize = function () {
    var newQ = [], action, spaceship;

    // loop through _gaq to strip out setup calls
    for (i = 0, ln = _gaq.length; i < ln; i++) {
      spaceship = null;

      if (typeof _gaq[i] === 'function') {
        Airlock.open(null, _gaq[i]);
        continue;
      }

      action = Airlock.readAction(_gaq[i][0]);

      if (rx.setupActions.test(action.action)) {
        // If we find a tracker we have not yet initialized, set it up
        if (!this.spaceships.has(action.namespace)) {
          spaceship = new SpaceShip(action.namespace);
          this.dock(spaceship);
        }
        this.pressurize(_gaq[i], this.spaceships.get(action.namespace));
        continue;
      }
      newQ.push(_gaq[i]);
    }
    Array.prototype.splice.apply(window._gaq, [0, _gaq.length].concat(newQ));

    this.spaceships.each(function (spaceship) {
      Airlock.open(spaceship, [
        'create',
        spaceship.account,
        spaceship.settings
      ]);
    });

    // No need to do anything fancy here. Setting an instance method
    // `push()` on the global `_gaq` variable will intercept any calls
    // to `_gaq.prototype.push()`
    window._gaq.push = function (args) {
      if (typeof args === 'function') {
        return Airlock.open(null, args);
      }

      var action = args[0];
      // Ensure users are sending a valid action, otherwise do nothing.
      if (!rx.actions.test(action)) { return; }

      // If the user is trying to setup/send an ecommerce action
      if (rx.ecommerceActions.test(action) && !Airlock.ecommerceInitialized) {
        Airlock.ecommerceInitialized = true;
        Airlock._open('require', 'ecommerce', 'ecommerce.js');
      }
      var spaceship = Airlock.spaceships.get(Airlock.readAction(action).namespace);

      if (!spaceship) { return; }

      args = Airlock.pressurize(args, spaceship);
      Airlock.open(spaceship, args);
    };

    for (i = 0, ln = _gaq.length; i < ln; i++) {
      _gaq.push(_gaq[i]);
    }
  };

  // Add tracker to Airlock, push settings to ga
  Airlock.dock = function (spaceship) {
    this.spaceships.set(spaceship.namespace, spaceship);
  };

  // Once our arguments are "pressurized", execute them.
  Airlock.open = function (spaceship, args) {
    if (typeof args === 'function') {
      return Airlock._open([args]);
    }
    if (!spaceship || !spaceship.account) { return; }
    var create = args[0] === 'create';
    if (args) {
      args[0] = !spaceship.namespace || create ?
        args[0] :
        [spaceship.namespace, args[0]].join('.');

      Airlock._open(args);
      if (create) { spaceship.initialize(); }
    }
  };

  Airlock._open = function (args) {
    return window[window.GoogleAnalyticsObject].apply(window, args);
  };

  Airlock.pressurize = function (args, spaceship) {
    var conversion = Airlock.conversions[args.splice(0,1)[0].replace(rx.actions, "$2")];
    if (!conversion) { return; }

    if (typeof conversion === 'function') {
      return conversion.apply(spaceship, args);
    }

    var index = {}, out = [],
        i = 0, ln = args.length < conversion.input.length ?
          args.length :
          conversion.input.length;

    // build an index to match input labels to input values
    for (; i < ln; i++) {
      index[conversion.input[i]] = args[i];
    }

    // loop through outputs and permute values as necessary
    i = 0; ln = conversion.output.length;
    for (; i < ln; i++) {
      var val = permute(conversion.output[i], index);
      if (val) {
        out.push(val);
      }
    }
    return out;
  };

  var permute = function (template, data) {
    if (typeof template === 'function') {
      return template(data);
    }
    var templMatch = template.match(/\[\[([a-zA-Z_]+)\]\]/);
    if (templMatch) {
      if (!data[templMatch[1]]) { return; }
      // if there is no input value corresponding to the template identifier
      // (i.e., if this is an optional value), this will return undefined
      // and will be ignored.
      return template.replace(templMatch[0], data[templMatch[1]]);
    }
    return template;
  };

  var passthru = function (arg) { return arg; };

  Airlock.conversions = {
    // Setup actions
    _setAccount: function (uaCode) {
      this.setAccount(uaCode);
    },
    _setSampleRate: function (rate) {
      this.settings.sampleRate = rate;
    },
    _setCookiePath: function (path) {
      this.settings.cookiePath = path;
    },
    _set: function (key, val) {
      this.settings[key.replace('ua', 'cookie')] = val;

      if (rx.writeableSet.test(key)) {
        return ['set',key,val];
      }
    },
    _setDomainName: function (domainName) {
      this._settings.domainName = domainName;
    },
    _setAllowLinker: function (allow) {
      var that = this;
      this.settings.allowLinker = allow;
      this.setupQueue.push(function () {
        Airlock._open('require', 'linker');
        Airlock.open(that, ['linker:autoLink', that._settings.domainName]);
      });
    },
    // Enhanced Link Attribution
    _require: function (type){
      if (type === 'inpage_linkid') {
        return ['require','linkid','linkid.js'];
      }
    },

    // Custom Variables
    _setCustomVar: function (slot, name, value) {
      var args = ['set', 'dimension' + slot, value];
      if (this.initialized) { return args; }
      this.setupQueue.push(Airlock.open.bind(Airlock, this, args));
    },

    // Tracking actions
    _trackEvent: {
      input: ['eventCategory', 'eventAction', 'eventLabel', 'eventValue', 'nonInteraction'],
      output: ['send', 'event', passthru]
    },
    _trackPageview: {
      input: ['pagePath'],
      output: ['send', 'pageview', '[[pagePath]]']
    },
    _trackTiming: {
      input: ['timingCategory', 'timingVar', 'timingValue', 'timingLabel'],
      output: ['send', 'timing', passthru]
    },
    _trackSocial: {
      input: ['socialNetwork', 'socialAction', 'socialTarget', 'page'],
      output: ['send', 'social', passthru]
    },

    // Ecommerce
    _addTrans: {
      input: ['id', 'affiliation', 'revenue', 'tax', 'shipping'],
      output: ['ecommerce:addTransaction', passthru]
    },
    _addItem: {
      input: ['id', 'sku', 'name', 'category', 'price', 'quantity'],
      output: ['ecommerce:addItem', passthru]
    },
    _trackTrans: {
      input: [],
      output: ['ecommerce:send']
    }
  };

  Airlock.initialize();
  window.Airlock = Airlock;
})(window, document);
<commit_msg>Fully prepped for Airlock in action.  Tests passing<commit_after>/* ---------------------------------------------------------------------------
         d8888 8888888 8888888b.  888      .d88888b.   .d8888b.  888    d8P
        d88888   888   888   Y88b 888     d88P" "Y88b d88P  Y88b 888   d8P
       d88P888   888   888    888 888     888     888 888    888 888  d8P
      d88P 888   888   888   d88P 888     888     888 888        888d88K
     d88P  888   888   8888888P"  888     888     888 888        8888888b
    d88P   888   888   888 T88b   888     888     888 888    888 888  Y88b
   d8888888888   888   888  T88b  888     Y88b. .d88P Y88b  d88P 888   Y88b
  d88P     888 8888888 888   T88b 88888888 "Y88888P"   "Y8888P"  888    Y88b
------------------------------------------------------------------------------
Airlock v0.1.2
(c) 2013 by Search Discovery <http://searchdiscovery.com/>

airlock.js may be freely distributed under the MIT license.

For all details and documentation: http://www.searchdiscovery.com/airlock
----------------------------------------------------------------------------*/
(function (window, document, undefined) {

  // First thing's first; load the new hotness.
  (function(a, i, r, l, o, c, k) {
    a.GoogleAnalyticsObject = o;
    a[o] = a[o] || (function() {
      (a[o].q = a[o].q || []).push(arguments);
    });
    a[o].l = 1 * (new Date());
    c = i.createElement(r);
    k = i.getElementsByTagName(r)[0];
    c.async = 1;
    c.src = l;
    k.parentNode.insertBefore(c, k);
  })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

  var _gaq = window._gaq,
      rx = {
        actions: /^([\w\d_-]+)?\.?(_track(Event|Pageview|Trans|Social|Timing)|_add(Item|Trans)|_set(CustomVar|Account|DomainName|AllowLinker|SampleRate|CookiePath)?|_link|_require)$/,
        setupActions: /^(.+\.)?_(set(Account|CustomVar|DomainName|AllowLinker|SampleRate|CookiePath)?)$/,
        ecommerceActions: /^(.+\.)?_(add(Trans|Item)|trackTrans)$/,
        writeableSet: /^page|title$/
      }, i, ln;

  var Store = function () {
    this._contents = {};
    this._defaultKey = '__default__';
  };
  Store.prototype.get = function (key) {
    key = key || this._defaultKey;
    return this._contents[key];
  };
  Store.prototype.set = function (key, val) {
    key = key || this._defaultKey;
    this._contents[key] = val;
    return this;
  };
  Store.prototype.has = function (key) {
    key = key || this._defaultKey;
    return !!this._contents[key];
  };
  Store.prototype.each = function (func, context) {
    for (var key in this._contents) {
      if (this._contents.hasOwnProperty(key)) {
        func.call(context, this._contents[key], key, this._contents);
      }
    }
  };

  // Each spaceship represents a tracker namespace.
  var SpaceShip = function (namespace) {
    this.settings = {};
    this._settings = {};
    this.namespace = namespace;
    this.setupQueue = [];
    this.initialized = false;
    this.settings.name = this.namespace;
  };

  SpaceShip.prototype.setAccount = function (uaCode) {
    this.account = uaCode;
  };

  SpaceShip.prototype.initialize = function () {
    for (i = 0, ln = this.setupQueue.length; i < ln; i++) {
      this.setupQueue[i]();
    }
    this.initialized = true;
  };

  var Airlock = {};

  Airlock.settings = window._airlock || {};
  Airlock.spaceships = new Store();

  Airlock.readAction = function (actionName) {
    var match = actionName.match(rx.actions);
    return {
      namespace: match[1],
      action: match[2]
    };
  };

  Airlock.initialize = function () {
    var newQ = [], action, spaceship;

    // loop through _gaq to strip out setup calls
    for (i = 0, ln = _gaq.length; i < ln; i++) {
      spaceship = null;

      if (typeof _gaq[i] === 'function') {
        Airlock.open(null, _gaq[i]);
        continue;
      }

      action = Airlock.readAction(_gaq[i][0]);

      if (rx.setupActions.test(action.action)) {
        // If we find a tracker we have not yet initialized, set it up
        if (!this.spaceships.has(action.namespace)) {
          spaceship = new SpaceShip(action.namespace);
          this.dock(spaceship);
        }
        this.pressurize(_gaq[i], this.spaceships.get(action.namespace));
        continue;
      }
      newQ.push(_gaq[i]);
    }
    Array.prototype.splice.apply(window._gaq, [0, _gaq.length].concat(newQ));

    this.spaceships.each(function (spaceship) {
      Airlock.open(spaceship, [
        'create',
        spaceship.account,
        spaceship.settings
      ]);
    });

    // No need to do anything fancy here. Setting an instance method
    // `push()` on the global `_gaq` variable will intercept any calls
    // to `_gaq.prototype.push()`
    window._gaq.push = function (args) {
      if (typeof args === 'function') {
        return Airlock.open(null, args);
      }

      var action = args[0];
      // Ensure users are sending a valid action, otherwise do nothing.
      if (!rx.actions.test(action)) { return; }

      // If the user is trying to setup/send an ecommerce action
      if (rx.ecommerceActions.test(action) && !Airlock.ecommerceInitialized) {
        Airlock.ecommerceInitialized = true;
        Airlock._open(['require', 'ecommerce', 'ecommerce.js']);
      }
      var spaceship = Airlock.spaceships.get(Airlock.readAction(action).namespace);

      if (!spaceship) { return; }

      args = Airlock.pressurize(args, spaceship);
      Airlock.open(spaceship, args);
    };

    for (i = 0, ln = _gaq.length; i < ln; i++) {
      _gaq.push(_gaq[i]);
    }
  };

  // Add tracker to Airlock, push settings to ga
  Airlock.dock = function (spaceship) {
    this.spaceships.set(spaceship.namespace, spaceship);
  };

  // Once our arguments are "pressurized", execute them.
  Airlock.open = function (spaceship, args) {
    if (typeof args === 'function') {
      return Airlock._open([args]);
    }
    if (!spaceship || !spaceship.account) { return; }
    var create = args[0] === 'create';
    if (args) {
      args[0] = !spaceship.namespace || create ?
        args[0] :
        [spaceship.namespace, args[0]].join('.');

      Airlock._open(args);
      if (create) { spaceship.initialize(); }
    }
  };

  Airlock._open = function (args) {
    return window[window.GoogleAnalyticsObject].apply(window, args);
  };

  Airlock.pressurize = function (args, spaceship) {
    var conversion = Airlock.conversions[args.splice(0,1)[0].replace(rx.actions, "$2")];
    if (!conversion) { return; }

    if (typeof conversion === 'function') {
      return conversion.apply(spaceship, args);
    }

    var index = {}, out = [],
        i = 0, ln = args.length < conversion.input.length ?
          args.length :
          conversion.input.length;

    // build an index to match input labels to input values
    for (; i < ln; i++) {
      index[conversion.input[i]] = args[i];
    }

    // loop through outputs and permute values as necessary
    i = 0; ln = conversion.output.length;
    for (; i < ln; i++) {
      var val = permute(conversion.output[i], index);
      if (val) {
        out.push(val);
      }
    }
    return out;
  };

  var permute = function (template, data) {
    if (typeof template === 'function') {
      return template(data);
    }
    var templMatch = template.match(/\[\[([a-zA-Z_]+)\]\]/);
    if (templMatch) {
      if (!data[templMatch[1]]) { return; }
      // if there is no input value corresponding to the template identifier
      // (i.e., if this is an optional value), this will return undefined
      // and will be ignored.
      return template.replace(templMatch[0], data[templMatch[1]]);
    }
    return template;
  };

  var passthru = function (arg) { return arg; };

  Airlock.conversions = {
    // Setup actions
    _setAccount: function (uaCode) {
      this.setAccount(uaCode);
    },
    _setSampleRate: function (rate) {
      this.settings.sampleRate = rate;
    },
    _setCookiePath: function (path) {
      this.settings.cookiePath = path;
    },
    _set: function (key, val) {
      this.settings[key.replace('ua', 'cookie')] = val;

      if (rx.writeableSet.test(key)) {
        return ['set',key,val];
      }
    },
    _setDomainName: function (domainName) {
      this._settings.domainName = domainName;
    },
    _setAllowLinker: function (allow) {
      var that = this;
      this.settings.allowLinker = allow;
      this.setupQueue.push(function () {
        Airlock._open(['require', 'linker']);
        Airlock.open(that, ['linker:autoLink', that._settings.domainName]);
      });
    },
    // Enhanced Link Attribution
    _require: function (type){
      if (type === 'inpage_linkid') {
        return ['require','linkid','linkid.js'];
      }
    },

    // Custom Variables
    _setCustomVar: function (slot, name, value) {
      var args = ['set', 'dimension' + slot, value];
      if (this.initialized) { return args; }
      this.setupQueue.push(Airlock.open.bind(Airlock, this, args));
    },

    // Tracking actions
    _trackEvent: {
      input: ['eventCategory', 'eventAction', 'eventLabel', 'eventValue', 'nonInteraction'],
      output: ['send', 'event', passthru]
    },
    _trackPageview: {
      input: ['pagePath'],
      output: ['send', 'pageview', '[[pagePath]]']
    },
    _trackTiming: {
      input: ['timingCategory', 'timingVar', 'timingValue', 'timingLabel'],
      output: ['send', 'timing', passthru]
    },
    _trackSocial: {
      input: ['socialNetwork', 'socialAction', 'socialTarget', 'page'],
      output: ['send', 'social', passthru]
    },

    // Ecommerce
    _addTrans: {
      input: ['id', 'affiliation', 'revenue', 'tax', 'shipping'],
      output: ['ecommerce:addTransaction', passthru]
    },
    _addItem: {
      input: ['id', 'sku', 'name', 'category', 'price', 'quantity'],
      output: ['ecommerce:addItem', passthru]
    },
    _trackTrans: {
      input: [],
      output: ['ecommerce:send']
    }
  };

  if (Airlock.settings.autoInit !== false) {
    Airlock.initialize();
  }
  window.Airlock = Airlock;
})(window, document);
<|endoftext|>


<commit_before>const fs = require('fs');
const os = require('os');
const got = require('got');
const registryUrl = require('registry-url')();
const pify = require('pify');
const recast = require('recast');
const path = require('path');

const devConfigFileName = path.join(__dirname, `../.hyper.js`);

let fileName =
  process.env.NODE_ENV !== 'production' && fs.existsSync(devConfigFileName)
    ? devConfigFileName
    : `${os.homedir()}/.hyper.js`;

/**
 * We need to make sure the file reading and parsing is lazy so that failure to
 * statically analyze the hyper configuration isn't fatal for all kinds of
 * subcommands. We can use memoization to make reading and parsing lazy.
 */
function memoize(fn) {
  let hasResult = false;
  let result;
  return (...args) => {
    if (!hasResult) {
      result = fn(...args);
      hasResult = true;
    }
    return result;
  };
}

const getFileContents = memoize(() => {
  try {
    return fs.readFileSync(fileName, 'utf8');
  } catch (err) {
    if (err.code !== 'ENOENT') {
      // ENOENT === !exists()
      throw err;
    }
  }
  return null;
});

const getParsedFile = memoize(() => recast.parse(getFileContents()));

const getProperties = memoize(() => getParsedFile().program.body[0].expression.right.properties);

const getPlugins = memoize(() => getProperties().find(property => property.key.name === 'plugins').value.elements);

const getLocalPlugins = memoize(
  () => getProperties().find(property => property.key.name === 'localPlugins').value.elements
);

function exists() {
  return getFileContents() !== undefined;
}

function isInstalled(plugin, locally) {
  const array = locally ? getLocalPlugins() : getPlugins();
  if (array && Array.isArray(array)) {
    return array.find(entry => entry.value === plugin) !== undefined;
  }
  return false;
}

function save() {
  return pify(fs.writeFile)(fileName, recast.print(getParsedFile()).code, 'utf8');
}

function existsOnNpm(plugin) {
  const name = getPackageName(plugin);
  return got.get(registryUrl + name.toLowerCase(), {timeout: 10000, json: true}).then(res => {
    if (!res.body.versions) {
      return Promise.reject(res);
    }
  });
}

function getPackageName(plugin) {
  const isScoped = plugin[0] === '@';
  const nameWithoutVersion = plugin.split('#')[0];

  if (isScoped) {
    return '@' + nameWithoutVersion.split('@')[1].replace('/', '%2f');
  }

  return nameWithoutVersion.split('@')[0];
}

function install(plugin, locally) {
  const array = locally ? getLocalPlugins() : getPlugins();
  return new Promise((resolve, reject) => {
    existsOnNpm(plugin)
      .then(() => {
        if (isInstalled(plugin, locally)) {
          return reject(`${plugin} is already installed`);
        }

        array.push(recast.types.builders.literal(plugin));
        save()
          .then(resolve)
          .catch(err => reject(err));
      })
      .catch(err => {
        const {statusCode} = err;
        if (statusCode && (statusCode === 404 || statusCode === 200)) {
          return reject(`${plugin} not found on npm`);
        }
        return reject(`${err.message}\nPlugin check failed. Check your internet connection or retry later.`);
      });
  });
}

function uninstall(plugin) {
  return new Promise((resolve, reject) => {
    if (!isInstalled(plugin)) {
      return reject(`${plugin} is not installed`);
    }

    const index = getPlugins().findIndex(entry => entry.value === plugin);
    getPlugins().splice(index, 1);
    save()
      .then(resolve)
      .catch(err => reject(err));
  });
}

function list() {
  if (Array.isArray(getPlugins())) {
    return getPlugins()
      .map(plugin => plugin.value)
      .join('\n');
  }
  return false;
}

module.exports.configPath = fileName;
module.exports.exists = exists;
module.exports.existsOnNpm = existsOnNpm;
module.exports.isInstalled = isInstalled;
module.exports.install = install;
module.exports.uninstall = uninstall;
module.exports.list = list;
<commit_msg>Simplify promises in `cli/api.js` (#3198)<commit_after>const fs = require('fs');
const os = require('os');
const got = require('got');
const registryUrl = require('registry-url')();
const pify = require('pify');
const recast = require('recast');
const path = require('path');

const devConfigFileName = path.join(__dirname, `../.hyper.js`);

let fileName =
  process.env.NODE_ENV !== 'production' && fs.existsSync(devConfigFileName)
    ? devConfigFileName
    : `${os.homedir()}/.hyper.js`;

/**
 * We need to make sure the file reading and parsing is lazy so that failure to
 * statically analyze the hyper configuration isn't fatal for all kinds of
 * subcommands. We can use memoization to make reading and parsing lazy.
 */
function memoize(fn) {
  let hasResult = false;
  let result;
  return (...args) => {
    if (!hasResult) {
      result = fn(...args);
      hasResult = true;
    }
    return result;
  };
}

const getFileContents = memoize(() => {
  try {
    return fs.readFileSync(fileName, 'utf8');
  } catch (err) {
    if (err.code !== 'ENOENT') {
      // ENOENT === !exists()
      throw err;
    }
  }
  return null;
});

const getParsedFile = memoize(() => recast.parse(getFileContents()));

const getProperties = memoize(() => getParsedFile().program.body[0].expression.right.properties);

const getPlugins = memoize(() => getProperties().find(property => property.key.name === 'plugins').value.elements);

const getLocalPlugins = memoize(
  () => getProperties().find(property => property.key.name === 'localPlugins').value.elements
);

function exists() {
  return getFileContents() !== undefined;
}

function isInstalled(plugin, locally) {
  const array = locally ? getLocalPlugins() : getPlugins();
  if (array && Array.isArray(array)) {
    return array.find(entry => entry.value === plugin) !== undefined;
  }
  return false;
}

function save() {
  return pify(fs.writeFile)(fileName, recast.print(getParsedFile()).code, 'utf8');
}

function existsOnNpm(plugin) {
  const name = getPackageName(plugin);
  return got.get(registryUrl + name.toLowerCase(), {timeout: 10000, json: true}).then(res => {
    if (!res.body.versions) {
      return Promise.reject(res);
    }
  });
}

function getPackageName(plugin) {
  const isScoped = plugin[0] === '@';
  const nameWithoutVersion = plugin.split('#')[0];

  if (isScoped) {
    return '@' + nameWithoutVersion.split('@')[1].replace('/', '%2f');
  }

  return nameWithoutVersion.split('@')[0];
}

function install(plugin, locally) {
  const array = locally ? getLocalPlugins() : getPlugins();
  return existsOnNpm(plugin)
    .catch(err => {
      const {statusCode} = err;
      if (statusCode && (statusCode === 404 || statusCode === 200)) {
        return Promise.reject(`${plugin} not found on npm`);
      }
      return Promise.reject(`${err.message}\nPlugin check failed. Check your internet connection or retry later.`);
    })
    .then(() => {
      if (isInstalled(plugin, locally)) {
        return Promise.reject(`${plugin} is already installed`);
      }

      array.push(recast.types.builders.literal(plugin));
      return save();
    });
}

function uninstall(plugin) {
  if (!isInstalled(plugin)) {
    return Promise.reject(`${plugin} is not installed`);
  }

  const index = getPlugins().findIndex(entry => entry.value === plugin);
  getPlugins().splice(index, 1);
  return save();
}

function list() {
  if (Array.isArray(getPlugins())) {
    return getPlugins()
      .map(plugin => plugin.value)
      .join('\n');
  }
  return false;
}

module.exports.configPath = fileName;
module.exports.exists = exists;
module.exports.existsOnNpm = existsOnNpm;
module.exports.isInstalled = isInstalled;
module.exports.install = install;
module.exports.uninstall = uninstall;
module.exports.list = list;
<|endoftext|>


<commit_before>'use strict';

/*
 * angular-click-x v1.0.0
 * (c) 2016 Tanase Butcaru, http://www.butcaru.com
 * License: MIT
 *
 * GitHub: https://github.com/TanaseButcaru/angular-click-x
 */

if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports) {
	module.exports = 'tb.clickX';
}

angular.module('tb.clickX', [])

	.provider('clickXConfig', function() {
		window.clickX = {};
		this.activeClass = 'click-x-active';
		this.isMobile = getDeviceType();
		this.swipeDetect = {
			//required min distance traveled to be considered swipe (px)
	    	threshold: 50,
	        //max distance allowed at the same time in perpendicular direction (px)
	        restraint: 50,
	        //max time allowed to travel that distance (ms)
	        allowedTime: 400
		};

		this.setActiveClass = function(className) {
			if (angular.isString(className)) {
				this.activeClass = className;
			}
		}

		this.configSwipeDetection = function(swipeOptions) {
			if (angular.isObject(swipeOptions)) {
				angular.forEach(swipeOptions, function(optionValue, optionName) {
					if (this.swipeDetect.hasOwnProperty(optionName) && angular.isNumber(optionValue)) {
						this.swipeDetect[optionName] = optionValue;
					}
				}, this);
			}
		}

		this.$get = function() {
			return this;
		}

		//helperFn: getDeviceType
		//source: https://github.com/kaimallea/isMobile/blob/master/isMobile.js
		function getDeviceType() {
			var isMobile = false,
				mobileList = {
					apple_phone: /iPhone/i,
			        apple_ipod: /iPod/i,
			        apple_tablet: /iPad/i,
			        android_phone: /(?=.*\bAndroid\b)(?=.*\bMobile\b)/i, // Match 'Android' AND 'Mobile'
			        android_tablet: /Android/i,
			        amazon_phone: /(?=.*\bAndroid\b)(?=.*\bSD4930UR\b)/i,
			        amazon_tablet: /(?=.*\bAndroid\b)(?=.*\b(?:KFOT|KFTT|KFJWI|KFJWA|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|KFARWI|KFASWI|KFSAWI|KFSAWA)\b)/i,
			        windows_phone: /IEMobile/i,
			        windows_tablet: /(?=.*\bWindows\b)(?=.*\bARM\b)/i, // Match 'Windows' AND 'ARM'
			        other_blackberry: /BlackBerry/i,
			        other_blackberry_10: /BB10/i,
			        other_opera: /Opera Mini/i,
			        other_chrome: /(CriOS|Chrome)(?=.*\bMobile\b)/i,
			        other_firefox: /(?=.*\bFirefox\b)(?=.*\bMobile\b)/i, // Match 'Firefox' AND 'Mobile'
			   	};

		   	angular.forEach(mobileList, function(regex, type) {
		   		if (isMobile) return false;
		   		else isMobile = regex.test(navigator.userAgent);
		   	});

		   	return isMobile;
		}
	})

	.directive('clickX', ['$parse', '$window', 'clickXConfig', function($parse, $window, cx) {
		return {
			priority: 0,
			restrict: 'A',

			link: function(scope, el, attrs) {
				var element = el[0],
					action = $parse(attrs.clickX),
					activeClass = cx.activeClass,
					swipe = cx.swipeDetect;

				if (cx.isMobile) {
					element.addEventListener('touchstart', actionPrepare);
					element.addEventListener('touchend', actionLaunch);
					element.addEventListener('touchcancel', actionCancel);
				}
				else {
					element.addEventListener('mousedown', actionPrepare);
					element.addEventListener('mouseup', actionLaunch);
					$window.addEventListener('mouseup', bindWindowMouseUp);
					element.addEventListener('mouseenter', bindElementMouseEnter);
					element.addEventListener('mouseleave', bindElementMouseLeave);
				}

				element.addEventListener('click', stopClickAction);

				//fn: actionPrepare
				//desc: on mouse button / finger press.
				function actionPrepare(e) {
					if (attrs.disabled === true || angular.isDefined(attrs.disabled)) {
						e.preventDefault();
					}

					if (cx.isMobile) {
						element.classList.add(activeClass);
						setElemenetStart(e.changedTouches[0]);
					}
					else {
						if ($window.clickX.hasOwnProperty('startTarget')) {
							if (isSameTarget(e, true)) {
								element.classList.add(activeClass);
							}
						}
						else {
							element.classList.add(activeClass);
							setElemenetStart(e);
						}
					}
				}

				//fn: actionLaunch
				//desc: on mouse button / finger release.
				function actionLaunch(e) {
					element.classList.remove(activeClass);

					if (isSwipe(e)) {
						return false;
					}
					else if ( ! isSameTarget(e)) {
						return false;
					}
					else if ((attrs.disabled === false || ! angular.isDefined(attrs.disabled))) {
						scope.$apply(function() {
							action(scope, {$event: e});
						});
					}
				}

				//fn: actionCancel
				//desc: on mousel leave / touch cancel.
				function actionCancel(e) {
					element.classList.remove(activeClass);
				}

				//helperFn: isSameTarget
				//desc: before launch, check if the action is released upon same target or its child nodes.
				function isSameTarget(e, justCheck) {
					var startTarget, endClientX, endClientY, endTarget,
						isChild = false, isEqual = false, isSameTarget = false,
						startPosition,  endPosition;

					if (cx.isMobile) {
						endClientX = e.changedTouches[0].clientX;
						endClientY = e.changedTouches[0].clientY;
						startTarget = element;
					}
					else if ($window.hasOwnProperty('clickX')) {
						endClientX = e.clientX;
						endClientY = e.clientY;
						startTarget = $window.clickX.startTarget;
					}
					else {
						return isSameTarget;
					}

					endTarget = document.elementFromPoint(endClientX, endClientY);
					isChild = startTarget.contains(endTarget);
					isEqual = startTarget.isEqualNode(endTarget);
					isSameTarget = isChild || isEqual;

					//check if target position remains the same
					//if diff, then a scroll happend
					if (isSameTarget) {
						startPosition = $window.clickX.startPosition;
						endPosition = getElementPosition($window.clickX.startTarget);

						if ( ! angular.equals(startPosition, endPosition)) {
							isSameTarget = false;
						}
					}

					if ( ! justCheck) {
						clickXWindowClean();
					}

					return isSameTarget;
				}

				//helperFn: isSwipe
				//source: http://www.javascriptkit.com/javatutors/touchevents2.shtml
				function isSwipe(e) {
					var pageX, pageY, distX, distY,
						elapsedTime, swipedir = 'none', isSwipe = false;

					if (cx.isMobile) {
						pageX = e.changedTouches[0].pageX;
						pageY = e.changedTouches[0].pageY;
					}
					else {
						pageX = e.pageX;
						pageY = e.pageY;
					}

					//get horizontal & vertical dist traveled by finger while in contact with surface
					distX = pageX - $window.clickX.swipePosition.startX;
					distY = pageY - $window.clickX.swipePosition.startY;
					elapsedTime = new Date().getTime() - $window.clickX.swipePosition.startTime;

					//check...
					if (elapsedTime <= swipe.allowedTime) {
						//... horizontal swipe condition
						if (Math.abs(distX) >= swipe.threshold && Math.abs(distY) <= swipe.restraint){
							//if dist traveled is negative, it indicates left swipe
							//swipedir = (distX < 0) ? 'left' : 'right';
							isSwipe = true;
						}
						//..vertical swipe condition
						else if (Math.abs(distY) >= swipe.threshold && Math.abs(distX) <= swipe.restraint){
							//if dist traveled is negative, it indicates up swipe
							//swipedir = (distY < 0)? 'up' : 'down';
							isSwipe = true;
						}
					}

					return isSwipe;
				}

				//helperFn: bindWindowMouseUp
				function bindWindowMouseUp(e){
					if ($window.clickX.hasOwnProperty('startTarget')) {
						if ( ! isSameTarget(e, true)) {
							clickXWindowClean();
						}
					}
				}

				//helperFn: bindElementMouseEnter
				function bindElementMouseEnter(e){
					if ($window.clickX.hasOwnProperty('startTarget')) {
						actionPrepare(e);
					}
				}

				//helperFn: bindElementMouseLeave
				function bindElementMouseLeave(e){
					if($window.clickX.hasOwnProperty('startTarget')) {
						actionCancel(e);
					}
				}

				//helperFn: stopClickAction
				function stopClickAction(e) {
					e.preventDefault();
				}

				//helperFn: clickXClean
				function clickXWindowClean() {
					for (var prop in $window.clickX) {
						delete $window.clickX[prop];
					}
				}

				//helperFn: getElementPosition
				function getElementPosition(elem) {
					return {
						top: elem.getBoundingClientRect().top,
						left: elem.getBoundingClientRect().left,
						right: elem.getBoundingClientRect().right
					};
				}

				//helperFn: setElemenetStart
				//desc: get element & calculate/get its position
				function setElemenetStart(position){
					$window.clickX.startTarget = document.elementFromPoint(position.clientX, position.clientY);
					$window.clickX.startPosition = getElementPosition($window.clickX.startTarget);
					$window.clickX.swipePosition = {
						startX: position.pageX,
						startY: position.pageY,
						startTime: new Date().getTime()
					};
				}

				//helperFn: getClickXParent
				//desc: determine the parent node with click-x attr
				function getClickXParent(childEl) {
					var parentEl = childEl.parentNode;

					while (parentEl.hasAttribute('click-x') === false) {
						parentEl = parentEl.parentNode;
					}

					return parentEl;
				}

				//clean up!
				scope.$on('$destroy', function() {
					if (cx.isMobile) {
						element.removeEventListener('touchstart', actionPrepare);
						element.removeEventListener('touchend', actionLaunch);
						element.removeEventListener('touchcancel', actionCancel);
					}
					else {
						element.removeEventListener('mousedown', actionPrepare);
						element.removeEventListener('mouseup', actionLaunch);
						$window.removeEventListener('mouseup', bindWindowMouseUp);
						element.removeEventListener('mouseenter', bindElementMouseEnter);
						element.removeEventListener('mouseleave', bindElementMouseLeave);
					}

					element.removeEventListener('click', stopClickAction);
					clickXWindowClean();
				});
			}
		}
	}]);
<commit_msg>fix tab spacing.<commit_after>'use strict';

/*
 * angular-click-x v1.0.0
 * (c) 2016 Tanase Butcaru, http://www.butcaru.com
 * License: MIT
 *
 * GitHub: https://github.com/TanaseButcaru/angular-click-x
 */

if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports) {
	module.exports = 'tb.clickX';
}

angular.module('tb.clickX', [])

	.provider('clickXConfig', function() {
		window.clickX = {};
		this.activeClass = 'click-x-active';
		this.isMobile = getDeviceType();
		this.swipeDetect = {
			//required min distance traveled to be considered swipe (px)
			threshold: 50,
			//max distance allowed at the same time in perpendicular direction (px)
			restraint: 50,
			//max time allowed to travel that distance (ms)
			allowedTime: 400
		};

		this.setActiveClass = function(className) {
			if (angular.isString(className)) {
				this.activeClass = className;
			}
		}

		this.configSwipeDetection = function(swipeOptions) {
			if (angular.isObject(swipeOptions)) {
				angular.forEach(swipeOptions, function(optionValue, optionName) {
					if (this.swipeDetect.hasOwnProperty(optionName) && angular.isNumber(optionValue)) {
						this.swipeDetect[optionName] = optionValue;
					}
				}, this);
			}
		}

		this.$get = function() {
			return this;
		}

		//helperFn: getDeviceType
		//source: https://github.com/kaimallea/isMobile/blob/master/isMobile.js
		function getDeviceType() {
			var isMobile = false,
				mobileList = {
					apple_phone: /iPhone/i,
			        apple_ipod: /iPod/i,
			        apple_tablet: /iPad/i,
			        android_phone: /(?=.*\bAndroid\b)(?=.*\bMobile\b)/i, // Match 'Android' AND 'Mobile'
			        android_tablet: /Android/i,
			        amazon_phone: /(?=.*\bAndroid\b)(?=.*\bSD4930UR\b)/i,
			        amazon_tablet: /(?=.*\bAndroid\b)(?=.*\b(?:KFOT|KFTT|KFJWI|KFJWA|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|KFARWI|KFASWI|KFSAWI|KFSAWA)\b)/i,
			        windows_phone: /IEMobile/i,
			        windows_tablet: /(?=.*\bWindows\b)(?=.*\bARM\b)/i, // Match 'Windows' AND 'ARM'
			        other_blackberry: /BlackBerry/i,
			        other_blackberry_10: /BB10/i,
			        other_opera: /Opera Mini/i,
			        other_chrome: /(CriOS|Chrome)(?=.*\bMobile\b)/i,
			        other_firefox: /(?=.*\bFirefox\b)(?=.*\bMobile\b)/i, // Match 'Firefox' AND 'Mobile'
			   	};

		   	angular.forEach(mobileList, function(regex, type) {
		   		if (isMobile) return false;
		   		else isMobile = regex.test(navigator.userAgent);
		   	});

		   	return isMobile;
		}
	})

	.directive('clickX', ['$parse', '$window', 'clickXConfig', function($parse, $window, cx) {
		return {
			priority: 0,
			restrict: 'A',

			link: function(scope, el, attrs) {
				var element = el[0],
					action = $parse(attrs.clickX),
					activeClass = cx.activeClass,
					swipe = cx.swipeDetect;

				if (cx.isMobile) {
					element.addEventListener('touchstart', actionPrepare);
					element.addEventListener('touchend', actionLaunch);
					element.addEventListener('touchcancel', actionCancel);
				}
				else {
					element.addEventListener('mousedown', actionPrepare);
					element.addEventListener('mouseup', actionLaunch);
					$window.addEventListener('mouseup', bindWindowMouseUp);
					element.addEventListener('mouseenter', bindElementMouseEnter);
					element.addEventListener('mouseleave', bindElementMouseLeave);
				}

				element.addEventListener('click', stopClickAction);

				//fn: actionPrepare
				//desc: on mouse button / finger press.
				function actionPrepare(e) {
					if (attrs.disabled === true || angular.isDefined(attrs.disabled)) {
						e.preventDefault();
					}

					if (cx.isMobile) {
						element.classList.add(activeClass);
						setElemenetStart(e.changedTouches[0]);
					}
					else {
						if ($window.clickX.hasOwnProperty('startTarget')) {
							if (isSameTarget(e, true)) {
								element.classList.add(activeClass);
							}
						}
						else {
							element.classList.add(activeClass);
							setElemenetStart(e);
						}
					}
				}

				//fn: actionLaunch
				//desc: on mouse button / finger release.
				function actionLaunch(e) {
					element.classList.remove(activeClass);

					if (isSwipe(e)) {
						return false;
					}
					else if ( ! isSameTarget(e)) {
						return false;
					}
					else if ((attrs.disabled === false || ! angular.isDefined(attrs.disabled))) {
						scope.$apply(function() {
							action(scope, {$event: e});
						});
					}
				}

				//fn: actionCancel
				//desc: on mousel leave / touch cancel.
				function actionCancel(e) {
					element.classList.remove(activeClass);
				}

				//helperFn: isSameTarget
				//desc: before launch, check if the action is released upon same target or its child nodes.
				function isSameTarget(e, justCheck) {
					var startTarget, endClientX, endClientY, endTarget,
						isChild = false, isEqual = false, isSameTarget = false,
						startPosition,  endPosition;

					if (cx.isMobile) {
						endClientX = e.changedTouches[0].clientX;
						endClientY = e.changedTouches[0].clientY;
						startTarget = element;
					}
					else if ($window.hasOwnProperty('clickX')) {
						endClientX = e.clientX;
						endClientY = e.clientY;
						startTarget = $window.clickX.startTarget;
					}
					else {
						return isSameTarget;
					}

					endTarget = document.elementFromPoint(endClientX, endClientY);
					isChild = startTarget.contains(endTarget);
					isEqual = startTarget.isEqualNode(endTarget);
					isSameTarget = isChild || isEqual;

					//check if target position remains the same
					//if diff, then a scroll happend
					if (isSameTarget) {
						startPosition = $window.clickX.startPosition;
						endPosition = getElementPosition($window.clickX.startTarget);

						if ( ! angular.equals(startPosition, endPosition)) {
							isSameTarget = false;
						}
					}

					if ( ! justCheck) {
						clickXWindowClean();
					}

					return isSameTarget;
				}

				//helperFn: isSwipe
				//source: http://www.javascriptkit.com/javatutors/touchevents2.shtml
				function isSwipe(e) {
					var pageX, pageY, distX, distY,
						elapsedTime, swipedir = 'none', isSwipe = false;

					if (cx.isMobile) {
						pageX = e.changedTouches[0].pageX;
						pageY = e.changedTouches[0].pageY;
					}
					else {
						pageX = e.pageX;
						pageY = e.pageY;
					}

					//get horizontal & vertical dist traveled by finger while in contact with surface
					distX = pageX - $window.clickX.swipePosition.startX;
					distY = pageY - $window.clickX.swipePosition.startY;
					elapsedTime = new Date().getTime() - $window.clickX.swipePosition.startTime;

					//check...
					if (elapsedTime <= swipe.allowedTime) {
						//... horizontal swipe condition
						if (Math.abs(distX) >= swipe.threshold && Math.abs(distY) <= swipe.restraint){
							//if dist traveled is negative, it indicates left swipe
							//swipedir = (distX < 0) ? 'left' : 'right';
							isSwipe = true;
						}
						//..vertical swipe condition
						else if (Math.abs(distY) >= swipe.threshold && Math.abs(distX) <= swipe.restraint){
							//if dist traveled is negative, it indicates up swipe
							//swipedir = (distY < 0)? 'up' : 'down';
							isSwipe = true;
						}
					}

					return isSwipe;
				}

				//helperFn: bindWindowMouseUp
				function bindWindowMouseUp(e){
					if ($window.clickX.hasOwnProperty('startTarget')) {
						if ( ! isSameTarget(e, true)) {
							clickXWindowClean();
						}
					}
				}

				//helperFn: bindElementMouseEnter
				function bindElementMouseEnter(e){
					if ($window.clickX.hasOwnProperty('startTarget')) {
						actionPrepare(e);
					}
				}

				//helperFn: bindElementMouseLeave
				function bindElementMouseLeave(e){
					if($window.clickX.hasOwnProperty('startTarget')) {
						actionCancel(e);
					}
				}

				//helperFn: stopClickAction
				function stopClickAction(e) {
					e.preventDefault();
				}

				//helperFn: clickXClean
				function clickXWindowClean() {
					for (var prop in $window.clickX) {
						delete $window.clickX[prop];
					}
				}

				//helperFn: getElementPosition
				function getElementPosition(elem) {
					return {
						top: elem.getBoundingClientRect().top,
						left: elem.getBoundingClientRect().left,
						right: elem.getBoundingClientRect().right
					};
				}

				//helperFn: setElemenetStart
				//desc: get element & calculate/get its position
				function setElemenetStart(position){
					$window.clickX.startTarget = document.elementFromPoint(position.clientX, position.clientY);
					$window.clickX.startPosition = getElementPosition($window.clickX.startTarget);
					$window.clickX.swipePosition = {
						startX: position.pageX,
						startY: position.pageY,
						startTime: new Date().getTime()
					};
				}

				//helperFn: getClickXParent
				//desc: determine the parent node with click-x attr
				function getClickXParent(childEl) {
					var parentEl = childEl.parentNode;

					while (parentEl.hasAttribute('click-x') === false) {
						parentEl = parentEl.parentNode;
					}

					return parentEl;
				}

				//clean up!
				scope.$on('$destroy', function() {
					if (cx.isMobile) {
						element.removeEventListener('touchstart', actionPrepare);
						element.removeEventListener('touchend', actionLaunch);
						element.removeEventListener('touchcancel', actionCancel);
					}
					else {
						element.removeEventListener('mousedown', actionPrepare);
						element.removeEventListener('mouseup', actionLaunch);
						$window.removeEventListener('mouseup', bindWindowMouseUp);
						element.removeEventListener('mouseenter', bindElementMouseEnter);
						element.removeEventListener('mouseleave', bindElementMouseLeave);
					}

					element.removeEventListener('click', stopClickAction);
					clickXWindowClean();
				});
			}
		}
	}]);
<|endoftext|>


<commit_before>import { graphql } from "react-apollo";
import compose from "lodash/fp/compose";
import { chunk } from "lodash";
import gql from "graphql-tag";
import pascalCase from "pascal-case";
import { withHandlers } from "recompose";
import invalidateQueriesOfTypes from "../utils/invalidateQueriesOfTypes";
import generateFragmentWithFields from "../utils/generateFragmentWithFields";
import { SAFE_UPSERT_PAGE_SIZE } from "../constants";

/**
 * withUpsert
 * @param {string | gql fragment} nameOrFragment supply either a name or a top-level fragment
 * @param {options} options
 * @typedef {object} options
 * @property {string} mutationName - optional rename of the default upsert function withXXXX to whatever you want
 * @property {[queryNameStrings]} refetchQueries -
 * @property {boolean} showError - default=true -- whether or not to show a default error message on failure
 * @property {obj | function} extraMutateArgs - obj or function that returns obj to get passed to the actual mutation call
 * @property {[string]} invalidate - array of model types to invalidate after the mutate
 * @property {boolean} asFunction - if true, this gives you back a function you can call directly instead of a HOC
 * @property {string} idAs - if not using a fragment, you get an id field back as default. But, if the record doesn't have an id field, and instead has a 'code', you can set idAs: 'code'
 * @property {boolean} forceCreate - sometimes the thing you're creating won't have an id field (it might have a code or something else as its primary key). This lets you override the default behavior of updating if no id is found
 * @property {boolean} forceUpdate - sometimes the thing you're updating might have an id field. This lets you override that. This lets you override the default behavior of creating if an id is found
 * @property {boolean} excludeResults - don't fetch back result entities after update or create
 * @return upsertXXXX function that takes an object or array of objects to upsert. It returns a promise resolving to an array of created/updated outputs
 */

export default function withUpsert(nameOrFragment, options = {}) {
  const {
    mutationName,
    extraMutateArgs = {},
    invalidate,
    asFunction,
    idAs,
    forceCreate: topLevelForceCreate,
    forceUpdate: topLevelForceUpdate,
    client,
    refetchQueries,
    showError = true,
    excludeResults = false,
    ...rest
  } = options;
  let fragment = typeof nameOrFragment === "string" ? null : nameOrFragment;
  if (Array.isArray(fragment)) {
    fragment = generateFragmentWithFields(...fragment);
  }
  const name = fragment
    ? fragment.definitions[0].typeCondition.name.value
    : nameOrFragment;

  // const {fragment, extraMutateArgs} = options
  const fragName = fragment && fragment.definitions[0].name.value;
  const pascalCaseName = pascalCase(name);
  const createName = `create${pascalCaseName}`;
  const resultString = `${
    !excludeResults
      ? `results {
    ${fragName ? `...${fragName}` : idAs || "id"}
  }`
      : ""
  }
  totalResults`;
  /* eslint-disable */
  const createMutation = gql`
    mutation ${createName}($input: [${createName}Input]) {
      ${createName}(input: $input) {
        createdItemsCursor {
          ${resultString}
        }
      }
    }
    ${fragment ? fragment : ``}
  `;
  /* eslint-enable */

  const updateName = `update${pascalCaseName}`;
  /* eslint-disable */
  const updateMutation = gql`
    mutation ${updateName}($input: [${updateName}Input]) {
      ${updateName}(input: $input) {
        updatedItemsCursor {
          ${resultString}
        }
      }
    }
    ${fragment ? fragment : ``}
  `;
  /* eslint-enable */

  const getExtraMutateArgs = (...args) => {
    const runtimeOptions = args && args[1];
    const extraMutateArgsToUse =
      runtimeOptions && runtimeOptions.extraMutateArgs
        ? runtimeOptions.extraMutateArgs
        : extraMutateArgs;
    let extraArgs = {};
    if (invalidate) {
      if (!Array.isArray(invalidate))
        throw new Error(
          'The invalidate option should be an array of model name strings eg ["user", "workflowRun"]'
        );
      extraArgs = { options: invalidateQueriesOfTypes(invalidate) };
    }
    if (typeof extraMutateArgsToUse === "function") {
      extraArgs = { ...extraArgs, ...extraMutateArgsToUse(...args) };
    } else {
      extraArgs = { ...extraArgs, ...extraMutateArgsToUse };
    }
    return extraArgs;
  };

  if (asFunction) {
    if (!client)
      return console.error(
        "You need to pass the apollo client to withUpsert if using as a function"
      );
    return async function upsert(valueOrValues, options) {
      const values = Array.isArray(valueOrValues)
        ? valueOrValues
        : [valueOrValues];
      if (!valueOrValues || !values.length) return [];
      let isUpdate = !!(values[0].id || values[0].code);
      if (topLevelForceCreate) {
        isUpdate = false;
      }
      if (topLevelForceUpdate) {
        isUpdate = true;
      }
      const upsertFn = values => {
        return client.mutate({
          mutation: isUpdate ? updateMutation : createMutation,
          variables: {
            input: values
          },
          ...rest,
          ...options
        });
      };

      const results = await getSafeUpsertResults(
        upsertFn,
        values,
        createName,
        updateName
      );
      return excludeResults ? results.totalResults : results;

      // DEPRECATED
      // return client
      //   .mutate({
      //     mutation: isUpdate ? updateMutation : createMutation,
      //     name: "createDataFile",
      //     variables: {
      //       input: values
      //     },
      //     ...rest,
      //     ...options
      //   })
      //   .then(function(res) {
      //     const resultInfo =
      //       res.data[isUpdate ? updateName : createName][
      //         isUpdate ? "updatedItemsCursor" : "createdItemsCursor"
      //       ];
      //     return Promise.resolve(
      //       excludeResults ? resultInfo.totalResults : resultInfo.results
      //     );
      //   });
    };
  }

  return compose(
    graphql(createMutation, {
      name: "createItem",
      props: ({ createItem }) => {
        return {
          createItem: (...args) => {
            const input = args[0];
            const { update } = args[1] || {};
            return createItem({
              variables: {
                input
              },
              update,
              refetchQueries,
              ...getExtraMutateArgs(...args)
            });
          }
        };
      },
      ...rest
    }),
    graphql(updateMutation, {
      name: "updateItem",
      props: ({ updateItem }) => {
        return {
          updateItem: (...args) => {
            const input = args[0];
            const { update } = args[1] || {};
            return updateItem({
              variables: {
                input
              },
              update,
              refetchQueries,
              ...getExtraMutateArgs(...args)
            });
          }
        };
      },
      ...rest
    }),
    withHandlers({
      createItem: undefined,
      updateItem: undefined,
      [mutationName || `upsert${pascalCaseName}`]: async ownProps => (
        valueOrValues,
        ...rest
      ) => {
        const { createItem, updateItem, apolloOptions = {} } = ownProps;
        const {
          forceCreate = topLevelForceCreate,
          forceUpdate = topLevelForceUpdate
        } = apolloOptions;
        //the upsertXXX function is the only thing we should be calling
        const values = Array.isArray(valueOrValues)
          ? valueOrValues
          : [valueOrValues];
        if (!valueOrValues || !values.length) return [];
        let isUpdate = !!(values[0].id || values[0].code);
        if (forceCreate) {
          isUpdate = false;
        }
        if (forceUpdate) {
          isUpdate = true;
        }
        const upsertFn = isUpdate ? updateItem : createItem;
        try {
          return getSafeUpsertResults(
            upsertFn,
            values,
            createName,
            updateName,
            rest
          );
        } catch (e) {
          if (showError) {
            window.toastr &&
              window.toastr.error(
                `Error ${isUpdate ? "updating" : "creating"} ${pascalCaseName}`
              );
            console.error(`withUpsert ${pascalCaseName} Error:`, e);
          }
          throw e; //rethrow the error so it can be caught again if need be
        }
        // DEPRECATED
        // return (isUpdate ? updateItem : createItem)(values, ...rest)
        //   .then(function(res) {
        //     const returnInfo =
        //       res.data[isUpdate ? updateName : createName][
        //         isUpdate ? "updatedItemsCursor" : "createdItemsCursor"
        //       ];
        //     let results = returnInfo.results;
        //     results = [...results];
        //     results.totalResults = returnInfo.totalResults;
        //     return Promise.resolve(results);
        //   })
        //   .catch(e => {
        //     if (showError) {
        //       window.toastr &&
        //         window.toastr.error(
        //           `Error ${
        //             isUpdate ? "updating" : "creating"
        //           } ${pascalCaseName}`
        //         );
        //       console.error(`withUpsert ${pascalCaseName} Error:`, e);
        //     }
        //     throw e; //rethrow the error so it can be caught again if need be
        //   });
      }
    })
  );
}

async function getSafeUpsertResults(
  upsertFn,
  values,
  createName,
  updateName,
  userOptions
) {
  let isUpdate = !!(values[0].id || values[0].code);

  let results = [];
  const addToResults = res => {
    const returnInfo =
      res.data[isUpdate ? updateName : createName][
        isUpdate ? "updatedItemsCursor" : "createdItemsCursor"
      ];
    results = results.concat(returnInfo.results);
    results.totalResults = returnInfo.totalResults;
  };
  if (values.length > SAFE_UPSERT_PAGE_SIZE) {
    const groupedVals = chunk(values, SAFE_UPSERT_PAGE_SIZE);
    for (const valGroup of groupedVals) {
      addToResults(await upsertFn(valGroup, ...userOptions));
    }
  } else {
    addToResults(await upsertFn(values, ...userOptions));
  }
  return results;
}
<commit_msg>Fixed async promise handler in withupsert<commit_after>import { graphql } from "react-apollo";
import compose from "lodash/fp/compose";
import { chunk } from "lodash";
import gql from "graphql-tag";
import pascalCase from "pascal-case";
import { withHandlers } from "recompose";
import invalidateQueriesOfTypes from "../utils/invalidateQueriesOfTypes";
import generateFragmentWithFields from "../utils/generateFragmentWithFields";
import { SAFE_UPSERT_PAGE_SIZE } from "../constants";

/**
 * withUpsert
 * @param {string | gql fragment} nameOrFragment supply either a name or a top-level fragment
 * @param {options} options
 * @typedef {object} options
 * @property {string} mutationName - optional rename of the default upsert function withXXXX to whatever you want
 * @property {[queryNameStrings]} refetchQueries -
 * @property {boolean} showError - default=true -- whether or not to show a default error message on failure
 * @property {obj | function} extraMutateArgs - obj or function that returns obj to get passed to the actual mutation call
 * @property {[string]} invalidate - array of model types to invalidate after the mutate
 * @property {boolean} asFunction - if true, this gives you back a function you can call directly instead of a HOC
 * @property {string} idAs - if not using a fragment, you get an id field back as default. But, if the record doesn't have an id field, and instead has a 'code', you can set idAs: 'code'
 * @property {boolean} forceCreate - sometimes the thing you're creating won't have an id field (it might have a code or something else as its primary key). This lets you override the default behavior of updating if no id is found
 * @property {boolean} forceUpdate - sometimes the thing you're updating might have an id field. This lets you override that. This lets you override the default behavior of creating if an id is found
 * @property {boolean} excludeResults - don't fetch back result entities after update or create
 * @return upsertXXXX function that takes an object or array of objects to upsert. It returns a promise resolving to an array of created/updated outputs
 */

export default function withUpsert(nameOrFragment, options = {}) {
  const {
    mutationName,
    extraMutateArgs = {},
    invalidate,
    asFunction,
    idAs,
    forceCreate: topLevelForceCreate,
    forceUpdate: topLevelForceUpdate,
    client,
    refetchQueries,
    showError = true,
    excludeResults = false,
    ...rest
  } = options;
  let fragment = typeof nameOrFragment === "string" ? null : nameOrFragment;
  if (Array.isArray(fragment)) {
    fragment = generateFragmentWithFields(...fragment);
  }
  const name = fragment
    ? fragment.definitions[0].typeCondition.name.value
    : nameOrFragment;

  // const {fragment, extraMutateArgs} = options
  const fragName = fragment && fragment.definitions[0].name.value;
  const pascalCaseName = pascalCase(name);
  const createName = `create${pascalCaseName}`;
  const resultString = `${
    !excludeResults
      ? `results {
    ${fragName ? `...${fragName}` : idAs || "id"}
  }`
      : ""
  }
  totalResults`;
  /* eslint-disable */
  const createMutation = gql`
    mutation ${createName}($input: [${createName}Input]) {
      ${createName}(input: $input) {
        createdItemsCursor {
          ${resultString}
        }
      }
    }
    ${fragment ? fragment : ``}
  `;
  /* eslint-enable */

  const updateName = `update${pascalCaseName}`;
  /* eslint-disable */
  const updateMutation = gql`
    mutation ${updateName}($input: [${updateName}Input]) {
      ${updateName}(input: $input) {
        updatedItemsCursor {
          ${resultString}
        }
      }
    }
    ${fragment ? fragment : ``}
  `;
  /* eslint-enable */

  const getExtraMutateArgs = (...args) => {
    const runtimeOptions = args && args[1];
    const extraMutateArgsToUse =
      runtimeOptions && runtimeOptions.extraMutateArgs
        ? runtimeOptions.extraMutateArgs
        : extraMutateArgs;
    let extraArgs = {};
    if (invalidate) {
      if (!Array.isArray(invalidate))
        throw new Error(
          'The invalidate option should be an array of model name strings eg ["user", "workflowRun"]'
        );
      extraArgs = { options: invalidateQueriesOfTypes(invalidate) };
    }
    if (typeof extraMutateArgsToUse === "function") {
      extraArgs = { ...extraArgs, ...extraMutateArgsToUse(...args) };
    } else {
      extraArgs = { ...extraArgs, ...extraMutateArgsToUse };
    }
    return extraArgs;
  };

  if (asFunction) {
    if (!client)
      return console.error(
        "You need to pass the apollo client to withUpsert if using as a function"
      );
    return async function upsert(valueOrValues, options) {
      const values = Array.isArray(valueOrValues)
        ? valueOrValues
        : [valueOrValues];
      if (!valueOrValues || !values.length) return [];
      let isUpdate = !!(values[0].id || values[0].code);
      if (topLevelForceCreate) {
        isUpdate = false;
      }
      if (topLevelForceUpdate) {
        isUpdate = true;
      }
      const upsertFn = values => {
        return client.mutate({
          mutation: isUpdate ? updateMutation : createMutation,
          variables: {
            input: values
          },
          ...rest,
          ...options
        });
      };

      const results = await getSafeUpsertResults(
        upsertFn,
        values,
        createName,
        updateName
      );
      return excludeResults ? results.totalResults : results;

      // DEPRECATED
      // return client
      //   .mutate({
      //     mutation: isUpdate ? updateMutation : createMutation,
      //     name: "createDataFile",
      //     variables: {
      //       input: values
      //     },
      //     ...rest,
      //     ...options
      //   })
      //   .then(function(res) {
      //     const resultInfo =
      //       res.data[isUpdate ? updateName : createName][
      //         isUpdate ? "updatedItemsCursor" : "createdItemsCursor"
      //       ];
      //     return Promise.resolve(
      //       excludeResults ? resultInfo.totalResults : resultInfo.results
      //     );
      //   });
    };
  }

  return compose(
    graphql(createMutation, {
      name: "createItem",
      props: ({ createItem }) => {
        return {
          createItem: (...args) => {
            const input = args[0];
            const { update } = args[1] || {};
            return createItem({
              variables: {
                input
              },
              update,
              refetchQueries,
              ...getExtraMutateArgs(...args)
            });
          }
        };
      },
      ...rest
    }),
    graphql(updateMutation, {
      name: "updateItem",
      props: ({ updateItem }) => {
        return {
          updateItem: (...args) => {
            const input = args[0];
            const { update } = args[1] || {};
            return updateItem({
              variables: {
                input
              },
              update,
              refetchQueries,
              ...getExtraMutateArgs(...args)
            });
          }
        };
      },
      ...rest
    }),
    withHandlers({
      createItem: undefined,
      updateItem: undefined,
      [mutationName || `upsert${pascalCaseName}`]: ownProps => (
        valueOrValues,
        ...rest
      ) => {
        const { createItem, updateItem, apolloOptions = {} } = ownProps;
        const {
          forceCreate = topLevelForceCreate,
          forceUpdate = topLevelForceUpdate
        } = apolloOptions;
        //the upsertXXX function is the only thing we should be calling
        const values = Array.isArray(valueOrValues)
          ? valueOrValues
          : [valueOrValues];
        if (!valueOrValues || !values.length) return [];
        let isUpdate = !!(values[0].id || values[0].code);
        if (forceCreate) {
          isUpdate = false;
        }
        if (forceUpdate) {
          isUpdate = true;
        }
        const upsertFn = isUpdate ? updateItem : createItem;
        try {
          return getSafeUpsertResults(
            upsertFn,
            values,
            createName,
            updateName,
            rest
          );
        } catch (e) {
          if (showError) {
            window.toastr &&
              window.toastr.error(
                `Error ${isUpdate ? "updating" : "creating"} ${pascalCaseName}`
              );
            console.error(`withUpsert ${pascalCaseName} Error:`, e);
          }
          throw e; //rethrow the error so it can be caught again if need be
        }
        // DEPRECATED
        // return (isUpdate ? updateItem : createItem)(values, ...rest)
        //   .then(function(res) {
        //     const returnInfo =
        //       res.data[isUpdate ? updateName : createName][
        //         isUpdate ? "updatedItemsCursor" : "createdItemsCursor"
        //       ];
        //     let results = returnInfo.results;
        //     results = [...results];
        //     results.totalResults = returnInfo.totalResults;
        //     return Promise.resolve(results);
        //   })
        //   .catch(e => {
        //     if (showError) {
        //       window.toastr &&
        //         window.toastr.error(
        //           `Error ${
        //             isUpdate ? "updating" : "creating"
        //           } ${pascalCaseName}`
        //         );
        //       console.error(`withUpsert ${pascalCaseName} Error:`, e);
        //     }
        //     throw e; //rethrow the error so it can be caught again if need be
        //   });
      }
    })
  );
}

async function getSafeUpsertResults(
  upsertFn,
  values,
  createName,
  updateName,
  userOptions
) {
  let isUpdate = !!(values[0].id || values[0].code);

  let results = [];
  const addToResults = res => {
    const returnInfo =
      res.data[isUpdate ? updateName : createName][
        isUpdate ? "updatedItemsCursor" : "createdItemsCursor"
      ];
    results = results.concat(returnInfo.results);
    results.totalResults = returnInfo.totalResults;
  };
  if (values.length > SAFE_UPSERT_PAGE_SIZE) {
    const groupedVals = chunk(values, SAFE_UPSERT_PAGE_SIZE);
    for (const valGroup of groupedVals) {
      addToResults(await upsertFn(valGroup, ...userOptions));
    }
  } else {
    addToResults(await upsertFn(values, ...userOptions));
  }
  return results;
}
<|endoftext|>


<commit_before>// annyang
// version : 0.0.1
// author : Tal Ater
// license : GNU v2
// TalAter.com

(function () {
  /*global webkitSpeechRecognition */
  "use strict";

  var commandsList;
  var root = this;

  // Check browser support
  if (!('webkitSpeechRecognition' in root)) {
    //@TODO: Display friendlier message to the primitive user
    root.annyang = null;
    return null;
  }

  // The command matching code is based on Backbone.Router by Jeremy Ashkenas, under the MIT license.
  var optionalParam = /\((.*?)\)/g;
  var namedParam    = /(\(\?)?:\w+/g;
  var splatParam    = /\*\w+/g;
  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g;
  var commandToRegExp = function(command) {
    command = command.replace(escapeRegExp, '\\$&')
                 .replace(optionalParam, '(?:$1)?')
                 .replace(namedParam, function(match, optional) {
                   return optional ? match : '([^\\s]+)';
                 })
                 .replace(splatParam, '(.*?)');
    return new RegExp('^' + command + '$', 'i');
  };

  // initiate webkitSpeechRecognition
  var recognition = new webkitSpeechRecognition();
  recognition.maxAlternatives = 5;
  recognition.continuous = true;
  /* @TODO: Add language support */
  recognition.lang = "en";

  recognition.onstart   = function()      { /* @TODO: Show visual cue that voice recognition is happening */ };

  recognition.onerror   = function()      { /* @TODO: handle errors */ };

  recognition.onend     = function()      { /* @TODO: restart speech recognition */ };

  recognition.onresult  = function(event) {
    var results = event.results[event.resultIndex];
    var commandText;
    for (var i = 0; i<results.length; i++) {
      commandText = results[i].transcript.trim();
      window.console.log('recognized: '+commandText);

      for (var j in commandsList) {
        var result = commandsList[j].command.exec(commandText);
        if (result) {
          commandsList[j].callback.apply(this, result.slice(1));
          return true;
        }
      }
    }
    // @TODO: If no command matched, consider asking for user intervention.
    return false;
  };

  root.annyang = {
    init: function(commands) {
      commandsList = [];
      var cb,
          command;
      for (var i in commands) {
        cb = root[commands[i]] || commands[i];
        if (typeof cb !== 'function') {
          continue;
        }
        //convert command to regex
        command = commandToRegExp(i);

        //@TODO: Check if there are issues with context for the callback
        commandsList.push({ command: command, callback: cb });
      }
    },

    start: function() {
      /* @TODO: Check permission to use the mic, and alert user to permission problem */
      recognition.start();
    }
  };

}).call(this);
<commit_msg>Removed all @TODO comments. Added them to GitHub issue tracker.<commit_after>// annyang
// version : 0.0.1
// author : Tal Ater
// license : GNU v2
// TalAter.com

(function () {
  /*global webkitSpeechRecognition */
  "use strict";

  var commandsList;
  var root = this;

  // Check browser support
  if (!('webkitSpeechRecognition' in root)) {
    root.annyang = null;
    return null;
  }

  // The command matching code is based on Backbone.Router by Jeremy Ashkenas, under the MIT license.
  var optionalParam = /\((.*?)\)/g;
  var namedParam    = /(\(\?)?:\w+/g;
  var splatParam    = /\*\w+/g;
  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g;
  var commandToRegExp = function(command) {
    command = command.replace(escapeRegExp, '\\$&')
                 .replace(optionalParam, '(?:$1)?')
                 .replace(namedParam, function(match, optional) {
                   return optional ? match : '([^\\s]+)';
                 })
                 .replace(splatParam, '(.*?)');
    return new RegExp('^' + command + '$', 'i');
  };

  // initiate webkitSpeechRecognition
  var recognition = new webkitSpeechRecognition();
  recognition.maxAlternatives = 5;
  recognition.continuous = true;
  recognition.lang = "en";

  recognition.onstart   = function()      { };

  recognition.onerror   = function()      { };

  recognition.onend     = function()      { };

  recognition.onresult  = function(event) {
    var results = event.results[event.resultIndex];
    var commandText;
    for (var i = 0; i<results.length; i++) {
      commandText = results[i].transcript.trim();
      window.console.log('recognized: '+commandText);

      for (var j in commandsList) {
        var result = commandsList[j].command.exec(commandText);
        if (result) {
          commandsList[j].callback.apply(this, result.slice(1));
          return true;
        }
      }
    }
    return false;
  };

  root.annyang = {
    init: function(commands) {
      commandsList = [];
      var cb,
          command;
      for (var i in commands) {
        cb = root[commands[i]] || commands[i];
        if (typeof cb !== 'function') {
          continue;
        }
        //convert command to regex
        command = commandToRegExp(i);

        commandsList.push({ command: command, callback: cb });
      }
    },

    start: function() {
      recognition.start();
    }
  };

}).call(this);
<|endoftext|>