diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..83fb5a7 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "root": true, + "extends": ["conversio"] +} diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index 1f2497f..0000000 --- a/.jscsrc +++ /dev/null @@ -1,18 +0,0 @@ -{ - "preset": "airbnb", - "disallowMultipleVarDecl": { - "allExcept": ["require"] - }, - "maxErrors": "Infinity", - "requireTrailingComma": null, - "requirePaddingNewLinesAfterBlocks": null, - "requirePaddingNewLinesBeforeLineComments": null, - "requireSpacesInsideObjectBrackets": "all", - "disallowSpacesInFunctionExpression": { - "beforeOpeningRoundBrace": true - }, - "disallowSpacesInAnonymousFunctionExpression": { - "beforeOpeningRoundBrace": true - }, - "requireSpacesInAnonymousFunctionExpression": null -} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 3ceaf90..0000000 --- a/.jshintrc +++ /dev/null @@ -1,73 +0,0 @@ -{ - /* - * ENVIRONMENTS - * ================= - */ - - // Define globals exposed by modern browsers. - "browser": true, - - // Define globals exposed by jQuery. - "jquery": true, - - // Define globals exposed by Node.js. - "node": true, - - "esnext": true, - - /* - * ENFORCING OPTIONS - * ================= - */ - - // Force all variable names to use either camelCase style or UPPER_CASE - // with underscores. - "camelcase": false, - - // Prohibit use of == and != in favor of === and !==. - "eqeqeq": true, - - // Enforce tab width of 2 spaces. - "indent": 2, - - // Prohibit use of a variable before it is defined. - "latedef": true, - - // Enforce line length to 80 characters - "maxlen": 100, - - // Require capitalized names for constructor functions. - "newcap": true, - - // Enforce use of single quotation marks for strings. - "quotmark": "single", - - // Enforce placing 'use strict' at the top function scope - "strict": true, - - // Prohibit use of explicitly undeclared variables. - "undef": true, - - // Warn when variables are defined but never used. - "unused": true, - - /* - * RELAXING OPTIONS - * ================= - */ - - // Suppress warnings about == null comparisons. - "eqnull": true, - - "mocha": true, - - "globals": { - "jquery": false, - "$": false, - "Morris": false, - "alert": false, - "alertify": false, - "printStackTrace": false, - "Spinner": false - } -} diff --git a/.travis.yml b/.travis.yml index 199951b..9c139f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: node_js node_js: -- '4' - '6' - '8' deploy: diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b4e9d18 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +3.0.0 +===== + + - Removed all callbacks, only Promise is supported now. + - Removed `authorise` method in favor of `authorize`. + - Removed `callback` method, `verify` will now throw an Error. + - Removed the logger, use `DEBUG=node-bigcommerce:*` as an environment variable to get debug messages. + - Dropped support of node-v4 for missing `class` implementation. + - Removed [catalogue](https://github.com/getconversio/node-bigcommerce/pull/18) hack, please use `apiVersion` configuration property. diff --git a/README.md b/README.md index 2bffbed..7d424d9 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,8 @@ # Bigcommerce for Node.js -[![Code Climate](https://codeclimate.com/repos/54b673f7e30ba0704d01ed1d/badges/1745c700ed531663cc86/gpa.svg)](https://codeclimate.com/repos/54b673f7e30ba0704d01ed1d/feed) [![Test Coverage](https://codeclimate.com/repos/54b673f7e30ba0704d01ed1d/badges/1745c700ed531663cc86/coverage.svg)](https://codeclimate.com/repos/54b673f7e30ba0704d01ed1d/feed) - -[![Build Status](https://travis-ci.org/getconversio/node-bigcommerce.svg?branch=master)](https://travis-ci.org/getconversio/node-bigcommerce) [![bitHound Overall Score](https://www.bithound.io/github/getconversio/node-bigcommerce/badges/score.svg)](https://www.bithound.io/github/getconversio/node-bigcommerce) [![bitHound Dependencies](https://www.bithound.io/github/getconversio/node-bigcommerce/badges/dependencies.svg)](https://www.bithound.io/github/getconversio/node-bigcommerce/master/dependencies/npm) [![bitHound Dev Dependencies](https://www.bithound.io/github/getconversio/node-bigcommerce/badges/devDependencies.svg)](https://www.bithound.io/github/getconversio/node-bigcommerce/master/dependencies/npm) [![bitHound Code](https://www.bithound.io/github/getconversio/node-bigcommerce/badges/code.svg)](https://www.bithound.io/github/getconversio/node-bigcommerce) - -A node module for authentication and use with the BigCommerce v2 API +[![Build Status](https://travis-ci.org/getconversio/node-bigcommerce.svg?branch=master)](https://travis-ci.org/getconversio/node-bigcommerce) +A node module for authentication and use with the BigCommerce API ## Installation @@ -15,14 +12,19 @@ To install the module using NPM: npm install node-bigcommerce ``` +Or Yarn: +``` +yarn add node-bigcommerce +``` + ## Setup Include the 'node-bigcommerce' module within your script and instantiate it with a config: ```javascript -var BigCommerce = require('node-bigcommerce'); +const BigCommerce = require('node-bigcommerce'); -var bigCommerce = new BigCommerce({ +const bigCommerce = new BigCommerce({ logLevel: 'info', clientId: '128ecf542a35ac5270a87dc740918404', secret: 'acbd18db4cc2f85cedef654fccc4a4d8', @@ -34,7 +36,7 @@ var bigCommerce = new BigCommerce({ ##### Instantiating a BigCommerce instance without a config object will result in an error -## Authorisation +## Authorization Set up your Big Commerce as above and pass the following configuration options in: @@ -50,25 +52,26 @@ Set up your Big Commerce as above and pass the following configuration options i You will be able to get your Client ID and Secret within your application setup. Below is an example using Express' routes: ```javascript -var express = require('express'), +const express = require('express'), router = express.Router(), BigCommerce = require('node-bigcommerce'); -var bigCommerce = new BigCommerce({ +const bigCommerce = new BigCommerce({ clientId: '128ecf542a35ac5270a87dc740918404', secret: 'acbd18db4cc2f85cedef654fccc4a4d8', callback: 'https://myapplication.com/auth', responseType: 'json' }); -router.get('/auth', function(req, res) { - bigCommerce.authorise(req.query, function(err, data){ - res.render('integrations/auth', { title: 'Authorised!', data: data }); - }) +router.get('/auth', (req, res, next) => { + bigCommerce.authorize(req.query) + .then(data => res.render('integrations/auth', { title: 'Authorized!', data: data }) + .catch(next); + }); }); ``` -The 'authorise' method requires the query parameters from the request to be passed. These are required to request a permanent access token which will be passed back in the data object. +The `authorize` method requires the query parameters from the request to be passed. These are required to request a permanent access token which will be passed back in the data object. An example data object: @@ -85,30 +88,30 @@ An example data object: } ``` -From this object you can store the 'access_token' for re-use when calling the Big Commerce API. +From this object you can store the `access_token` for re-use when calling the Big Commerce API. ## Load & Uninstall -The only configuration element required to use the callback method (used for both load and uninstall endpoints) is 'secret'. Below is an example using Express' routes: +The only configuration element required to use the `verify` method (used for both load and uninstall endpoints) is `secret`. Below is an example using Express' routes: ```javascript -var express = require('express'), +const express = require('express'), router = express.Router(), BigCommerce = require('node-bigcommerce'); -var bigCommerce = new BigCommerce({ +const bigCommerce = new BigCommerce({ secret: 'acbd18db4cc2f85cedef654fccc4a4d8', responseType: 'json' }); -router.get('/load', function(req, res) { - bigCommerce.callback(req.query['signed_payload'], function(err, data){ - res.render('integrations/welcome', { title: 'Welcome!', data: data }); - }) +router.get('/load', (req, res, next) => { + bigCommerce.verify(req.query['signed_payload']) + .then(data => res.render('integrations/welcome', { title: 'Welcome!', data: data }) + .catch(next); }); ``` -The 'callback' method requires the 'signed_payload' query parameter to be passed from the request. This is used to verify that the request has come from Big Commerce. The callback method returns the following object: +The `verify` method requires the `signed_payload` query parameter to be passed from the request. This is used to verify that the request has come from Big Commerce. The `verify` method returns the following object: ``` { @@ -126,7 +129,7 @@ This will allow you to automatically log the user in (if required) when BigComme ## Calling the API -The API can be called once the user has been authorised and has an access token. There is a helper for each type of request available within Big Commerce (GET, POST, PUT, DELETE). +The API can be called once the user has been authorized and has an access token. There is a helper for each type of request available within Big Commerce (GET, POST, PUT, DELETE). To make an API Request you will need the following minimum configuration: @@ -141,32 +144,32 @@ To make an API Request you will need the following minimum configuration: Parameters that are added to the url need to be escaped before they are passed as part of the path of any call: ```javascript -var path = '/products?name=' + escape('Plain T-Shirt'); +const path = '/products?name=' + escape('Plain T-Shirt'); ``` ### GET -The 'Get' call requires a path and callback: get(path, callback): +The `Get` call requires a path: get(path): ```javascript -var BigCommerce = require('node-bigcommerce'); +const BigCommerce = require('node-bigcommerce'); -var bigCommerce = new BigCommerce({ +const bigCommerce = new BigCommerce({ clientId: '128ecf542a35ac5270a87dc740918404' accessToken: '9df3b01c60df20d13843841ff0d4482c', responseType: 'json' }); -bigCommerce.get('/products', function(err, data, response){ - // Catch any errors, or handle the data returned - // The response object is passed back for convenience -}); +bigCommerce.get('/products') + .then(data => { + // Catch any errors, or handle the data returned + }); ``` ### POST & PUT -The 'POST' & 'PUT' calls requires a path and callback with optional data to be sent: post(path, data, callback): +The 'POST' & 'PUT' calls requires a path with optional data to be sent: post(path, data): ```javascript var BigCommerce = require('node-bigcommerce'); @@ -196,114 +199,58 @@ bigCommerce.post('/products', product, function(err, data, response){ ### DELETE -The 'DELETE' call requires a path and callback with optional data to be sent: delete(path, data, callback). A delete call will not return any data and will return a response status of 204. +The 'DELETE' call requires a path: delete(path). A delete call will not return any data and will return a response status of 204. ```javascript -var BigCommerce = require('node-bigcommerce'); +const BigCommerce = require('node-bigcommerce'); -var bigCommerce = new BigCommerce({ +const bigCommerce = new BigCommerce({ clientId: '128ecf542a35ac5270a87dc740918404', accessToken: '9df3b01c60df20d13843841ff0d4482c' }); -bigCommerce.delete('/products/' + productId, null, function(err, data, response){ +bigCommerce.delete('/products/' + productId) + .then(() => { // Catch any errors, data will be null - // The response object is passed back for convenience -}); + }); ``` -## Logging - -There are 2 levels of logging which can be set in the config during instantiation. By default the logging level is set to 'errors'. For more verbose debugging the log level of 'info' can be set: - -```javascript -var BigCommerce = require('node-bigcommerce'); +## Debugging -var bigCommerce = new BigCommerce({ - logLevel: 'info', - clientId: '128ecf542a35ac5270a87dc740918404', - accessToken: '9df3b01c60df20d13843841ff0d4482c', - responseType: 'json' -}); +We use `debug`, so just run with environment variable DEBUG set to `node-bigcommerce:*` -bigCommerce.post('/products?name=' + escape('Plain T-Shirt'), null, function(err, data, response){ - // Catch any errors, data will be null - // The response object is passed back for convenience -}); +```js +$ DEBUG=node-bigcommerce:* node my_test.js ``` -We recommend you only use the log level 'info' on a development build as it logs a lot of information. - ## Response Type You may require the Big Commerce API to return data in a specific format. To return in either JSON or XML just add a 'responseType' to the config: ```javascript -var BigCommerce = require('node-bigcommerce'); +const BigCommerce = require('node-bigcommerce'); -var bigCommerce = new BigCommerce({ +const bigCommerce = new BigCommerce({ logLevel: 'info', clientId: '128ecf542a35ac5270a87dc740918404', accessToken: '9df3b01c60df20d13843841ff0d4482c', responseType: 'xml' }); -bigCommerce.post('/products?name=' + escape('Plain T-Shirt'), null, function(err, data, response){ +bigCommerce.post('/products?name=' + escape('Plain T-Shirt')) + .then(data => { // Catch any errors, data will be null - // The response object is passed back for convenience -}); + }); ``` Note that when returning in JSON the data will be parsed into an object, XML will not, and will return a string. When no response type is given the type will resort to whatever the BigCommerce default is. Webhooks can only be JSON so when dealing with the '/hooks' endpoint leave the responseType blank (or null). -## Notes - -You can instantiate the BigCommerce object within a script and re-use throughout. The config object within the BigCommerce allows the addition of other elements after the initial instantiation. For example, in a scenario when you have the authorisation and a call to the api, you can do the following: - -```javascript -var express = require('express'), - router = express.Router(), - BigCommerce = require('node-bigcommerce'); - -var bigCommerce = new BigCommerce({ - clientId: '128ecf542a35ac5270a87dc740918404', - secret: 'acbd18db4cc2f85cedef654fccc4a4d8', - callback: 'https://myapplication.com/auth' -}); - -router.get('/auth', function(req, res) { - bigCommerce.authorise(req.query, function(err, data){ - - /** - * Your code to save the access token & - * store hash to the current user - */ - - res.render('integrations/auth', { title: 'Authorised!', data: data }); - }) -}); - -router.get('/products', function(req, res) { - - /** - * Your code to get the current users access token & store hash - * and add it to the variables: var accessToken & var storeHash - */ - - bigCommerce.config.accessToken = accessToken; - bigCommerce.config.storeHash = storeHash; - bigCommerce.get('/products?min_id=3&max_id=10', function(err, data){ - res.render('integrations/products', { title: 'Product List Between 3 & 10', data: data }); - }) -}); -``` - ## Testing ``` -npm test +yarn test ``` ## Contributing diff --git a/lib/bigcommerce.js b/lib/bigcommerce.js index 5503d84..18db561 100644 --- a/lib/bigcommerce.js +++ b/lib/bigcommerce.js @@ -18,194 +18,113 @@ * } */ -var logger = require('./logger'), +const logger = require('debug')('node-bigcommerce:bigcommerce'), crypto = require('crypto'), Request = require('./request'); -var BigCommerce = function(cfg) { - // The config is required to access the API - if (!cfg) { - var error = 'Config missing. The config object is ' + - 'required to make any call to the BigCommerce API'; - logger.error(error); - throw new Error(error); - } +class BigCommerce { + constructor(config) { + if (!config) throw new Error('Config missing. The config object is required to make any call to the BigCommerce API'); - this.config = cfg; - this.logger = logger; - - // Set the log level, default: 0 - var levels = { info: 1, error: 0 }; - this.config.logLevel = this.config.logLevel ? - levels[this.config.logLevel] : 0; - logger.level = this.config.logLevel; - this.apiVersion = this.config.apiVersion || 'v2'; -}; - -BigCommerce.prototype.verify = function(signedRequest) { - if (!signedRequest) { - logger.error('The signed request is required ' + - 'to verify the call.'); - return; + this.config = config; + this.apiVersion = this.config.apiVersion || 'v2'; } - var splitRequest = signedRequest.split('.'); - - if (splitRequest.length < 2) { - logger.error('The signed request will come in ' + - 'two parts seperated by a .(full stop). this ' + - 'signed request contains less than 2 parts.'); - return; - } + verify(signedRequest) { + if (!signedRequest) throw new Error('The signed request is required to verify the call.'); - var signature = new Buffer(splitRequest[1], 'base64').toString('utf8'); - var json = new Buffer(splitRequest[0], 'base64').toString('utf8'); - var data = JSON.parse(json); + const splitRequest = signedRequest.split('.'); - logger.info('JSON: ' + json); - logger.info('Signature: ' + signature); + if (splitRequest.length < 2) { + throw new Error('The signed request will come in two parts seperated by a .(full stop). this signed request contains less than 2 parts.'); + } - var expected = crypto.createHmac('sha256', this.config.secret) - .update(json) - .digest('hex'); + const signature = new Buffer(splitRequest[1], 'base64').toString('utf8'); + const json = new Buffer(splitRequest[0], 'base64').toString('utf8'); + const data = JSON.parse(json); - logger.info('Expected Signature: ' + expected); + logger('JSON: ' + json); + logger('Signature: ' + signature); - if (expected === signature) { - logger.info('Signature is valid'); - return data; - } + const expected = crypto.createHmac('sha256', this.config.secret) + .update(json) + .digest('hex'); - logger.error('Signature is invalid'); + logger('Expected Signature: ' + expected); - return; -}; - -BigCommerce.prototype.callback = function(payload, cb) { - // Verify the self signed request - logger.info('Signed Payload:'); - logger.info(payload); + if (expected === signature) { + logger('Signature is valid'); + return data; + } - var data = this.verify(payload); - if (!data) { - return cb(new Error('The signed request provide was invalid.')); + throw new Error('Signature is invalid'); } - logger.info('Data sent by BigCommerce:'); - logger.info(data); - - cb(null, data); -}; + authorize(query) { + if (!query) return Promise.reject(new Error('The URL query paramaters are required.')); + + const payload = { + client_id: this.config.clientId, + client_secret: this.config.secret, + redirect_uri: this.config.callback, + grant_type: 'authorization_code', + code: query.code, + scope: query.scope, + context: query.context + }; + + const request = new Request('login.bigcommerce.com', { failOnLimitReached: this.config.failOnLimitReached }); + return request.run('post', '/oauth2/token', payload); + } -BigCommerce.prototype.authorize = -BigCommerce.prototype.authorise = function(query, cb) { - if (!query) { - var qError = 'The URL query paramaters are required.'; - logger.error(qError); - return cb(new Error(qError)); + createAPIRequest() { + const accept = this.config.responseType === 'xml' ? 'application/xml' : 'application/json'; + + return new Request('api.bigcommerce.com', { + headers: { + Accept: accept, + 'X-Auth-Client': this.config.clientId, + 'X-Auth-Token': this.config.accessToken + }, + failOnLimitReached: this.config.failOnLimitReached, + agent: this.config.agent + }); } - // jscs:disable requireCamelCaseOrUpperCaseIdentifiers - var payload = { - client_id: this.config.clientId, - client_secret: this.config.secret, - redirect_uri: this.config.callback, - grant_type: 'authorization_code', - code: query.code, - scope: query.scope, - context: query.context - }; - - var request = new Request( - 'login.bigcommerce.com', - { 'Content-Type': 'application/json' }, - this.config.logLevel, - this.config.failOnLimitReached - ); - - request.completeRequest( - 'post', - '/oauth2/token', - payload, - function(err, data, response) { - if (err) { - logger.error(err); - logger.error(response.text); - return cb(err); - } - - logger.info('Auth Successful, object returned:'); - logger.info(data); - logger.info(response.statusCode); - return cb(null, data); + request(type, path, data) { + if (!this.config.accessToken || !this.config.storeHash) { + return Promise.reject(new Error('Get request error: the access token and store hash are required to call the BigCommerce API')); } - ); -}; - -BigCommerce.prototype.createAPIRequest = function() { - var accept = this.config.responseType === 'xml' ? 'application/xml' : 'application/json'; - - return new Request( - 'api.bigcommerce.com', - { - Accept: accept, - 'Content-Type': 'application/json', - 'X-Auth-Client': this.config.clientId, - 'X-Auth-Token': this.config.accessToken - }, - this.config.logLevel, - this.config.failOnLimitReached - ); -}; - -BigCommerce.prototype.request = function(type, path, data, cb) { - if (!this.config.accessToken || !this.config.storeHash) { - var err = new Error('Get request error: the access token and store hash' + - ' are required to call the BigCommerce API'); - logger.error(err.message); - if (cb) cb(err); - return Promise.reject(err); - } - var extension = this.config.responseType === 'xml' ? '.xml' : ''; - var version = this.apiVersion; + const extension = this.config.responseType === 'xml' ? '.xml' : ''; + const version = this.apiVersion; - if (path.indexOf('/catalog') === 0) { - // This is legacy behavior and will be removed in the next major version of - // node-bigcommerce. - version = 'v3'; - } + const request = this.createAPIRequest(); + let fullPath = `/stores/${this.config.storeHash}/${version}`; + if (version !== 'v3') { + fullPath += path.replace(/(\?|$)/, extension + '$1'); + } else { + fullPath += path; + } - var request = this.createAPIRequest(); - var fullPath = '/stores/' + - this.config.storeHash + - '/' + version; - if (version !== 'v3') { - fullPath += path.replace(/(\?|$)/, extension + '$1'); - } else { - fullPath += path; + return request.run(type, fullPath, data); } - // Allow use of keep-alive agent - request.agent = this.config.agent; - - return request.completeRequest(type, fullPath, data, cb); -}; - -BigCommerce.prototype.get = function(path, cb) { - return this.request('get', path, null, cb); -}; + get(path) { + return this.request('get', path); + } -BigCommerce.prototype.post = function(path, data, cb) { - return this.request('post', path, data, cb); -}; + post(path, data) { + return this.request('post', path, data); + } -BigCommerce.prototype.put = function(path, data, cb) { - return this.request('put', path, data, cb); -}; + put(path, data) { + return this.request('put', path, data); + } -BigCommerce.prototype.delete = function(path, data, cb) { - return this.request('delete', path, data, cb); -}; + delete(path) { + return this.request('delete', path); + } +} module.exports = BigCommerce; diff --git a/lib/helpers.js b/lib/helpers.js deleted file mode 100644 index 1244de9..0000000 --- a/lib/helpers.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -// Polyfill if the global promise does not exist. -if (!global.Promise) { - require('es6-promise').polyfill(); -} - -module.exports = { - /** - * Creates a deferred object with resolve and reject functions. - */ - defer: function() { - var deferred = {}; - deferred.promise = new Promise(function(resolve, reject) { - deferred.resolve = resolve; - deferred.reject = reject; - }); - return deferred; - } -}; diff --git a/lib/logger.js b/lib/logger.js deleted file mode 100644 index 0a36143..0000000 --- a/lib/logger.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -/** - * A simple helper for logging info & errors - */ - -module.exports = { - /** - * Log Levels: - * 0: Error only - * 1: Info and Error - */ - level: 0, - info: function(message) { - if (this.level < 1) return; - console.log('\x1b[36mInfo: \x1b[0m%s', message); - }, - error: function(message) { - console.log('\x1b[31mError: \x1b[0m%s', message); - } -}; diff --git a/lib/request.js b/lib/request.js index 4afb9ee..3626497 100644 --- a/lib/request.js +++ b/lib/request.js @@ -4,167 +4,118 @@ * To make the requests asynchronously */ -var logger = require('./logger'), - helpers = require('./helpers'), - util = require('util'); - -var version = require('../package.json').version; - -var Request = function(hostname, headers, logLevel, failOnLimitReached) { - if (!hostname) { - var error = 'The host name is required ' + - 'to make the call to the server.'; - logger.error(error); - throw new Error(error); - } +const logger = require('debug')('node-bigcommerce:request'), + https = require('https'); - this.hostname = hostname; - this.headers = headers; - this.logger = logger; - logger.level = logLevel || 0; - this.failOnLimitReached = !!failOnLimitReached; -}; - -Request.prototype.completeRequest = function(method, path, data, cb) { - var _this = this; - var _hasError = false; - - logger.info('Requesting Data from: https://' + this.hostname + path + - ' Using the ' + method + ' method'); - - var https = require('https'); - var dataString = JSON.stringify(data); - var options = { - hostname: this.hostname, - path: path, - method: method.toUpperCase(), - port: 443, - agent: this.agent, - headers: { - 'User-Agent': 'node-bigcommerce/' + version - } - }; +const version = require('../package.json').version; - // Set Custom Headers - if (this.headers) { - for (var key in this.headers) { - options.headers[key] = this.headers[key]; - } - } +class Request { + constructor(hostname, { headers = { }, failOnLimitReached = false, agent = null } = { }) { + if (!hostname) throw new Error('The hostname is required to make the call to the server.'); - if (data) { - options.headers['Content-Length'] = new Buffer(dataString).length; + this.hostname = hostname; + this.headers = headers; + this.failOnLimitReached = failOnLimitReached; + this.agent = agent; } - logger.info('Starting Request, with options: ' + util.inspect(options)); - - var deferred = helpers.defer(); + run(method, path, data) { + logger(`Requesting Data from: https://${this.hostname}${path} Using the ${method} method`); - var req = https.request(options, function(res) { - logger.info('Status Returned: ' + res.statusCode); - logger.info('Headers Returned: ' + JSON.stringify(res.headers)); + const dataString = JSON.stringify(data); - res.setEncoding('utf8'); + const options = { + path, + hostname: this.hostname, + method: method.toUpperCase(), + port: 443, + headers: Object.assign({ + 'User-Agent': 'node-bigcommerce/' + version, + 'Content-Type': 'application/json' + }, this.headers) + }; - var body = ''; + if (this.agent) options.agent = this.agent; - // If we've already had an error, bail - if (_hasError) { - return; + if (data) { + options.headers['Content-Length'] = new Buffer(dataString).length; } - // If the API limit has been reached - if (res.statusCode === 429) { - var timeToWait = res.headers['x-retry-after']; + logger('Starting Request, with options.', options); - if (_this.failOnLimitReached) { - var err = new Error( - 'You have reached the user\'s rate limit for the BigCommerce API. ' + - 'Please retry in ' + timeToWait + ' seconds.' - ); + return new Promise((resolve, reject) => { + const req = https.request(options, res => { + logger('Status Returned: ' + res.statusCode); + logger('Headers Returned: ' + JSON.stringify(res.headers)); - err.retryAfter = Number(timeToWait); + res.setEncoding('utf8'); - if (cb) return deferred.resolve(cb(err)); - return deferred.reject(err); - } + let body = ''; - var info = 'You have reached the rate limit for the ' + - 'BigCommerce API, we will retry again in ' + - timeToWait + ' seconds.'; + // If the API limit has been reached + if (res.statusCode === 429) { + const timeToWait = res.headers['x-retry-after']; - logger.error(info); + if (this.failOnLimitReached) { + const err = new Error(`You have reached the rate limit for the BigCommerce API. Please retry in ${timeToWait} seconds.`); + err.retryAfter = Number(timeToWait); - return setTimeout(function() { - logger.info('Restarting request call after suggested time'); + return reject(err); + } - _this.completeRequest(method, path, data, cb); - }, timeToWait * 1000); - } + logger(`You have reached the rate limit for the BigCommerce API, we will retry again in ${timeToWait} seconds.`); - res.on('data', function(chunk) { - body += chunk; - }); + return setTimeout(() => { + logger('Restarting request call after suggested time'); - res.on('end', function() { - logger.info('Request complete'); + this.run(method, path, data) + .then(resolve) + .catch(reject); + }, timeToWait * 1000); + } - if (res.statusCode >= 400 && res.statusCode <= 600) { - var error = new Error('Request returned error code: ' + - res.statusCode + ' and body: ' + body); - error.code = res.statusCode; - logger.info(error.message); - if (cb) cb(error, null, res); - return deferred.reject(error); - } + res.on('data', chunk => body += chunk); - try { - var pattern = new RegExp('application/json'); - if (!pattern.test(res.headers['content-type'])) { - if (cb) cb(null, body, res); - return deferred.resolve(body); - } + res.on('end', () => { + logger('Request complete'); + + if (res.statusCode >= 400 && res.statusCode <= 600) { + const error = new Error(`Request returned error code: ${res.statusCode} and body: ${body}`); + error.code = res.statusCode; + + return reject(error); + } + + try { + if (!/application\/json/.test(res.headers['content-type']) || body.trim() === '') { + return resolve(body); + } - var json = {}; + const json = JSON.parse(body); - if (body.trim() !== '') { - logger.info('Body is not empty, parsing.'); + if (Object.prototype.hasOwnProperty.call(json, 'error') || Object.prototype.hasOwnProperty.call(json, 'errors')) { + const err = new Error(json.error || JSON.stringify(json.errors)); - json = JSON.parse(body); - if (json.hasOwnProperty('error') || json.hasOwnProperty('errors')) { - var err = new Error(json.error || JSON.stringify(json.errors)); - if (cb) cb(err, null, res); - return deferred.reject(err); + return reject(err); + } + + return resolve(json); + } catch (e) { + return reject(e); } - } + }); + }); - logger.info('Request complete, returning data and response.'); + req.on('error', e => reject(e)); - if (cb) cb(null, json, res); - return deferred.resolve(json); - } catch (e) { - logger.error('Error parsing JSON: ' + e); - if (cb) cb(e, null, res); - return deferred.reject(e); + if (data) { + logger('Sending Data: ' + dataString); + req.write(dataString); } + + req.end(); }); - }); - - req.on('error', function(e) { - logger.error(e); - _hasError = true; - if (cb) cb(e); - return deferred.reject(e); - }); - - if (data) { - logger.info('Sending Data: ' + dataString); - req.write(dataString); } - - req.end(); - - return deferred.promise; -}; +} module.exports = Request; diff --git a/package.json b/package.json index 0a29b3c..8ab27e9 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,12 @@ { "name": "node-bigcommerce", - "version": "2.5.2", + "version": "3.0.0", "description": "A node module for authentication and use with the BigCommerce API", "main": "./lib/bigcommerce", "scripts": { + "autotest": "node_modules/.bin/supervisor -q -n exit -x node_modules/.bin/_mocha --", "exectests": "node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha", - "jshint": "node_modules/.bin/jshint lib", - "jscs": "node_modules/.bin/jscs lib", - "lint": "npm run jshint && npm run jscs", + "lint": "node_modules/.bin/eslint lib test", "test": "npm run exectests && npm run lint" }, "repository": { @@ -31,15 +30,15 @@ "devDependencies": { "agentkeepalive": "^3.1.0", "chai": "^3.5.0", + "eslint": "^4.9.0", + "eslint-config-conversio": "^1.0.4", "istanbul": "^0.4.2", - "jscs": "^2.9.0", - "jshint": "^2.9.1", "mocha": "^2.4.5", "nock": "^7.0.2", - "require-reload": "^0.2.2", - "sinon": "^1.17.3" + "sinon": "^1.17.3", + "supervisor": "^0.12.0" }, "dependencies": { - "es6-promise": "^3.1.2" + "debug": "^3.1.0" } } diff --git a/test/.eslintrc.json b/test/.eslintrc.json new file mode 100644 index 0000000..cc22815 --- /dev/null +++ b/test/.eslintrc.json @@ -0,0 +1,8 @@ +{ + "env": { + "mocha": true + }, + "rules": { + "import/no-extraneous-dependencies": ["error", { "devDependencies": true }] + } +} diff --git a/test/bigcommerce.js b/test/bigcommerce.js index 8ac3c8f..b227ec4 100644 --- a/test/bigcommerce.js +++ b/test/bigcommerce.js @@ -1,14 +1,14 @@ 'use strict'; -var BigCommerce = require('../lib/bigcommerce'), +const BigCommerce = require('../lib/bigcommerce'), Request = require('../lib/request'), should = require('chai').should(), - expect = require('chai').expect, - sinon = require('sinon'), - nock = require('nock'); + sinon = require('sinon'); -describe('BigCommerce', function() { - var bc = new BigCommerce({ +describe('BigCommerce', () => { + const self = { }; + + const bc = new BigCommerce({ secret: '123456abcdef', clientId: '123456abcdef', callback: 'http://foo.com', @@ -16,61 +16,70 @@ describe('BigCommerce', function() { storeHash: '12abc' }); - var sandbox; - beforeEach(function() { - sandbox = sinon.sandbox.create(); - }); - - afterEach(function() { - sandbox.restore(); - }); + beforeEach(() => self.sandbox = sinon.sandbox.create()); + afterEach(() => self.sandbox.restore()); - describe('#constructor', function() { - it('should return an error if config is missing', function() { - should.Throw(function() { + describe('#constructor', () => { + it('should return an error if config is missing', () => { + /* eslint no-new: off */ + should.Throw(() => { new BigCommerce(); }, Error); }); - it('should save config to the object', function() { - var newBc = new BigCommerce({ test: true }); - newBc.config.should.not.be.null; + it('should save config to the object', () => { + const newBc = new BigCommerce({ test: true }); + newBc.config.should.be.a('object'); newBc.apiVersion.should.equal('v2'); }); - it('should set the logger to the correct level', function() { - new BigCommerce({ logLevel: 'info' }).logger.level.should.equal(1); - }); - - it('should set api version to a default', function() { + it('should set api version to a default', () => { new BigCommerce({ apiVersion: 'v3' }).apiVersion.should.equal('v3'); }); }); - describe('#verify', function() { - context('given a null signed request', function() { - it('should return null', function() { - var verify = bc.verify(); - should.not.exist(verify); + describe('#verify', () => { + context('given a null signed request', () => { + it('should return null', () => { + try { + bc.verify(); + } catch (e) { + e.message.should.match(/signed request is required/); + return; + } + + throw new Error('You shall not pass!'); }); }); - context('given a signed request without a full stop', function() { - it('should return null', function() { - var verify = bc.verify('12345'); - should.not.exist(verify); + context('given a signed request without a full stop', () => { + it('should return null', () => { + try { + bc.verify('12345'); + } catch (e) { + e.message.should.match(/full stop/); + return; + } + + throw new Error('You shall not pass!'); }); }); - context('given an invalid signature', function() { - it('should return null', function() { - var verify = bc.verify('eyJmb28iOiJmb28ifQ==.Zm9v'); - should.not.exist(verify); + context('given an invalid signature', () => { + it('should return null', () => { + try { + bc.verify('eyJmb28iOiJmb28ifQ==.Zm9v'); + } catch (e) { + e.message.should.match(/invalid/); + return; + } + + throw new Error('You shall not pass!'); }); }); - it('should return the JSON data', function() { - var verify = bc.verify( + it('should return the JSON data', () => { + const verify = bc.verify( 'eyJmb28iOiJmb28ifQ==.YjMzMTQ2ZGU4ZTUzNWJiOTI3NTI1ODJmNzhiZGM' + '5NzBjNGQ3MjZkZDdkMDY1MjdkZGYxZDA0NGZjNDVjYmNkMA==' ); @@ -78,410 +87,172 @@ describe('BigCommerce', function() { }); }); - describe('#callback', function() { - context('given an invalid signature', function() { - it('should return an error', function(done) { - bc.callback('eyJmb28iOiJmb28ifQ==.Zm9v', function(err) { - err.should.not.be.null; - done(); - }); - }); - }); - - it('should return the data', function(done) { - bc.callback( - 'eyJmb28iOiJmb28ifQ==.YjMzMTQ2ZGU4ZTUzNWJiOTI3NTI1ODJmNzhiZGM' + - '5NzBjNGQ3MjZkZDdkMDY1MjdkZGYxZDA0NGZjNDVjYmNkMA==', - function(err, data) { - should.not.exist(err); - data.foo.should.equal('foo'); - done(); - } - ); + describe('#authorize', () => { + beforeEach(() => { + self.runStub = self.sandbox.stub(Request.prototype, 'run') + .returns(Promise.resolve({ test: true })); }); - }); - describe('#authorise', function() { - var query = { code: '', scope: '', context: '' }; + const query = { code: '', scope: '', context: '' }; - context('when the query params are missing', function() { - it('should return an error', function(done) { - bc.authorise(null, function(err) { - err.should.not.be.null; - done(); - }); - }); + it('should return an object', () => { + return bc.authorize(query) + .then(data => data.should.not.be.null); }); - context('when the authorisation fails', function() { - it('should return and error', function(done) { - var requestStub = sandbox.stub( - Request.prototype, - 'completeRequest', - function(method, path, data, cb) { - cb(new Error('Test Request Error'), null, { text: 'Test error text' }); - } - ); - - bc.authorise(query, function(err, data) { - should.not.exist(data); - err.should.not.be.null; - requestStub.restore(); - done(); - }); + context('when the query params are missing', () => { + it('should return an error', () => { + return bc.authorize(null) + .then(() => should.fail('You shall not pass!')) + .catch(err => err.message.should.match(/are required/)); }); }); - it('should return an object', function(done) { - var requestStub = sandbox.stub( - Request.prototype, - 'completeRequest', - function(method, path, data, cb) { - cb(null, {}, { text: '' }); - } - ); - - bc.authorise(query, function(err, data) { - should.not.exist(err); - data.should.not.be.null; - requestStub.restore(); - done(); + context('when the authorization fails', () => { + beforeEach(() => { + self.runStub.returns(Promise.reject(new Error('foo'))); }); - }); - - it('should work for its naming alias "authorize"', function(done) { - var requestStub = sandbox.stub( - Request.prototype, - 'completeRequest', - function(method, path, data, cb) { - cb(null, {}, { text: '' }); - } - ); - bc.authorize(query, function(err, data) { - should.not.exist(err); - data.should.not.be.null; - requestStub.restore(); - done(); + it('should return and error', () => { + return bc.authorize(query) + .then(() => should.fail('You shall not pass!')) + .catch(err => err.message.should.equal('foo')); }); }); }); - describe('#checkRequirements', function() { - }); - - describe('#createAPIRequest', function() { - it('should create a request object with the correct headers', function() { - var request = bc.createAPIRequest(); - request.headers['Content-Type'].should.equal('application/json'); + describe('#createAPIRequest', () => { + it('should create a request object with the correct headers', () => { + const request = bc.createAPIRequest(); request.headers['X-Auth-Client'].should.equal('123456abcdef'); request.headers['X-Auth-Token'].should.equal('123456'); }); - it('should pass the correct log level to the request object', function() { - var infoBc = new BigCommerce({ - accessToken: '123456', - clientId: 'abcdef', - logLevel: 'info' - }); - - var request = infoBc.createAPIRequest(); - request.logger.level.should.equal(1); - }); - - it('should have the correct API hostname', function() { - var request = bc.createAPIRequest(); + it('should have the correct API hostname', () => { + const request = bc.createAPIRequest(); request.hostname.should.equal('api.bigcommerce.com'); }); }); - describe('#request', function() { - context('when the header requirements are not met', function() { - it('should return an error', function(done) { - new BigCommerce({ }).request('get', '/foo', null, function(err) { - err.should.not.be.null; - done(); - }); - }); - - it('should reject with an error', function(done) { - new BigCommerce({ }).request('get', '/foo', null) - .then(function() { - done(new Error('Should not resolve')); - }) - .catch(function(err) { - err.should.not.be.null; - done(); - }); - }); + describe('#request', () => { + beforeEach(() => { + self.requestStub = self.sandbox.stub(Request.prototype, 'run') + .returns(Promise.resolve({ text: '' })); }); - context('when no responseType is given', function() { - it('should call the request object with no extension', function(done) { - var requestStub = sandbox.stub( - Request.prototype, - 'completeRequest', - function(method, path, data, cb) { - path.should.equal('/stores/12abc/v2/foo'); - cb(null, {}, { text: '' }); - } - ); - - bc.request('get', '/foo', null, function() { - requestStub.restore(); - done(); - }); - }); + it('should make a call to the request object', () => { + return bc.request('get', '/foo') + .then(() => sinon.assert.calledOnce(self.requestStub)); }); - context('when the response type is xml', function() { - var xmlBc = new BigCommerce({ + it('should use v3 if specified in config', () => { + const bcV3 = new BigCommerce({ + secret: '123456abcdef', + clientId: '123456abcdef', + callback: 'http://foo.com', accessToken: '123456', - clientId: 'abcdef', - storeHash: 'abcd/1', - responseType: 'xml' + storeHash: '12abc', + apiVersion: 'v3' }); - it('should call the request object with extension .xml', function(done) { - var requestStub = sandbox.stub( - Request.prototype, - 'completeRequest', - function(method, path, data, cb) { - path.should.equal('/stores/abcd/1/v2/foo.xml'); - cb(null, {}, { text: '' }); - } - ); - - xmlBc.request('get', '/foo', null, function() { - requestStub.restore(); - done(); - }); + return bcV3.request('get', '/themes') + .then(() => sinon.assert.calledWith(self.requestStub, 'get', '/stores/12abc/v3/themes')); + }); + + context('when the header requirements are not met', () => { + it('should return an error', () => { + const bc = new BigCommerce({ }); + return bc.request('get', '/foo') + .then(() => should.fail('You shall not pass!')) + .catch(e => e.message.should.match(/access token/)); }); }); - context('when the response type is json', function() { - var jsonBc = new BigCommerce({ + context('when the response type is xml', () => { + const xmlBc = new BigCommerce({ accessToken: '123456', clientId: 'abcdef', storeHash: 'abcd/1', - responseType: 'json' - }); - - it('should make a call to the request object with an empty extension', function(done) { - var requestStub = sandbox.stub( - Request.prototype, - 'completeRequest', - function(method, path, data, cb) { - path.should.equal('/stores/abcd/1/v2/foo'); - cb(null, {}, { text: '' }); - } - ); - - jsonBc.request('get', '/foo', null, function() { - requestStub.restore(); - done(); - }); - }); - }); - - it('should make a call to the request object', function(done) { - var requestStub = sandbox.stub( - Request.prototype, - 'completeRequest', - function(method, path, data, cb) { - cb(null, {}, { text: '' }); - } - ); - - bc.request('get', '/foo', null, function() { - sinon.assert.calledOnce(requestStub); - requestStub.restore(); - done(); + responseType: 'xml' }); - }); - - it('should use v3 for catalog requests (LEGACY)', function(done) { - var requestStub = sandbox.stub( - Request.prototype, - 'completeRequest', - function(method, path, data, cb) { - path.should.equal('/stores/12abc/v3/catalog'); - cb(null, {}, { text: '' }); - } - ); - bc.request('get', '/catalog', null, function() { - requestStub.restore(); - done(); + it('should call the request object with extension .xml', () => { + return xmlBc.request('get', '/foo') + .then(() => sinon.assert.calledWith(self.requestStub, 'get', '/stores/abcd/1/v2/foo.xml')); }); }); - it('should use v3 if specified in config', function(done) { - var requestStub = sandbox.stub( - Request.prototype, - 'completeRequest', - function(method, path, data, cb) { - path.should.equal('/stores/12abc/v3/themes'); - cb(null, {}, { text: '' }); - } - ); - - var bcV3 = new BigCommerce({ - secret: '123456abcdef', - clientId: '123456abcdef', - callback: 'http://foo.com', - accessToken: '123456', - storeHash: '12abc', - apiVersion: 'v3' - }); + context('when the response type is json', () => { + it('should make a call to the request object with an empty extension', () => { + const jsonBc = new BigCommerce({ + accessToken: '123456', + clientId: 'abcdef', + storeHash: 'abcd/1', + responseType: 'json' + }); - bcV3.request('get', '/themes', null, function() { - requestStub.restore(); - done(); + return jsonBc.request('get', '/foo') + .then(() => sinon.assert.calledWith(self.requestStub, 'get', '/stores/abcd/1/v2/foo')); }); }); }); - describe('#get', function() { - it('should make a request with the correct arguments', function(done) { - var requestStub = sandbox.stub( - BigCommerce.prototype, - 'request', - function(type, path, data, cb) { - type.should.equal('get'); - path.should.not.be.null; - should.not.exist(data); - cb(null, {}, { text: '' }); - } - ); - - bc.get('/foo', function() { - requestStub.restore(); - done(); - }); + describe('#get', () => { + beforeEach(() => { + self.requestStub = self.sandbox.stub(Request.prototype, 'run') + .returns(Promise.resolve({ text: '' })); }); - it('should resolve a promise with the data', function() { - var fooNock = nock('https://api.bigcommerce.com') - .get('/stores/12abc/v2/foo') - .reply(200, { some: 'data' }); - + it('should make a request with the correct arguments', () => { return bc.get('/foo') - .then(function(data) { - fooNock.done(); - data.should.deep.equal({ some: 'data' }); - }); - }); - - it('should reject a promise with an error', function(done) { - var fooNock = nock('https://api.bigcommerce.com') - .get('/stores/12abc/v2/foo') - .reply(400, {}); - - bc.get('/foo') - .then(function() { - done(new Error('Should not resolve')); - }) - .catch(function(err) { - err.code.should.equal(400); - err.should.be.a('error'); - done(); + .then(res => { + res.should.deep.equal({ text: '' }); + sinon.assert.calledWith(self.requestStub, 'get', '/stores/12abc/v2/foo', undefined); }); }); }); - describe('#post', function() { - it('should make a request with the correct arguments', function(done) { - var requestStub = sandbox.stub( - BigCommerce.prototype, - 'request', - function(type, path, data, cb) { - type.should.equal('post'); - path.should.not.be.null; - data.should.not.be.null; - cb(null, {}, { text: '' }); - } - ); - - bc.post('/foo', {}, function() { - requestStub.restore(); - done(); - }); + describe('#post', () => { + beforeEach(() => { + self.requestStub = self.sandbox.stub(Request.prototype, 'run') + .returns(Promise.resolve({ text: '' })); }); - it('should resolve a promise with the data', function() { - var fooNock = nock('https://api.bigcommerce.com') - .post('/stores/12abc/v2/foo') - .reply(200, { some: 'data' }); - - return bc.post('/foo') - .then(function(data) { - fooNock.done(); - data.should.deep.equal({ some: 'data' }); + it('should make a request with the correct arguments', () => { + return bc.post('/foo', { foo: 'bar' }) + .then(res => { + res.should.deep.equal({ text: '' }); + sinon.assert.calledWith(self.requestStub, 'post', '/stores/12abc/v2/foo', { foo: 'bar' }); }); }); }); - describe('#put', function() { - it('should make a request with the correct arguments', function(done) { - var requestStub = sandbox.stub( - BigCommerce.prototype, - 'request', - function(type, path, data, cb) { - type.should.equal('put'); - path.should.not.be.null; - data.should.not.be.null; - cb(null, {}, { text: '' }); - } - ); - - bc.put('/foo', {}, function() { - requestStub.restore(); - done(); - }); + describe('#put', () => { + beforeEach(() => { + self.requestStub = self.sandbox.stub(Request.prototype, 'run') + .returns(Promise.resolve({ text: '' })); }); - it('should resolve a promise with the data', function() { - var fooNock = nock('https://api.bigcommerce.com') - .put('/stores/12abc/v2/foo') - .reply(200, { some: 'data' }); - - return bc.put('/foo') - .then(function(data) { - fooNock.done(); - data.should.deep.equal({ some: 'data' }); + it('should make a request with the correct arguments', () => { + return bc.put('/foo', { foo: 'bar' }) + .then(res => { + res.should.deep.equal({ text: '' }); + sinon.assert.calledWith(self.requestStub, 'put', '/stores/12abc/v2/foo', { foo: 'bar' }); }); }); }); - describe('#delete', function() { - it('should make a request with the correct arguments', function(done) { - var requestStub = sandbox.stub( - BigCommerce.prototype, - 'request', - function(type, path, data, cb) { - type.should.equal('delete'); - path.should.not.be.null; - data.should.not.be.null; - cb(null, {}, { text: '' }); - } - ); - - bc.delete('/foo', {}, function() { - requestStub.restore(); - done(); - }); + describe('#delete', () => { + beforeEach(() => { + self.requestStub = self.sandbox.stub(Request.prototype, 'run') + .returns(Promise.resolve({ text: '' })); }); - it('should resolve a promise with the data', function() { - var fooNock = nock('https://api.bigcommerce.com') - .delete('/stores/12abc/v2/foo') - .reply(200, { some: 'data' }); - + it('should make a request with the correct arguments', () => { return bc.delete('/foo') - .then(function(data) { - fooNock.done(); - data.should.deep.equal({ some: 'data' }); + .then(res => { + res.should.deep.equal({ text: '' }); + sinon.assert.calledWith(self.requestStub, 'delete', '/stores/12abc/v2/foo', undefined); }); }); }); diff --git a/test/helpers.js b/test/helpers.js deleted file mode 100644 index fcd623b..0000000 --- a/test/helpers.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -var reload = require('require-reload')(require), - helpers = require('../lib/helpers'), - logger = require('../lib/logger'), - should = require('chai').should(), - sinon = require('sinon'); - -describe('Helpers', function() { - describe('defer', function() { - it('Should create a deferred promise that resolves', function() { - var deferred = helpers.defer(); - deferred.resolve(123); - return deferred.promise - .then(function(result) { - result.should.equal(123); - }); - }); - - it('Should create a deferred promise that rejects', function(done) { - var deferred = helpers.defer(); - deferred.reject(new Error('oh no')); - deferred.promise - .then(function() { - done(new Error('Should not resolve')); - }) - .catch(function(err) { - err.message.should.equal('oh no'); - done(); - }); - }); - - it('should work when there is no global promise', function() { - var oldPromise = global.Promise; - global.Promise = undefined; - - helpers = reload('../lib/helpers'); - - var deferred = helpers.defer(); - deferred.resolve(123); - return deferred.promise - .then(function(result) { - result.should.equal(123); - global.Promise = oldPromise; - }); - }); - }); -}); diff --git a/test/logger.js b/test/logger.js deleted file mode 100644 index d89e0bf..0000000 --- a/test/logger.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -var Request = require('../lib/request'), - logger = require('../lib/logger'), - should = require('chai').should(), - sinon = require('sinon'); - -describe('Logger', function() { - it('should be set to a log level of zero by default', function() { - logger.level.should.equal(0); - }); - - it('Should log an error at any log level', function() { - var stub = sinon.stub(console, 'log'); - - logger.error('Logging test error at 0'); - logger.level = 1; - logger.error('Logging test error at 1'); - - sinon.assert.calledTwice(stub); - stub.restore(); - }); - - it('should log info at level one', function() { - var stub = sinon.stub(console, 'log'); - - logger.level = 1; - logger.info('Logging test error at 1'); - - sinon.assert.calledOnce(stub); - stub.restore(); - }); - - it('should not log info at level zero', function() { - var stub = sinon.stub(console, 'log'); - - logger.level = 0; - logger.info('Logging test error at 1'); - - sinon.assert.notCalled(stub); - stub.restore(); - }); -}); diff --git a/test/request.js b/test/request.js index 9b41caa..f76d785 100644 --- a/test/request.js +++ b/test/request.js @@ -1,264 +1,170 @@ 'use strict'; -var Request = require('../lib/request'), +const Request = require('../lib/request'), should = require('chai').should(), - expect = require('chai').expect, nock = require('nock'), - sinon = require('sinon'), - version = require('../package.json').version, HttpsAgent = require('agentkeepalive').HttpsAgent; -describe('Request', function() { - var request = new Request( - 'api.bigcommerce.com', - { 'Content-Type': 'application/json' }, - 0 - ); +describe('Request', () => { + const self = { }; - afterEach(function() { - nock.cleanAll(); - }); + const request = new Request('api.bigcommerce.com', { headers: { 'Content-Type': 'application/json' } }); + + afterEach(() => nock.cleanAll()); - context('given a missing hostname', function() { - it('should return an error if hostname is missing', function() { - should.Throw(function() { + context('given a missing hostname', () => { + it('should return an error if hostname is missing', () => { + /* eslint no-new: off */ + should.Throw(() => { new Request(); }, Error); }); }); - context('given a 429 status code', function() { - beforeEach(function() { - nock('https://api.bigcommerce.com') - .defaultReplyHeaders({ - 'X-Retry-After': 0.1 - }) + context('given a 429 status code', () => { + beforeEach(() => { + self.ordersCall = nock('https://api.bigcommerce.com') .post('/orders') - .reply(429, {}); + .reply(429, { }, { 'X-Retry-After': 0.1 }) + .post('/orders') + .reply(200, { }); }); - it('should retry the request', function(done) { - request.completeRequest('post', '/orders', {}, function() {}); - - sinon.stub(request, 'completeRequest', function() { - request.completeRequest.restore(); - done(); - }); + it('should retry the request', () => { + return request.run('post', '/orders') + .then(() => self.ordersCall.isDone().should.equal(true)); }); - context('given a failOnLimitReached option', function() { - var failRequest = new Request( - 'api.bigcommerce.com', - { 'Content-Type': 'application/json' }, - 0, - true - ); - - it('should return an error', function(done) { - failRequest.completeRequest('post', '/orders', {}, function(err) { - err.should.not.be.null; - done(); - }); + context('given a failOnLimitReached option', () => { + const failRequest = new Request('api.bigcommerce.com', { + headers: { 'Content-Type': 'application/json' }, + failOnLimitReached: true }); - it('includes the retryAfter delay in the error', function(done) { - failRequest.completeRequest('post', '/orders', {}, function(err) { - err.retryAfter.should.equal(0.1); - done(); - }); - }) + it('should return an error', () => { + return failRequest.run('post', '/orders') + .then(() => should.fail('You shall not pass!')) + .catch(e => { + e.message.should.match(/rate limit/); + e.retryAfter.should.equal(0.1); + }); + }); }); }); - context('given a bad request or internal error is returned', function() { - beforeEach(function() { + context('given a bad request or internal error is returned', () => { + beforeEach(() => { nock('https://api.bigcommerce.com') - .post('/orders', {}) + .post('/orders') .reply(400, {}); }); - it('should return an error', function(done) { - request.completeRequest('post', '/orders', {}, function(err) { - err.should.not.be.null; - done(); - }); + it('should return an error', () => { + return request.run('post', '/orders', { }) + .then(() => should.fail('You shall not pass!')) + .catch(e => e.message.should.match(/Request returned error code/)); }); }); - context('if "error" are found in the response JSON', function() { - beforeEach(function() { + context('if "error" are found in the response JSON', () => { + beforeEach(() => { nock('https://api.bigcommerce.com') - .post('/orders', {}) + .post('/orders') .reply(200, { error: 'An error has occurred.' }); }); - it('should return an error', function(done) { - request.completeRequest('post', '/orders', {}, function(err) { - err.should.not.be.null; - err.message.should.equal('An error has occurred.'); - done(); - }); + it('should return an error', () => { + return request.run('post', '/orders', { }) + .then(() => should.fail('You shall not pass!')) + .catch(e => e.message.should.match(/An error has occurred/)); }); }); - context('if "errors" are found in the response JSON', function() { - beforeEach(function() { + context('if "errors" are found in the response JSON', () => { + beforeEach(() => { nock('https://api.bigcommerce.com') - .post('/orders', {}) + .post('/orders') .reply(200, { errors: ['An error has occurred.'] }); }); - it('should return an error', function(done) { - request.completeRequest('post', '/orders', {}, function(err) { - err.should.not.be.null; - err.message.should.equal('["An error has occurred."]'); - done(); - }); + it('should return an error', () => { + return request.run('post', '/orders', { }) + .then(() => should.fail('You shall not pass!')) + .catch(e => e.message.should.match(/An error has occurred/)); }); }); - context('given a malformed request JSON', function() { - beforeEach(function() { + context('given a malformed request JSON', () => { + beforeEach(() => { nock('https://api.bigcommerce.com') - .defaultReplyHeaders({ - 'content-type': 'application/json' - }) - .post('/orders', {}) + .defaultReplyHeaders({ 'Content-Type': 'application/json' }) + .post('/orders') .reply(200, ''); }); - it('should return an error the ', function(done) { - request.completeRequest('post', '/orders', {}, function(err) { - err.should.not.be.null; - err.message.should.include('Unexpected token <'); - done(); - }); + it('should return an error', () => { + return request.run('post', '/orders', { }) + .then(() => should.fail('You shall not pass!')) + .catch(e => e.message.should.match(/Unexpected token/)); }); }); - context('if json is not returned', function() { - beforeEach(function() { + context('if json is not returned', () => { + beforeEach(() => { nock('https://api.bigcommerce.com') - .defaultReplyHeaders({ - 'content-type': 'application/xml' - }) - .post('/orders', {}) + .defaultReplyHeaders({ 'Content-Type': 'application/xml' }) + .post('/orders') .reply(200, ''); }); - it('should return the raw response ', function(done) { - request.completeRequest('post', '/orders', {}, function(err, data) { - should.not.exist(err); - data.should.be.a.string; - done(); - }); + it('should return the raw response', () => { + return request.run('post', '/orders', { }) + .then(res => res.should.equal('')); }); }); - context('timeout', function() { - beforeEach(function() { + context('timeout', () => { + beforeEach(() => { nock('https://api.bigcommerce.com') - .defaultReplyHeaders({ - 'content-type': 'application/json' - }) - .post('/orders', {}) + .post('/orders') .replyWithError('ECONNRESET'); }); - it('should return an error', function(done) { - request.completeRequest('post', '/orders', {}, function(err) { - err.should.not.be.null; - err.message.should.include('ECONNRESET'); - done(); - }); - }); - - it('should return a promise based error', function(done) { - request.completeRequest('post', '/orders', {}) - .then(function(res) { - return done(new Error('Should not complete')); - }) - .catch(function(err) { - return done(); - }); + it('should return an error', () => { + return request.run('post', '/orders', { }) + .then(() => should.fail('You shall not pass!')) + .catch(e => e.message.should.match(/ECONNRESET/)); }); }); - it('Should attach a keep-alive HTTPS agent', function(done) { + it('should attach a keep-alive HTTPS agent', () => { nock('https://api.bigcommerce.com') - .post('/orders', {}) + .post('/orders') .reply(200, { order: true }); - request.agent = new HttpsAgent({ - maxSockets: 30, - maxFreeSockets: 30, - timeout: 60000, - keepAliveTimeout: 30000 + const request = new Request('api.bigcommerce.com', { + headers: { 'Content-Type': 'application/json' }, + agent: new HttpsAgent({ + maxSockets: 30, + maxFreeSockets: 30, + timeout: 60000, + keepAliveTimeout: 30000 + }) }); - request.completeRequest('post', '/orders', {}, function(err, data) { - should.not.exist(err); - data.should.be.a('object'); - done(); - }); + return request.run('post', '/orders') + .then(res => res.should.be.a('object')); }); - it('Should return a JSON object on success', function(done) { + it('should return a JSON object on success', () => { nock('https://api.bigcommerce.com') - .post('/orders', {}) - .reply(200, { order:true }); - - request.completeRequest('post', '/orders', {}, function(err, data) { - should.not.exist(err); - data.should.be.a('object'); - done(); - }); - }); - - it('Should use the package version for the user agent', function(done) { - // Check that the version is non-empty just to be safe :-) - expect(version).to.be.a('string'); - expect(version).to.have.length.above(3); - - var storeCall = nock('https://api.bigcommerce.com', { - reqheaders: { - 'User-Agent': 'node-bigcommerce/' + version - } - }) - .get('/store', {}) - .reply(200, {}); - - request.completeRequest('get', '/store', {}, function(err, data) { - storeCall.done(); - done(); - }); - }); - - describe('Promises', function() { - it('Should promise to return a JSON object', function() { - nock('https://api.bigcommerce.com') - .post('/orders', {}) - .reply(200, { order:true }); - - return request.completeRequest('post', '/orders', {}) - .then(function(data) { - data.should.deep.equal({ order: true }); - }); - }); - - it('Should reject on error', function(done) { - nock('https://api.bigcommerce.com') - .post('/orders', {}) - .reply(400, {}); + .post('/orders') + .reply(200, { order: true }); - request.completeRequest('post', '/orders', {}) - .then(function() { - done(new Error('Should not resolve')); - }) - .catch(function(err) { - err.code.should.equal(400); - done(); - }); - }); + return request.run('post', '/orders') + .then(res => { + res.should.be.a('object'); + res.order.should.equal(true); + }); }); }); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..1015d7c --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1426 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + +abbrev@1.0.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" + +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + dependencies: + acorn "^3.0.4" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + +acorn@^5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.2.tgz#911cb53e036807cf0fa778dc5d370fbd864246d7" + +agentkeepalive@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.3.0.tgz#6d5de5829afd3be2712201a39275fd11c651857c" + dependencies: + humanize-ms "^1.2.1" + +ajv-keywords@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0" + +ajv@^5.2.0, ajv@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.3.tgz#c06f598778c44c6b161abafe3466b81ad1814ed2" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + json-schema-traverse "^0.3.0" + json-stable-stringify "^1.0.1" + +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-escapes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-styles@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" + dependencies: + color-convert "^1.9.0" + +argparse@^1.0.7: + version "1.0.9" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + dependencies: + sprintf-js "~1.0.2" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +arrify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +assertion-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" + +async@1.x, async@^1.4.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +babel-code-frame@^6.22.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +builtin-modules@^1.0.0, builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + dependencies: + callsites "^0.2.0" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +"chai@>=1.9.2 <4.0.0", chai@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" + dependencies: + assertion-error "^1.0.1" + deep-eql "^0.1.3" + type-detect "^1.0.0" + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e" + dependencies: + ansi-styles "^3.1.0" + escape-string-regexp "^1.0.5" + supports-color "^4.0.0" + +circular-json@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +color-convert@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" + dependencies: + color-name "^1.1.1" + +color-name@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + +commander@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" + +commander@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cross-spawn@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +debug@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" + dependencies: + ms "0.7.1" + +debug@^2.2.0, debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +debug@^3.0.1, debug@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +decamelize@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +deep-eql@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" + dependencies: + type-detect "0.1.1" + +deep-equal@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + dependencies: + globby "^5.0.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + rimraf "^2.2.8" + +diff@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +error-ex@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + +escape-string-regexp@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +escodegen@1.8.x: + version "1.8.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" + dependencies: + esprima "^2.7.1" + estraverse "^1.9.1" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.2.0" + +eslint-config-airbnb-base@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-10.0.1.tgz#f17d4e52992c1d45d1b7713efbcd5ecd0e7e0506" + +eslint-config-conversio@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/eslint-config-conversio/-/eslint-config-conversio-1.0.4.tgz#56f78dbb4732fab03eb55e5c00676efbf0a6e34a" + dependencies: + eslint-config-airbnb-base "^10.0.1" + eslint-plugin-import "^2.1.0" + +eslint-import-resolver-node@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz#4422574cde66a9a7b099938ee4d508a199e0e3cc" + dependencies: + debug "^2.6.8" + resolve "^1.2.0" + +eslint-module-utils@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449" + dependencies: + debug "^2.6.8" + pkg-dir "^1.0.0" + +eslint-plugin-import@^2.1.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.7.0.tgz#21de33380b9efb55f5ef6d2e210ec0e07e7fa69f" + dependencies: + builtin-modules "^1.1.1" + contains-path "^0.1.0" + debug "^2.6.8" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.1" + eslint-module-utils "^2.1.1" + has "^1.0.1" + lodash.cond "^4.3.0" + minimatch "^3.0.3" + read-pkg-up "^2.0.0" + +eslint-scope@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.9.0.tgz#76879d274068261b191fe0f2f56c74c2f4208e8b" + dependencies: + ajv "^5.2.0" + babel-code-frame "^6.22.0" + chalk "^2.1.0" + concat-stream "^1.6.0" + cross-spawn "^5.1.0" + debug "^3.0.1" + doctrine "^2.0.0" + eslint-scope "^3.7.1" + espree "^3.5.1" + esquery "^1.0.0" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^9.17.0" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^3.0.6" + is-resolvable "^1.0.0" + js-yaml "^3.9.1" + json-stable-stringify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.4" + minimatch "^3.0.2" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^7.0.0" + progress "^2.0.0" + require-uncached "^1.0.3" + semver "^5.3.0" + strip-ansi "^4.0.0" + strip-json-comments "~2.0.1" + table "^4.0.1" + text-table "~0.2.0" + +espree@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.1.tgz#0c988b8ab46db53100a1954ae4ba995ddd27d87e" + dependencies: + acorn "^5.1.1" + acorn-jsx "^3.0.0" + +esprima@2.7.x, esprima@^2.7.1: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + +esprima@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + +esquery@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + dependencies: + estraverse "^4.1.0" + object-assign "^4.0.1" + +estraverse@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +external-editor@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.5.tgz#52c249a3981b9ba187c7cacf5beb50bf1d91a6bc" + dependencies: + iconv-lite "^0.4.17" + jschardet "^1.4.2" + tmp "^0.0.33" + +fast-deep-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + +flat-cache@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + +formatio@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" + dependencies: + samsam "~1.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +function-bind@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + +glob@3.2.11: + version "3.2.11" + resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.11.tgz#4a973f635b9190f715d10987d5c00fd2815ebe3d" + dependencies: + inherits "2" + minimatch "0.3" + +glob@^5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^9.17.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + dependencies: + array-union "^1.0.1" + arrify "^1.0.0" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +graceful-fs@^4.1.2: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +growl@1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" + +handlebars@^4.0.1: + version "4.0.10" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +has@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + +hosted-git-info@^2.1.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + dependencies: + ms "^2.0.0" + +iconv-lite@^0.4.17: + version "0.4.19" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" + +ignore@^3.3.3: + version "3.3.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.5.tgz#c4e715455f6073a8d7e5dae72d2fc9d71663dba6" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + +inquirer@^3.0.6: + version "3.3.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + dependencies: + path-is-inside "^1.0.1" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-resolvable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" + dependencies: + tryit "^1.0.1" + +isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +istanbul@^0.4.2: + version "0.4.5" + resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" + dependencies: + abbrev "1.0.x" + async "1.x" + escodegen "1.8.x" + esprima "2.7.x" + glob "^5.0.15" + handlebars "^4.0.1" + js-yaml "3.x" + mkdirp "0.5.x" + nopt "3.x" + once "1.x" + resolve "1.1.x" + supports-color "^3.1.0" + which "^1.1.1" + wordwrap "^1.0.0" + +jade@0.26.3: + version "0.26.3" + resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c" + dependencies: + commander "0.6.1" + mkdirp "0.3.0" + +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +js-yaml@3.x, js-yaml@^3.9.1: + version "3.10.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jschardet@^1.4.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.1.tgz#c519f629f86b3a5bedba58a88d311309eec097f9" + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash.cond@^4.3.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" + +lodash@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + +lodash@^4.17.4, lodash@^4.3.0: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + +lolex@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" + +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + +lru-cache@2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" + +lru-cache@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + +minimatch@0.3: + version "0.3.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd" + dependencies: + lru-cache "2" + sigmund "~1.0.0" + +"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + +mkdirp@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" + +mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +mocha@^2.4.5: + version "2.5.3" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-2.5.3.tgz#161be5bdeb496771eb9b35745050b622b5aefc58" + dependencies: + commander "2.3.0" + debug "2.2.0" + diff "1.4.0" + escape-string-regexp "1.0.2" + glob "3.2.11" + growl "1.9.2" + jade "0.26.3" + mkdirp "0.5.1" + supports-color "1.2.0" + to-iso-string "0.0.2" + +ms@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" + +ms@2.0.0, ms@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + +nock@^7.0.2: + version "7.7.3" + resolved "https://registry.yarnpkg.com/nock/-/nock-7.7.3.tgz#d0600980a4443edf6e50b5ed3314602cb7ccc489" + dependencies: + chai ">=1.9.2 <4.0.0" + debug "^2.2.0" + deep-equal "^1.0.0" + json-stringify-safe "^5.0.1" + lodash "^3.10.1" + mkdirp "^0.5.0" + propagate "0.3.x" + qs "^6.0.2" + +nopt@3.x: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + dependencies: + abbrev "1" + +normalize-package-data@^2.3.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +once@1.x, once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +optionator@^0.8.1, optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +p-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-is-inside@^1.0.1, path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + +propagate@0.3.x: + version "0.3.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-0.3.1.tgz#e3a84404a7ece820dd6bbea9f6d924e3135ae09c" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +qs@^6.0.2: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +readable-stream@^2.2.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +require-uncached@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + +resolve@1.1.x: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + +resolve@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" + dependencies: + path-parse "^1.0.5" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + dependencies: + align-text "^0.1.1" + +rimraf@^2.2.8: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + dependencies: + glob "^7.0.5" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + dependencies: + is-promise "^2.1.0" + +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +samsam@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" + +samsam@~1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" + +"semver@2 || 3 || 4 || 5", semver@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + +sigmund@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + +signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +sinon@^1.17.3: + version "1.17.7" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" + dependencies: + formatio "1.1.1" + lolex "1.3.2" + samsam "1.1.2" + util ">=0.10.3 <1" + +slice-ansi@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + dependencies: + is-fullwidth-code-point "^2.0.0" + +source-map@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" + dependencies: + amdefine ">=0.0.4" + +source-map@~0.5.1: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" + dependencies: + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + +string-width@^2.1.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +supervisor@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/supervisor/-/supervisor-0.12.0.tgz#de7e6337015b291851c10f3538c4a7f04917ecc1" + +supports-color@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^3.1.0: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + dependencies: + has-flag "^1.0.0" + +supports-color@^4.0.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" + dependencies: + has-flag "^2.0.0" + +table@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" + dependencies: + ajv "^5.2.3" + ajv-keywords "^2.1.0" + chalk "^2.1.0" + lodash "^4.17.4" + slice-ansi "1.0.0" + string-width "^2.1.1" + +text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + dependencies: + os-tmpdir "~1.0.2" + +to-iso-string@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/to-iso-string/-/to-iso-string-0.0.2.tgz#4dc19e664dfccbe25bd8db508b00c6da158255d1" + +tryit@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +type-detect@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" + +type-detect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +uglify-js@^2.6: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +"util@>=0.10.3 <1": + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + dependencies: + inherits "2.0.1" + +validate-npm-package-license@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + +which@^1.1.1, which@^1.2.9: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + +wordwrap@^1.0.0, wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + dependencies: + mkdirp "^0.5.1" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0"