diff --git a/.nvmrc b/.nvmrc index 8104cab..0e79152 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -8.1.0 +8.1.1 diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 153b7c8..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,6 +0,0 @@ -# Change Log - -## v1.0.0-alpha.1 (11-06-2017) - -* Initial release including `shopifyRouter`, `withShop`, and `withWebhook` - diff --git a/README.md b/README.md index 37aa780..23d0162 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,23 @@ -# shopify-express +## Deprecation -A small set of abstractions that will help you quickly build an Express.js app that consumes the Shopify API. +:exclamation: **This project is deprecated**. This means Shopify will not be maintaining it going forward. If you are interested in building a Shopify app using first party tools then check out our other libraries: + +* [@shopify/koa-shopify-auth](https://github.com/Shopify/quilt/tree/master/packages/koa-shopify-auth) +* [@shopify/koa-shopify-graphql-proxy](https://github.com/Shopify/quilt/blob/master/packages/koa-shopify-graphql-proxy/README.md) +* [shopify_app](https://github.com/Shopify/shopify_app) -:exclamation: **This project is currently in alpha status**. This means that the API could change at any time. It also means that your feedback will have a big impact on how the project evolves, so please feel free to [open issues](https://github.com/shopify/shopify-express/issues) if there is something you would like to see added. +These are all used internally and written against technologies we use for our own applications. Of course, if you wish to continue using Express, feel free to fork this codebase and continue it as you wish. +# shopify-express + +A small set of abstractions that will help you quickly build an Express.js app that consumes the Shopify API. ## Example ```javascript const express = require('express'); const shopifyExpress = require('@shopify/shopify-express'); +const session = require('express-session'); const app = express(); @@ -20,11 +28,15 @@ const { NODE_ENV, } = process.env; -const shopify = shopifyExpress({ +// session is necessary for api proxy and auth verification +app.use(session({secret: SHOPIFY_APP_SECRET})); + +const {routes, withShop} = shopifyExpress({ host: SHOPIFY_APP_HOST, apiKey: SHOPIFY_APP_KEY, secret: SHOPIFY_APP_SECRET, scope: ['write_orders, write_products'], + accessMode: 'offline', afterAuth(request, response) { const { session: { accessToken, shop } } = request; // install webhooks or hook into your own app here @@ -32,8 +44,11 @@ const shopify = shopifyExpress({ }, }); -// mounts '/auth/shopify' and '/api' off of '/' -app.use('/', shopify.routes); +// mounts '/auth' and '/api' off of '/shopify' +app.use('/shopify', routes); + +// shields myAppMiddleware from being accessed without session +app.use('/myApp', withShop({authBaseUrl: '/shopify'}), myAppMiddleware) ``` ## Shopify routes @@ -62,30 +77,71 @@ endpoints from a client application without having to worry about CORS. By default the package comes with `MemoryStrategy`, `RedisStrategy`, and `SqliteStrategy`. If none are specified, the default is `MemoryStrategy`. -You can use them in a config like so: +#### MemoryStrategy + +Simple javascript object based memory store for development purposes. Do not use this in production! + +```javascript +const shopifyExpress = require('@shopify/shopify-express'); +const {MemoryStrategy} = require('@shopify/shopify-express/strategies'); + +const shopify = shopifyExpress({ + shopStore: new MemoryStrategy(redisConfig), + ...restOfConfig, +}); +``` + +#### RedisStrategy + +Uses [redis](https://www.npmjs.com/package/redis) under the hood, so you can pass it any configuration that's valid for the library. ```javascript const shopifyExpress = require('@shopify/shopify-express'); const {RedisStrategy} = require('@shopify/shopify-express/strategies'); +const redisConfig = { + // your config here +}; + +const shopify = shopifyExpress({ + shopStore: new RedisStrategy(redisConfig), + ...restOfConfig, +}); +``` + +#### SQLStrategy + +Uses [knex](https://www.npmjs.com/package/knex) under the hood, so you can pass it any configuration that's valid for the library. By default it uses `sqlite3` so you'll need to run `yarn add sqlite3` to use it. Knex also supports `postgreSQL` and `mySQL`. + +```javascript +const shopifyExpress = require('@shopify/shopify-express'); +const {SQLStrategy} = require('@shopify/shopify-express/strategies'); + +// uses sqlite3 if no settings are specified +const knexConfig = { + // your config here +}; + const shopify = shopifyExpress({ - shopStore: new RedisStrategy(), + shopStore: new SQLStrategy(knexConfig), ...restOfConfig, }); ``` -### Custom Strategy +SQLStrategy expects a table named `shops` with a primary key `id`, and `string` fields for `shopify_domain` and `access_token`. It's recommended you index `shopify_domain` since it is used to look up tokens. + +If you do not have a table already created for your store, you can generate one with `new SQLStrategy(myConfig).initialize()`. This returns a promise so you can finish setting up your app after it if you like, but we suggest you make a separate db initialization script, or keep track of your schema yourself. -`shopifyExpress` takes a `shopStore` parameter. This can be any javascript class matching the following interface: +#### Custom Strategy + +`shopifyExpress` accepts any javascript class matching the following interface: ```javascript class Strategy { - constructor(){} // shop refers to the shop's domain name - getShop({ shop }, done)){} + getShop({ shop }): Promise<{accessToken: string}> // shop refers to the shop's domain name - // data can by any serializable object - storeShop({ shop, accessToken, data }, done){} + storeShop({ shop, accessToken }): Promise<{accessToken: string}> } ``` @@ -95,20 +151,51 @@ const shopify = shopifyExpress({ ### `withShop` -`app.use('/someProtectedPath', withShop, someHandler);` +`app.use('/someProtectedPath', withShop({authBaseUrl: '/shopify'}), someHandler);` -Express middleware that validates the presence of your shop session. +Express middleware that validates the presence of your shop session. The parameter you pass to it should match the base URL for where you've mounted the shopify routes. ### `withWebhook` `app.use('/someProtectedPath', withWebhook, someHandler);` -Express middleware that validates the the presence of a valid HMAC signature to allow webhook requests from shopify to your app. +Express middleware that validates the presence of a valid HMAC signature to allow webhook requests from shopify to your app. ## Example app You can look at [shopify-node-app](https://github.com/shopify/shopify-node-app) for a complete working example. +## Gotchas + +### Install route +For the moment the app expects you to mount your install route at `/install`. See [shopify-node-app](https://github.com/shopify/shopify-node-app) for details. + +### Express Session +This library expects [express-session](https://www.npmjs.com/package/express-session) or a compatible library to be installed and set up for much of it's functionality. Api Proxy and auth verification functions won't work without something putting a `session` key on `request`. + +It is possible to use auth without a session key on your request, but not recommended. + +### Body Parser +This library handles body parsing on it's own for webhooks. If you're using webhooks you should make sure to follow express best-practices by only adding your body parsing middleware to specific routes that need it. + +**Good** +```javascript + app.use('/some-route', bodyParser.json(), myHandler); + + app.use('/webhook', withWebhook(myWebhookHandler)); + app.use('/', shopifyExpress.routes); +``` + +**Bad** +```javascript + app.use(bodyParser.json()); + app.use('/some-route', myHandler); + + app.use('/webhook', withWebhook(myWebhookHandler)); + app.use('/', shopifyExpress.routes); +``` + + ## Contributing Contributions are welcome. Please refer to the [contributing guide](https://github.com/Shopify/shopify-express/blob/master/CONTRIBUTING.md) for more details. diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..b43c86c --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,8 @@ +1. Merge your branch into master +2. Run `npm version [version]` which will do the following: + * write new version to package.json + * create a new commit with a commit message matching the version number + * create a new tag matching the version number +3. Push the new commit and tags to master with `git push origin master --tags` +4. Create a release on Github. Include a change log in the description +5. Deploy via Shipit diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..ca8e08e --- /dev/null +++ b/circle.yml @@ -0,0 +1,9 @@ +machine: + node: + version: 8.1 +dependencies: + override: + - yarn install --dev +test: + override: + - yarn run test:ci diff --git a/constants.js b/constants.js new file mode 100644 index 0000000..3f0765b --- /dev/null +++ b/constants.js @@ -0,0 +1,2 @@ +module.exports.TEST_COOKIE_NAME = 'shopifyTestCookie'; +module.exports.TOP_LEVEL_OAUTH_COOKIE_NAME = 'shopifyTopLevelOAuth'; diff --git a/index.js b/index.js index 42655d1..9fe156f 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,27 @@ +const PropTypes = require('prop-types'); const createRouter = require('./routes'); const createMiddleware = require('./middleware'); const {MemoryStrategy} = require('./strategies'); +const ShopifyConfigTypes = { + apiKey: PropTypes.string.isRequired, + host: PropTypes.string.isRequired, + secret: PropTypes.string.isRequired, + scope: PropTypes.arrayOf(PropTypes.string).isRequired, + afterAuth: PropTypes.func.isRequired, + shopStore: PropTypes.object, + accessMode: PropTypes.oneOf(['offline', 'online']), +}; + +const defaults = { + shopStore: new MemoryStrategy(), + accessMode: 'offline' +}; + module.exports = function shopify(shopifyConfig) { - const config = Object.assign( - {shopStore: new MemoryStrategy()}, - shopifyConfig, - ); + PropTypes.checkPropTypes(ShopifyConfigTypes, shopifyConfig, 'option', 'ShopifyExpress'); + + const config = Object.assign({}, defaults, shopifyConfig); return { middleware: createMiddleware(config), diff --git a/middleware/index.js b/middleware/index.js index 79064f9..8c83f5f 100644 --- a/middleware/index.js +++ b/middleware/index.js @@ -1,9 +1,8 @@ const createWithWebhook = require('./webhooks'); -const createWithShop = require('./withShop'); +const withShop = require('./withShop'); module.exports = function createMiddleware(shopifyConfig) { const withWebhook = createWithWebhook(shopifyConfig); - const withShop = createWithShop(); return { withShop, diff --git a/middleware/webhooks.js b/middleware/webhooks.js index 5544ab6..f6ed4c2 100644 --- a/middleware/webhooks.js +++ b/middleware/webhooks.js @@ -1,29 +1,40 @@ const crypto = require('crypto'); +const getRawBody = require('raw-body'); -module.exports = function createWithWebhook({ secret, shopStore }) { - return function withWebhook(request, response, next) { - const { body: data } = request; - const hmac = request.get('X-Shopify-Hmac-Sha256'); - const topic = request.get('X-Shopify-Topic'); - const shopDomain = request.get('X-Shopify-Shop-Domain'); +module.exports = function configureWithWebhook({ secret, shopStore }) { + return function createWebhookHandler(onVerified) { + return async function withWebhook(request, response, next) { + const { body: data } = request; + const hmac = request.get('X-Shopify-Hmac-Sha256'); + const topic = request.get('X-Shopify-Topic'); + const shopDomain = request.get('X-Shopify-Shop-Domain'); - const generated_hash = crypto - .createHmac('sha256', SHOPIFY_APP_SECRET) - .update(JSON.stringify(data)) - .digest('base64'); + try { + const rawBody = await getRawBody(request); + const generated_hash = crypto + .createHmac('sha256', secret) + .update(rawBody) + .digest('base64'); - if (generated_hash !== hmac) { - return response.status(401).send("Request doesn't pass HMAC validation"); - } + if (generated_hash !== hmac) { + response.status(401).send(); + onVerified(new Error("Unable to verify request HMAC")); + return; + } - shopStore.getShop({ shop: shopDomain }, (error, { accessToken }) => { - if (error) { - next(error); - } + const {accessToken} = await shopStore.getShop({ shop: shopDomain }); + + request.body = rawBody.toString('utf8'); + request.webhook = { topic, shopDomain, accessToken }; - request.webhook = { topic, shopDomain, accessToken }; + response.status(200).send(); - next(); - }); - }; + onVerified(null, request); + } catch (error) { + response.status(401).send(); + onVerified(new Error("Unable to verify request HMAC")); + return; + } + }; + } }; diff --git a/middleware/withShop.js b/middleware/withShop.js index 487ea2c..e550a7f 100644 --- a/middleware/withShop.js +++ b/middleware/withShop.js @@ -1,19 +1,23 @@ -module.exports = function withShop({ redirect } = { redirect: true }) { +const {TEST_COOKIE_NAME, TOP_LEVEL_OAUTH_COOKIE_NAME} = require('../constants'); + +module.exports = function withShop({ authBaseUrl } = {}) { return function verifyRequest(request, response, next) { - const { query: { shop }, session } = request; + const { query: { shop }, session, baseUrl } = request; if (session && session.accessToken) { - return next(); + response.cookie(TOP_LEVEL_OAUTH_COOKIE_NAME); + next(); + return; } - if (shop && redirect) { - return response.redirect(`/auth/shopify?shop=${shop}`); - } + response.cookie(TEST_COOKIE_NAME, '1'); - if (redirect) { - return response.redirect('/install'); + if (shop) { + response.redirect(`${authBaseUrl || baseUrl}/auth?shop=${shop}`); + return; } - return response.status(401).json('Unauthorized'); + response.redirect('/install'); + return; }; }; diff --git a/package.json b/package.json index bfca4e5..5f81345 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shopify/shopify-express", - "version": "1.0.0-alpha.3", + "version": "1.0.0-alpha.8", "author": "Shopify Inc.", "description": "Get up and running quickly with Express.js and the Shopify API.", "keywords": [ @@ -18,22 +18,39 @@ "node": ">=8.1.0" }, "scripts": { - "pretty": "prettier --single-quote --trailing-comma es5 --write {middleware,routes,shopStore}{/*,/**/*}.js", - "precommit": "yarn run pretty", - "test": "jest" + "prettier:check": "prettier-check --single-quote --trailing-comma=all {middleware,routes,shopStore}{/*,/**/*}.{js}", + "prettier:fix": "prettier --single-quote --trailing-comma=all --write {middleware,routes,shopStore}{/*,/**/*}.{js}", + "precommit": "lint-staged", + "test": "jest", + "test:ci": "jest --ci" + }, + "lint-staged": { + "*.{js}": [ + "prettier:check" + ] + }, + "publishConfig": { + "access": "public" }, "dependencies": { + "body-parser": "^1.18.2", "connect-redis": "^3.3.0", + "cookie-parser": "^1.4.3", "express": "^4.16.2", "express-session": "^1.15.3", "knex": "^0.13.0", - "redis": "^2.7.1", + "node-fetch": "^1.7.3", + "prop-types": "^15.6.1", + "raw-body": "^2.3.2", "sqlite3": "^3.1.9", "url": "^0.11.0" }, "devDependencies": { + "find-free-port": "^1.1.0", + "husky": "^0.14.3", "jest": "^21.2.1", - "prettier": "^1.5.2", - "supertest": "^3.0.0" + "lint-staged": "^5.0.0", + "prettier": "^1.8.2", + "prettier-check": "^2.0.0" } } diff --git a/routes/index.js b/routes/index.js index ff1e559..7db0762 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,14 +1,37 @@ const express = require('express'); +const bodyParser = require('body-parser'); +const cookieParser = require('cookie-parser') -const createWithShop = require('../middleware/withShop'); -const createShopifyAuthRouter = require('./shopifyAuth'); +const createShopifyAuthRoutes = require('./shopifyAuth'); const shopifyApiProxy = require('./shopifyApiProxy'); module.exports = function createRouter(shopifyConfig) { const router = express.Router(); + const rawParser = bodyParser.raw({ type: '*/*' }); + const simpleCookieParser = cookieParser(); + const {auth, callback, enableCookies} = createShopifyAuthRoutes(shopifyConfig) - router.use('/auth/shopify', createShopifyAuthRouter(shopifyConfig)); - router.use('/api', createWithShop({ redirect: false }), shopifyApiProxy); + router.use('/auth/callback', callback); + router.use('/auth/enable_cookies', enableCookies); + router.use('/auth', simpleCookieParser, auth); + router.use( + '/api', + rawParser, + verifyApiCall, + shopifyApiProxy, + ); return router; }; + +function verifyApiCall(request, response, next) { + const {session} = request; + + if (session && session.accessToken) { + next(); + return; + } + + response.status(401).send(); + return; +} diff --git a/routes/shopifyApiProxy.js b/routes/shopifyApiProxy.js index c6d6c36..6abfc2b 100644 --- a/routes/shopifyApiProxy.js +++ b/routes/shopifyApiProxy.js @@ -1,47 +1,69 @@ -const { URL } = require('url'); +const querystring = require('querystring'); +const fetch = require('node-fetch'); -const ALLOWED_URLS = ['/products', '/orders']; +const DISALLOWED_URLS = [ + '/application_charges', + '/application_credits', + '/carrier_services', + '/fulfillment_services', + '/recurring_application_charges', + '/script_tags', + '/storefront_access_token', + '/webhooks', + '/oauth', +]; -module.exports = function shopifyApiProxy(request, response, next) { - const { query, method, path, body, session } = request; - const { shop, accessToken } = session; +module.exports = async function shopifyApiProxy(incomingRequest, response, next) { + const { query, method, path: pathname, body, session } = incomingRequest; - const strippedPath = path.split('?')[0].split('.json')[0]; + if (session == null) { + console.error('A session middleware must be installed to use ApiProxy.') + response.status(401).send(new Error('Unauthorized')); + return; + } - const inAllowed = ALLOWED_URLS.some(resource => { - return strippedPath === resource; - }); + const { shop, accessToken } = session; + + if (shop == null || accessToken == null) { + response.status(401).send(new Error('Unauthorized')); + return; + } - if (!inAllowed) { - return response.status(403).send('Endpoint not in whitelist'); + if (!validRequest(pathname)) { + response.status(403).send('Endpoint not in whitelist'); + return; } - const fetchOptions = { - method, - body, - headers: { - 'Content-Type': 'application/json', - 'X-Shopify-Access-Token': accessToken, - }, - }; - - fetchWithParams(`https://${shop}/admin${path}`, fetchOptions, query) - .then(remoteResponse => { - const { status } = remoteResponse; - return Promise.all([remoteResponse.json(), status]); - }) - .then(([responseBody, status]) => { - response.status(status).send(responseBody); - }) - .catch(err => response.err(err)); + try { + const searchParams = querystring.stringify(query); + const searchString = searchParams.length > 0 + ? `?${searchParams}` + : ''; + + const url = `https://${shop}/admin${pathname}${searchString}`; + const result = await fetch(url, { + method, + body, + headers: { + 'Content-Type': 'application/json', + 'X-Shopify-Access-Token': accessToken, + }, + }); + + const data = await result.text(); + response.status(result.status).send(data); + } catch (error) { + console.log(error); + response.status(500).send(error); + } }; -function fetchWithParams(url, fetchOpts, query) { - const parsedUrl = new URL(url); +module.exports.DISALLOWED_URLS = DISALLOWED_URLS; - Object.entries(query).forEach(([key, value]) => { - parsedUrl.searchParams.append(key, value); - }); +function validRequest(path) { + const strippedPath = path.split('?')[0].split('.json')[0]; - return fetch(parsedUrl.href, fetchOpts); + return DISALLOWED_URLS.every(resource => { + return strippedPath.indexOf(resource) === -1; + }); } diff --git a/routes/shopifyAuth.js b/routes/shopifyAuth.js index 0700374..317ab68 100644 --- a/routes/shopifyAuth.js +++ b/routes/shopifyAuth.js @@ -1,95 +1,560 @@ -const express = require('express'); const querystring = require('querystring'); const crypto = require('crypto'); +const fetch = require('node-fetch'); -module.exports = function createShopifyAuthRouter({ +const {TEST_COOKIE_NAME, TOP_LEVEL_OAUTH_COOKIE_NAME} = require('../constants'); + +module.exports = function createShopifyAuthRoutes({ host, apiKey, secret, scope, afterAuth, shopStore, + accessMode, }) { - const router = express.Router(); - - // This function initializes the Shopify OAuth Process - router.get('/', function(request, response) { - const { query } = request; - const { shop } = query; - - if (shop == null) { - return response.status(400).send('Expected a shop query parameter'); - } - - const redirectTo = `https://${shop}/admin/oauth/authorize`; - const redirectParams = `?client_id=${apiKey}&scope=${scope}&redirect_uri=${host}/auth/shopify/callback`; - - response.send( - ` - - - - - ` - ); - }); - - // Users are redirected here after clicking `Install`. - // The redirect from Shopify contains the authorization_code query parameter, - // which the app exchanges for an access token - router.get('/callback', (request, response) => { - const { query } = request; - const { code, hmac, shop } = query; - - const map = JSON.parse(JSON.stringify(query)); - delete map['signature']; - delete map['hmac']; - - const message = querystring.stringify(map); - const generated_hash = crypto - .createHmac('sha256', secret) - .update(message) - .digest('hex'); - - if (generated_hash !== hmac) { - return response.status(400).send('HMAC validation failed'); - } - - if (shop == null) { - return response.status(400).send('Expected a shop query parameter'); - } - - const requestBody = querystring.stringify({ - code, - client_id: apiKey, - client_secret: secret, - }); - - fetch(`https://${shop}/admin/oauth/access_token`, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': Buffer.byteLength(requestBody), - }, - body: requestBody, - }) - .then(remoteResponse => remoteResponse.json()) - .then(responseBody => { - const accessToken = responseBody.access_token; - - shopStore.storeShop({ accessToken, shop }, (err, token) => { - if (err) { - console.error('🔴 Error storing shop access token', err); - } + return { + // This function initializes the Shopify OAuth Process + auth(request, response) { + const { query, baseUrl } = request; + const { shop } = query; + + if (shop == null) { + return response.status(400).send('Expected a shop query parameter'); + } + + if (!request.cookies[TEST_COOKIE_NAME]) { + // This is to avoid a redirect loop if the app doesn't use verifyRequest or set the test cookie elsewhere. + response.cookie(TEST_COOKIE_NAME, '1'); + topLevelRedirect(response, `${host}${baseUrl}/enable_cookies?${querystring.stringify({shop})}`); + return; + } + + const redirectTo = `https://${shop}/admin/oauth/authorize`; + + const redirectParams = { + baseUrl, + scope, + client_id: apiKey, + redirect_uri: `${host}${baseUrl}/callback`, + }; + + if (accessMode === 'online') { + redirectParams['grant_options[]'] = 'per-user'; + } + + if (!request.cookies[TOP_LEVEL_OAUTH_COOKIE_NAME]) { + response.cookie(TOP_LEVEL_OAUTH_COOKIE_NAME, '1'); + topLevelRedirect(response, `${redirectTo}?${querystring.stringify(redirectParams)}`); + return; + } + + response.clearCookie(TOP_LEVEL_OAUTH_COOKIE_NAME); + response.redirect(`${redirectTo}?${querystring.stringify(redirectParams)}`); + }, + + // Users are redirected here after clicking `Install`. + // The redirect from Shopify contains the authorization_code query parameter, + // which the app exchanges for an access token + async callback(request, response, next) { + const { query } = request; + const { code, hmac, shop } = query; + + const map = JSON.parse(JSON.stringify(query)); + delete map['signature']; + delete map['hmac']; + + const message = querystring.stringify(map); + const generated_hash = crypto + .createHmac('sha256', secret) + .update(message) + .digest('hex'); + + if (generated_hash !== hmac) { + return response.status(400).send('HMAC validation failed'); + } + + if (shop == null) { + return response.status(400).send('Expected a shop query parameter'); + } + + const requestBody = querystring.stringify({ + code, + client_id: apiKey, + client_secret: secret, + }); + const remoteResponse = await fetch(`https://${shop}/admin/oauth/access_token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': Buffer.byteLength(requestBody), + }, + body: requestBody, + }); + + const responseBody = await remoteResponse.json(); + const accessToken = responseBody.access_token; + + try { + const {token} = await shopStore.storeShop({ accessToken, shop }) + + if (request.session) { request.session.accessToken = accessToken; request.session.shop = shop; - afterAuth(request, response); - }); - }); - }); + } else { + console.warn('Session not present on request, please install a session middleware.'); + } + + afterAuth(request, response); + } catch (error) { + console.error('🔴 Error storing shop access token', error); + next(error); + } + }, + + enableCookies(request, response) { + const HEADING = 'Enable cookies'; + const BODY = 'You must manually enable cookies in this browser in order to use this app within Shopify.' + const FOOTER = `Cookies let the app authenticate you by temporarily storing your preferences and personal +information. They expire after 30 days.` + const ACTION = 'Enable cookies'; + + const { query, baseUrl } = request; + const { shop } = query; + + if (shop == null) { + return response.status(400).send('Expected a shop query parameter'); + } + + response.send(` + + + + + + Redirecting… + + + + +
+
+
+
+
+
+
+
+
+

${HEADING}

+
+
+

${BODY}

+
+
+

${FOOTER}

+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +`); + } + }; }; + +function topLevelRedirect(response, url) { + response.send( + ` + + + + + `, + ); +} diff --git a/routes/tests/__snapshots__/shopifyAuth.test.js.snap b/routes/tests/__snapshots__/shopifyAuth.test.js.snap index 5ce5cb1..36a7e14 100644 --- a/routes/tests/__snapshots__/shopifyAuth.test.js.snap +++ b/routes/tests/__snapshots__/shopifyAuth.test.js.snap @@ -1,14 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`shopifyAuth / responds to get requests by returning a redirect page 1`] = ` +exports[`shopifyAuth / responds with a 400 when no shop query parameter is given 1`] = `"Expected a shop query parameter"`; + +exports[`shopifyAuth / with cookie access but without a prior top-level attempt responds to get requests by returning a redirect page 1`] = ` " - - - - - " + + + + + " `; - -exports[`shopifyAuth / responds with a 400 when no shop query parameter is given 1`] = `"Expected a shop query parameter"`; diff --git a/routes/tests/shopifyApiProxy.test.js b/routes/tests/shopifyApiProxy.test.js new file mode 100644 index 0000000..71065c2 --- /dev/null +++ b/routes/tests/shopifyApiProxy.test.js @@ -0,0 +1,134 @@ +const findFreePort = require('find-free-port') +const express = require('express'); +const http = require('http'); +const fetch = require.requireActual('node-fetch'); +const fetchMock = require.requireMock('node-fetch'); + +const shopifyApiProxy = require('../shopifyApiProxy'); + +const { DISALLOWED_URLS } = shopifyApiProxy; +const PORT = 3000; +const BASE_URL = `http://localhost:${PORT}`; +const API_ROUTE = '/api'; + +jest.mock('node-fetch'); + +let session; +let server; +const originalConsoleError = console.error; +describe('shopifyApiProxy', async () => { + beforeEach(async () => { + fetchMock.mockImplementation(() => ({ status: 200, text: () => Promise.resolve() })); + + session = { + shop: 'shop.com', + accessToken: 'token', + }; + + server = await createServer(); + console.error = jest.fn(); + }); + + afterEach(() => { + fetchMock.mockClear(); + server.close(); + console.error = originalConsoleError; + }); + + it('errors when no session is present', async () => { + const endpoint = '/products'; + session = null; + + const response = await fetch(`${BASE_URL}${API_ROUTE}${endpoint}`); + + expect(fetchMock).not.toBeCalled(); + expect(console.error).toBeCalledWith('A session middleware must be installed to use ApiProxy.'); + expect(response.status).toBe(401); + }); + + it('errors when shop information is not in session', async () => { + const endpoint = '/products'; + session.shop = null; + session.accessToken = null; + + const response = await fetch(`${BASE_URL}${API_ROUTE}${endpoint}`); + + expect(fetchMock).not.toBeCalled(); + expect(response.status).toBe(401); + }); + + + it('proxies requests to the shop given in session', async () => { + const shop = 'some-shop.com'; + const endpoint = '/products'; + session.shop = shop; + + const expectedPath = `https://${shop}/admin${endpoint}`; + const response = await fetch(`${BASE_URL}${API_ROUTE}${endpoint}`); + + expect(fetchMock).toBeCalled(); + expect(fetchMock.mock.calls[0][0]).toBe(expectedPath); + expect(response.status).toBe(200); + }); + + it('includes the access token given in session and json content type', async () => { + const accessToken = 'foo-token'; + session.accessToken = accessToken; + + const expectedHeaders = { + 'Content-Type': 'application/json', + 'X-Shopify-Access-Token': accessToken, + }; + const expectedPath = `https://${session.shop}/admin/`; + + const response = await fetch(`${BASE_URL}${API_ROUTE}`); + + expect(fetchMock).toBeCalled(); + expect(fetchMock.mock.calls[0][1].headers).toMatchObject(expectedHeaders); + expect(response.status).toBe(200); + }); + + it('does not proxy requests to dissallowed urls', async () => { + for(const url of DISALLOWED_URLS) { + response = await fetch(`${BASE_URL}${API_ROUTE}${url}`); + expect(response.status).toBe(403); + } + }); + + it('returns body from proxied request', async () => { + const expectedBody = 'body text'; + fetchMock.mockImplementation(() => { + return {status: 200, text: () => Promise.resolve(expectedBody)}; + }); + + const response = await fetch(`${BASE_URL}${API_ROUTE}`); + const body = await response.text(); + + expect(response.status).toBe(200); + expect(body).toBe(expectedBody); + }); +}); + +function createServer() { + const app = express(); + + app.use( + API_ROUTE, + (req, _, next) => { + req.session = session; + next(); + }, + shopifyApiProxy + ); + + server = http.createServer(app); + + return new Promise((resolve, reject) => { + findFreePort(PORT, (err, freePort) => { + if (err) { + throw err; + } + server.listen(PORT, resolve(server)); + }) + }); +} diff --git a/routes/tests/shopifyAuth.test.js b/routes/tests/shopifyAuth.test.js index ed42d9e..84eb97e 100644 --- a/routes/tests/shopifyAuth.test.js +++ b/routes/tests/shopifyAuth.test.js @@ -1,58 +1,126 @@ -const request = require('supertest'); +const findFreePort = require('find-free-port') +const fetch = require('node-fetch'); const http = require('http'); const express = require('express'); +const cookieParser = require('cookie-parser') -const {MemoryStrategy} = require('../../strategies'); -const createShopifyAuthRouter = require('../shopifyAuth'); +const { MemoryStrategy } = require('../../strategies'); +const createShopifyAuthRoutes = require('../shopifyAuth'); + +const PORT = 3000; +const BASE_URL = `http://localhost:${PORT}` let server; +let afterAuth; describe('shopifyAuth', async () => { + beforeEach(async () => { + afterAuth = jest.fn(); + server = await createServer({afterAuth}); + }); + afterEach(() => { server.close(); }); describe('/', () => { - it('responds to get requests by returning a redirect page', async () => { - const {app, server} = await createServer(); - const {status, text} = await request(app).get('/?shop=shop1').then((resp) => resp); + describe('without cookie access', () => { + it('top-level redirects to the enable_cookies page', async () => { + const response = await fetch(`${BASE_URL}/auth?shop=shop1`); + const data = await response.text(); + + expect(response.status).toBe(200); + expect(data).toContain(`window.top.location.href = "${BASE_URL}/auth/enable_cookies?shop=shop1"`); + }); + }); + + describe('with cookie access but without a prior top-level attempt', () => { + it('responds to get requests by returning a redirect page', async () => { + const headers = {cookie: 'shopifyTestCookie=1;'}; + const response = await fetch(`${BASE_URL}/auth?shop=shop1`, {headers}); + const data = await response.text(); + + expect(response.status).toBe(200); + expect(data).toMatchSnapshot(); + }); + + it('redirect page includes per-user grant for accessMode: online', async () => { + await server.close(); + server = await createServer({accessMode: 'online'}); + + const headers = {cookie: 'shopifyTestCookie=1;'}; + const response = await fetch(`${BASE_URL}/auth?shop=shop1`, {headers}); + const data = await response.text(); + + expect(response.status).toBe(200); + expect(data).toContain('grant_options%5B%5D=per-user'); + }); + }); - expect(status).toBe(200); - expect(text).toMatchSnapshot(); + describe('with cookie access and a prior top-level attempt', () => { + it('redirects directly to the grant page and removes top-level cookie', async () => { + const headers = {cookie: 'shopifyTestCookie=1; shopifyTopLevelOAuth=1;'}; + const response = await fetch(`${BASE_URL}/auth?shop=shop1`, {headers, redirect: 'manual'}); + const data = await response.text(); - server.close(); + expect(response.status).toBe(302); + expect(response.headers.get('location')).toContain('https://shop1/admin/oauth/authorize'); + expect(response.headers.get('cookie')).toBe(null); + }); }); it('responds with a 400 when no shop query parameter is given', async () => { - const {app, server} = await createServer(); - const {status, text} = await request(app).get('/').then((resp) => resp); + const response = await fetch(`${BASE_URL}/auth`); + const data = await response.text(); + + expect(response.status).toBe(400); + expect(data).toMatchSnapshot(); + }); + }); + + describe('/callback', () => { + it('errors when hmac validation fails', () => { + pending(); + }); - expect(status).toBe(400); - expect(text).toMatchSnapshot(); + it('does not error when hmac validation succeds', () => { + pending(); + }); - server.close(); + it('requests access token', () => { + pending(); + }); + + it('console warns when no session is present on request context', () => { + pending(); }); }); -}) +}); -function createServer({afterAuth = jest.fn()} = {}) { +function createServer(userConfig = {}) { const app = express(); - app.use('/', createShopifyAuthRouter({ + const serverConfig = { + host: BASE_URL, apiKey: 'key', secret: 'secret', scope: ['scope'], shopStore: new MemoryStrategy(), - afterAuth, - })); + accessMode: 'offline', + afterAuth: jest.fn(), + }; + + const {auth, callback} = createShopifyAuthRoutes(Object.assign({}, serverConfig, userConfig)); + app.use('/auth', cookieParser(), auth); + app.use('/auth/callback', callback); server = http.createServer(app); return new Promise((resolve, reject) => { - const results = { - server, - app, - afterAuth, - }; - server.listen(3000, resolve(results)); + findFreePort(PORT, (err, freePort) => { + if (err) { + throw err; + } + server.listen(PORT, resolve(server)); + }) }); } diff --git a/strategies/MemoryStrategy.js b/strategies/MemoryStrategy.js index 486d088..99be6d1 100644 --- a/strategies/MemoryStrategy.js +++ b/strategies/MemoryStrategy.js @@ -3,19 +3,13 @@ module.exports = class MemoryStrategy { this.store = {}; } - storeShop({ shop, accessToken, data = {} }, done) { - this.store[shop] = Object.assign( - {}, - { - accessToken, - }, - data - ); + async storeShop({ shop, accessToken }) { + this.store[shop] = {accessToken}; - return done(null, accessToken); + return {accessToken}; } - getShop({ shop }, done) { - return done(null, this.store[shop]); + async getShop({ shop }) { + return this.store[shop]; } }; diff --git a/strategies/RedisStrategy.js b/strategies/RedisStrategy.js index 43e29b4..080169c 100644 --- a/strategies/RedisStrategy.js +++ b/strategies/RedisStrategy.js @@ -1,28 +1,23 @@ -const Redis = require('redis'); +const util = require('util'); +const redis = require('redis'); module.exports = class RedisStrategy { - constructor() { - this.client = Redis.createClient(); + constructor(redisConfig) { + const client = redis.createClient(redisConfig); + + this.client = { + hgetall: util.promisify(client.hgetall).bind(client), + hmset: util.promisify(client.hmset).bind(client), + } } - storeShop({ shop, accessToken, data = {} }, done) { - const shopData = Object.assign({}, { accessToken }, data); - this.client.hmset(shop, shopData, err => { - if (err) { - done(err); - } + async storeShop({ shop, accessToken }) { + await this.client.hmset(shop, {accessToken}) - done(null, shopData); - }); + return {accessToken}; } - getShop({ shop }, done) { - this.client.hgetall(shop, (err, shopData) => { - if (err) { - return done(err); - } - - done(null, shopData); - }); + async getShop({ shop }) { + return await this.client.hgetall(shop) || {}; } }; diff --git a/strategies/SQLStrategy.js b/strategies/SQLStrategy.js new file mode 100644 index 0000000..cfc0561 --- /dev/null +++ b/strategies/SQLStrategy.js @@ -0,0 +1,36 @@ +const Knex = require('knex'); + +const defaultConfig = { + dialect: 'sqlite3', + useNullAsDefault: true, + connection: { + filename: './db.sqlite3', + }, +}; + +module.exports = class SQLStrategy { + constructor(config = defaultConfig) { + this.knex = Knex(config); + } + + initialize() { + return this.knex.schema.createTableIfNotExists('shops', table => { + table.increments('id'); + table.string('shopify_domain'); + table.string('access_token'); + table.unique('shopify_domain'); + }); + } + + async storeShop({ shop, accessToken }) { + await this.knex.raw( + `INSERT OR IGNORE INTO shops (shopify_domain, access_token) VALUES ('${shop}', '${accessToken}')` + ); + + return {accessToken}; + } + + getShop({ shop }) { + return this.knex('shops').where('shopify_domain', shop) + } +}; diff --git a/strategies/SqliteStrategy.js b/strategies/SqliteStrategy.js deleted file mode 100644 index 1c89130..0000000 --- a/strategies/SqliteStrategy.js +++ /dev/null @@ -1,42 +0,0 @@ -const Knex = require('knex'); - -module.exports = class SqliteStrategy { - constructor() { - this.knex = Knex({ - dialect: 'sqlite3', - useNullAsDefault: true, - connection: { - filename: './db.sqlite3', - }, - }); - - this.knex.schema - .createTableIfNotExists('shops', table => { - table.increments('id'); - table.string('shopify_domain'); - table.string('access_token'); - table.unique('shopify_domain'); - }) - .catch(err => { - console.log(err); - }); - } - - storeShop({ shop, accessToken, data = {} }, done) { - this.knex - .raw( - `INSERT OR IGNORE INTO shops (shopify_domain, access_token) VALUES ('${shop}', '${accessToken}')` - ) - .then(result => { - return done(null, accessToken); - }); - } - - getShop({ shop }, done) { - this.knex('shops') - .where('shopify_domain', shop) - .then(result => { - return done(null, result); - }); - } -}; diff --git a/strategies/index.js b/strategies/index.js index 8ad6d22..43e4117 100644 --- a/strategies/index.js +++ b/strategies/index.js @@ -1,9 +1,9 @@ const RedisStrategy = require('./RedisStrategy'); const MemoryStrategy = require('./MemoryStrategy'); -const SqliteStrategy = require('./SqliteStrategy'); +const SQLStrategy = require('./SQLStrategy'); module.exports = { RedisStrategy, MemoryStrategy, - SqliteStrategy, + SQLStrategy, } diff --git a/tests/ShopifyConfig.test.js b/tests/ShopifyConfig.test.js new file mode 100644 index 0000000..3cd4360 --- /dev/null +++ b/tests/ShopifyConfig.test.js @@ -0,0 +1,42 @@ +const shopifyExpress = require('../index'); + +describe('ShopifyConfig', async () => { + const originalConsoleError = console.error; + beforeEach(() => { + console.error = jest.fn(); + }); + + afterEach(() => { + console.error = originalConsoleError; + }); + + it('logs errors when given empty object', () => { + shopifyExpress({}); + expect(console.error).toBeCalled(); + expect(console.error.mock.calls).toMatchSnapshot(); + }); + + it('logs errors when given bad props', () => { + shopifyExpress({ + apiKey: 32, + host: { notGood: true }, + secret: true, + scope: 'orders', + afterAuth: true, + accessMode: 'gerblable', + }); + expect(console.error).toBeCalled(); + expect(console.error.mock.calls).toMatchSnapshot(); + }); + + it('does not log errors when given valid proptypes', () => { + shopifyExpress({ + apiKey: 'fake', + host: 'fake', + secret: 'cats', + scope: [], + afterAuth: () => null, + }); + expect(console.error).not.toBeCalled(); + }); +}); diff --git a/tests/__snapshots__/ShopifyConfig.test.js.snap b/tests/__snapshots__/ShopifyConfig.test.js.snap new file mode 100644 index 0000000..45b1f02 --- /dev/null +++ b/tests/__snapshots__/ShopifyConfig.test.js.snap @@ -0,0 +1,44 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ShopifyConfig logs errors when given bad props 1`] = ` +Array [ + Array [ + "Warning: Failed option type: Invalid option \`apiKey\` of type \`number\` supplied to \`ShopifyExpress\`, expected \`string\`.", + ], + Array [ + "Warning: Failed option type: Invalid option \`host\` of type \`object\` supplied to \`ShopifyExpress\`, expected \`string\`.", + ], + Array [ + "Warning: Failed option type: Invalid option \`secret\` of type \`boolean\` supplied to \`ShopifyExpress\`, expected \`string\`.", + ], + Array [ + "Warning: Failed option type: Invalid option \`scope\` of type \`string\` supplied to \`ShopifyExpress\`, expected an array.", + ], + Array [ + "Warning: Failed option type: Invalid option \`afterAuth\` of type \`boolean\` supplied to \`ShopifyExpress\`, expected \`function\`.", + ], + Array [ + "Warning: Failed option type: Invalid option \`accessMode\` of value \`gerblable\` supplied to \`ShopifyExpress\`, expected one of [\\"offline\\",\\"online\\"].", + ], +] +`; + +exports[`ShopifyConfig logs errors when given empty object 1`] = ` +Array [ + Array [ + "Warning: Failed option type: The option \`apiKey\` is marked as required in \`ShopifyExpress\`, but its value is \`undefined\`.", + ], + Array [ + "Warning: Failed option type: The option \`host\` is marked as required in \`ShopifyExpress\`, but its value is \`undefined\`.", + ], + Array [ + "Warning: Failed option type: The option \`secret\` is marked as required in \`ShopifyExpress\`, but its value is \`undefined\`.", + ], + Array [ + "Warning: Failed option type: The option \`scope\` is marked as required in \`ShopifyExpress\`, but its value is \`undefined\`.", + ], + Array [ + "Warning: Failed option type: The option \`afterAuth\` is marked as required in \`ShopifyExpress\`, but its value is \`undefined\`.", + ], +] +`; diff --git a/yarn.lock b/yarn.lock index eb90bfd..cc9b09c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,10 +11,10 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" accepts@~1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" + version "1.3.5" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" dependencies: - mime-types "~2.1.16" + mime-types "~2.1.18" negotiator "0.6.1" acorn-globals@^3.1.0: @@ -35,8 +35,8 @@ ajv@^4.9.1: json-stable-stringify "^1.0.1" ajv@^5.1.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.4.0.tgz#32d1cf08dbc80c432f426f12e10b2511f6b46474" + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" dependencies: co "^4.6.0" fast-deep-equal "^1.0.0" @@ -55,6 +55,10 @@ amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" +ansi-escapes@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + ansi-escapes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" @@ -71,12 +75,16 @@ 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, ansi-styles@^3.2.0: +ansi-styles@^3.2.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" +any-observable@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.2.0.tgz#c67870058003579009083f54ac0abafb5c33d242" + anymatch@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" @@ -84,6 +92,10 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" +app-root-path@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46" + append-transform@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" @@ -102,8 +114,8 @@ are-we-there-yet@~1.1.2: readable-stream "^2.0.6" argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" dependencies: sprintf-js "~1.0.2" @@ -133,6 +145,10 @@ arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + asn1@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" @@ -208,8 +224,8 @@ babel-core@^6.0.0, babel-core@^6.26.0: source-map "^0.5.6" babel-generator@^6.18.0, babel-generator@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" dependencies: babel-messages "^6.23.0" babel-runtime "^6.26.0" @@ -217,7 +233,7 @@ babel-generator@^6.18.0, babel-generator@^6.26.0: detect-indent "^4.0.0" jsesc "^1.3.0" lodash "^4.17.4" - source-map "^0.5.6" + source-map "^0.5.7" trim-right "^1.0.1" babel-helpers@^6.24.1: @@ -339,7 +355,7 @@ bluebird@^3.4.6: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" -body-parser@1.18.2: +body-parser@1.18.2, body-parser@^1.18.2: version "1.18.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" dependencies: @@ -373,8 +389,8 @@ boom@5.x.x: hoek "4.x.x" brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" dependencies: balanced-match "^1.0.0" concat-map "0.0.1" @@ -430,7 +446,7 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chalk@^1.0.0, chalk@^1.1.3: +chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -440,18 +456,35 @@ chalk@^1.0.0, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" +chalk@^2.0.1, chalk@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796" dependencies: - ansi-styles "^3.1.0" + ansi-styles "^3.2.0" escape-string-regexp "^1.0.5" - supports-color "^4.0.0" + supports-color "^5.2.0" ci-info@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4" +cli-cursor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" + dependencies: + restore-cursor "^1.0.1" + +cli-spinners@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" + +cli-truncate@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" + dependencies: + slice-ansi "0.0.4" + string-width "^1.0.1" + cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -486,27 +519,23 @@ color-name@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" -combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" +combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" dependencies: delayed-stream "~1.0.0" -commander@^2.2.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" - -component-emitter@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" +commander@^2.11.0, commander@^2.2.0, commander@^2.9.0: + version "2.14.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" connect-redis@^3.3.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/connect-redis/-/connect-redis-3.3.2.tgz#3706f9bfef1ec9b5d11c4b35b265de42c218b408" + version "3.3.3" + resolved "https://registry.yarnpkg.com/connect-redis/-/connect-redis-3.3.3.tgz#0fb8f370192f62da75ec7a9507807599fbe15b37" dependencies: debug "^3.1.0" redis "^2.1.0" @@ -528,8 +557,15 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" convert-source-map@^1.4.0, convert-source-map@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" + version "1.5.1" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" + +cookie-parser@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.3.tgz#0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5" + dependencies: + cookie "0.3.1" + cookie-signature "1.0.6" cookie-signature@1.0.6: version "1.0.6" @@ -539,18 +575,27 @@ cookie@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" -cookiejar@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a" +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" core-js@^2.4.0, core-js@^2.5.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" + version "2.5.3" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" core-util-is@1.0.2, 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" +cosmiconfig@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-3.1.0.tgz#640a94bf9847f321800403cd273af60665c73397" + dependencies: + is-directory "^0.3.1" + js-yaml "^3.9.0" + parse-json "^3.0.0" + require-from-string "^2.0.1" + crc@3.4.4: version "3.4.4" resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b" @@ -591,6 +636,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +date-fns@^1.27.2: + version "1.29.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" + debug@2.6.9, debug@^2.1.3, debug@^2.2.0, debug@^2.6.8: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -607,6 +656,10 @@ decamelize@^1.0.0, decamelize@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + deep-extend@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" @@ -629,10 +682,14 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -depd@1.1.1, depd@~1.1.1: +depd@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" +depd@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" @@ -650,8 +707,8 @@ detect-indent@^4.0.0: repeating "^2.0.0" detect-libc@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.2.tgz#71ad5d204bf17a6a6ca8f450c61454066ef461e1" + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" diff@^3.2.0: version "3.4.0" @@ -671,17 +728,27 @@ ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" -encodeurl@~1.0.1: +elegant-spinner@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" + resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" -errno@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" +encodeurl@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + dependencies: + iconv-lite "~0.4.13" + +errno@~0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" dependencies: - prr "~0.0.0" + prr "~1.0.1" -error-ex@^1.2.0: +error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" dependencies: @@ -696,15 +763,15 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" escodegen@^1.6.1: - version "1.9.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852" + version "1.9.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" dependencies: esprima "^3.1.3" estraverse "^4.2.0" esutils "^2.0.2" optionator "^0.8.1" optionalDependencies: - source-map "~0.5.6" + source-map "~0.6.1" esprima@^3.1.3: version "3.1.3" @@ -732,6 +799,18 @@ exec-sh@^0.2.0: dependencies: merge "^1.1.3" +execa@^0.6.0: + version "0.6.3" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.6.3.tgz#57b69a594f081759c69e5370f0d17b9cb11658fe" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + execa@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" @@ -744,6 +823,22 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +exit-hook@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + expand-brackets@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" @@ -832,13 +927,17 @@ extglob@^0.3.1: dependencies: is-extglob "^1.0.0" -extsprintf@1.3.0, extsprintf@^1.2.0: +extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + 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" + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" fast-json-stable-stringify@^2.0.0: version "2.0.0" @@ -854,6 +953,25 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" +fbjs@^0.8.16: + version "0.8.16" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.9" + +figures@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" + dependencies: + escape-string-regexp "^1.0.5" + object-assign "^4.1.0" + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -887,6 +1005,14 @@ finalhandler@1.1.0: statuses "~1.3.1" unpipe "~1.0.0" +find-free-port@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/find-free-port/-/find-free-port-1.2.0.tgz#fba7fb3b5b933f1a276d10695584e3e445468636" + +find-parent-dir@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54" + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -927,14 +1053,6 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" -form-data@^2.3.1, form-data@~2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" - form-data@~2.1.1: version "2.1.4" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" @@ -943,9 +1061,13 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" -formidable@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" +form-data@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + dependencies: + asynckit "^0.4.0" + combined-stream "1.0.6" + mime-types "^2.1.12" forwarded@~0.1.2: version "0.1.2" @@ -1008,6 +1130,10 @@ get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" +get-own-enumerable-property-symbols@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz#5c4ad87f2834c4b9b4e84549dc1e0650fb38c24b" + get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -1112,9 +1238,9 @@ 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-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" has-unicode@^2.0.0: version "2.0.1" @@ -1143,8 +1269,8 @@ hoek@2.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" hoek@4.x.x: - version "4.2.0" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" + version "4.2.1" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" home-or-tmp@^2.0.0: version "2.0.0" @@ -1194,7 +1320,15 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -iconv-lite@0.4.19: +husky@^0.14.3: + version "0.14.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-0.14.3.tgz#c69ed74e2d2779769a17ba8399b54ce0b63c12c3" + dependencies: + is-ci "^1.0.10" + normalize-path "^1.0.0" + strip-indent "^2.0.0" + +iconv-lite@0.4.19, iconv-lite@~0.4.13: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" @@ -1202,6 +1336,16 @@ imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + dependencies: + repeating "^2.0.0" + +indent-string@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -1214,16 +1358,16 @@ inherits@2, inherits@2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" ini@^1.3.4, ini@~1.3.0: - version "1.3.4" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" interpret@^0.6.5: version "0.6.6" resolved "https://registry.yarnpkg.com/interpret/-/interpret-0.6.6.tgz#fecd7a18e7ce5ca6abfb953e1f86213a49f1625b" invariant@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" + version "2.2.3" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.3.tgz#1a827dfde7dcbd7c323f0ca826be8fa7c5e9d688" dependencies: loose-envify "^1.0.0" @@ -1231,9 +1375,9 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" -ipaddr.js@1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" +ipaddr.js@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b" is-arrayish@^0.2.1: version "0.2.1" @@ -1250,11 +1394,15 @@ is-builtin-module@^1.0.0: builtin-modules "^1.0.0" is-ci@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" dependencies: ci-info "^1.0.0" +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + is-dotfile@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" @@ -1273,6 +1421,10 @@ is-extglob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + is-finite@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" @@ -1295,6 +1447,12 @@ is-glob@^2.0.0, is-glob@^2.0.1: dependencies: is-extglob "^1.0.0" +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + dependencies: + is-extglob "^2.1.1" + is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -1307,6 +1465,16 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + +is-observable@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-0.2.0.tgz#b361311d83c6e5d726cabf5e250b0237106f5ae2" + dependencies: + symbol-observable "^0.2.2" + is-posix-bracket@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" @@ -1315,7 +1483,15 @@ is-primitive@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" -is-stream@^1.1.0: +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + +is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -1349,29 +1525,36 @@ isobject@^2.0.0: dependencies: isarray "1.0.0" +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" istanbul-api@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620" + version "1.2.2" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.2.tgz#e17cd519dd5ec4141197f246fdf380b75487f3b1" dependencies: async "^2.1.4" fileset "^2.0.2" - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.1.2" istanbul-lib-hook "^1.1.0" - istanbul-lib-instrument "^1.9.1" - istanbul-lib-report "^1.1.2" - istanbul-lib-source-maps "^1.2.2" - istanbul-reports "^1.1.3" + istanbul-lib-instrument "^1.9.2" + istanbul-lib-report "^1.1.3" + istanbul-lib-source-maps "^1.2.3" + istanbul-reports "^1.1.4" js-yaml "^3.7.0" mkdirp "^0.5.1" once "^1.4.0" -istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" +istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.2.tgz#4113c8ff6b7a40a1ef7350b01016331f63afde14" istanbul-lib-hook@^1.1.0: version "1.1.0" @@ -1379,40 +1562,40 @@ istanbul-lib-hook@^1.1.0: dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e" +istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.2.tgz#84905bf47f7e0b401d6b840da7bad67086b4aab6" dependencies: babel-generator "^6.18.0" babel-template "^6.16.0" babel-traverse "^6.18.0" babel-types "^6.18.0" babylon "^6.18.0" - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.1.2" semver "^5.3.0" -istanbul-lib-report@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425" +istanbul-lib-report@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.3.tgz#2df12188c0fa77990c0d2176d2d0ba3394188259" dependencies: - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.1.2" mkdirp "^0.5.1" path-parse "^1.0.5" supports-color "^3.1.2" -istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c" +istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.3.tgz#20fb54b14e14b3fb6edb6aca3571fd2143db44e6" dependencies: debug "^3.1.0" - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.1.2" mkdirp "^0.5.1" rimraf "^2.6.1" source-map "^0.5.3" -istanbul-reports@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10" +istanbul-reports@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.4.tgz#5ccba5e22b7b5a5d91d5e0a830f89be334bf97bd" dependencies: handlebars "^4.0.3" @@ -1626,7 +1809,7 @@ jest-util@^21.2.1: jest-validate "^21.2.1" mkdirp "^0.5.1" -jest-validate@^21.2.1: +jest-validate@^21.1.0, jest-validate@^21.2.1: version "21.2.1" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-21.2.1.tgz#cc0cbca653cd54937ba4f2a111796774530dd3c7" dependencies: @@ -1645,7 +1828,7 @@ js-tokens@^3.0.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.7.0: +js-yaml@^3.7.0, js-yaml@^3.9.0: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: @@ -1785,6 +1968,78 @@ liftoff@~2.2.0: rechoir "^0.6.2" resolve "^1.1.7" +lint-staged@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-5.0.0.tgz#f1c670e03e2fdf3f3d0eb81f72d3bcf658770e54" + dependencies: + app-root-path "^2.0.0" + chalk "^2.1.0" + commander "^2.11.0" + cosmiconfig "^3.1.0" + dedent "^0.7.0" + execa "^0.8.0" + find-parent-dir "^0.3.0" + is-glob "^4.0.0" + jest-validate "^21.1.0" + listr "^0.13.0" + lodash "^4.17.4" + log-symbols "^2.0.0" + minimatch "^3.0.0" + npm-which "^3.0.1" + p-map "^1.1.1" + path-is-inside "^1.0.2" + pify "^3.0.0" + staged-git-files "0.0.4" + stringify-object "^3.2.0" + +listr-silent-renderer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" + +listr-update-renderer@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.4.0.tgz#344d980da2ca2e8b145ba305908f32ae3f4cc8a7" + dependencies: + chalk "^1.1.3" + cli-truncate "^0.2.1" + elegant-spinner "^1.0.1" + figures "^1.7.0" + indent-string "^3.0.0" + log-symbols "^1.0.2" + log-update "^1.0.2" + strip-ansi "^3.0.1" + +listr-verbose-renderer@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#8206f4cf6d52ddc5827e5fd14989e0e965933a35" + dependencies: + chalk "^1.1.3" + cli-cursor "^1.0.2" + date-fns "^1.27.2" + figures "^1.7.0" + +listr@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/listr/-/listr-0.13.0.tgz#20bb0ba30bae660ee84cc0503df4be3d5623887d" + dependencies: + chalk "^1.1.3" + cli-truncate "^0.2.1" + figures "^1.7.0" + indent-string "^2.1.0" + is-observable "^0.2.0" + is-promise "^2.1.0" + is-stream "^1.1.0" + listr-silent-renderer "^1.1.1" + listr-update-renderer "^0.4.0" + listr-verbose-renderer "^0.4.0" + log-symbols "^1.0.2" + log-update "^1.0.2" + ora "^0.2.3" + p-map "^1.1.1" + rxjs "^5.4.2" + stream-to-observable "^0.2.0" + strip-ansi "^3.0.1" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -1812,14 +2067,33 @@ locate-path@^2.0.0: path-exists "^3.0.0" lodash@^4.14.0, lodash@^4.17.4, lodash@^4.6.0: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + version "4.17.5" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" + +log-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" + dependencies: + chalk "^1.0.0" + +log-symbols@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + dependencies: + chalk "^2.0.1" + +log-update@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1" + dependencies: + ansi-escapes "^1.0.0" + cli-cursor "^1.0.2" longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" -loose-envify@^1.0.0: +loose-envify@^1.0.0, loose-envify@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" dependencies: @@ -1856,7 +2130,7 @@ merge@^1.1.3: version "1.2.0" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" -methods@^1.1.1, methods@~1.1.2: +methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -1878,23 +2152,23 @@ micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7: parse-glob "^3.0.4" regex-cache "^0.4.2" -mime-db@~1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" -mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.7: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" dependencies: - mime-db "~1.30.0" + mime-db "~1.33.0" -mime@1.4.1, mime@^1.4.1: +mime@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" mimic-fn@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" @@ -1929,8 +2203,8 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" nan@^2.3.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" + version "2.9.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.9.2.tgz#f564d75f5f8f36a6d9456cca7a6c4fe488ab7866" nan@~2.7.0: version "2.7.0" @@ -1944,18 +2218,25 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" +node-fetch@^1.0.1, node-fetch@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" node-notifier@^5.0.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff" + version "5.2.1" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea" dependencies: growly "^1.3.0" - semver "^5.3.0" - shellwords "^0.1.0" - which "^1.2.12" + semver "^5.4.1" + shellwords "^0.1.1" + which "^1.3.0" node-pre-gyp@^0.6.39, node-pre-gyp@~0.6.38: version "0.6.39" @@ -1989,18 +2270,36 @@ normalize-package-data@^2.3.2: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" +normalize-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379" + normalize-path@^2.0.0, normalize-path@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: remove-trailing-separator "^1.0.1" +npm-path@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.4.tgz#c641347a5ff9d6a09e4d9bce5580c4f505278e64" + dependencies: + which "^1.2.10" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" dependencies: path-key "^2.0.0" +npm-which@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa" + dependencies: + commander "^2.9.0" + npm-path "^2.0.2" + which "^1.2.10" + npmlog@^4.0.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -2022,7 +2321,7 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" -object-assign@^4.1.0: +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -2049,6 +2348,10 @@ once@^1.3.0, once@^1.3.3, once@^1.4.0: dependencies: wrappy "1" +onetime@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" @@ -2067,6 +2370,15 @@ optionator@^0.8.1: type-check "~0.3.2" wordwrap "~1.0.0" +ora@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" + dependencies: + chalk "^1.1.1" + cli-cursor "^1.0.2" + cli-spinners "^0.1.2" + object-assign "^4.0.1" + os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -2084,8 +2396,8 @@ os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" osenv@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" dependencies: os-homedir "^1.0.0" os-tmpdir "^1.0.0" @@ -2099,8 +2411,10 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" p-limit@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" + dependencies: + p-try "^1.0.0" p-locate@^2.0.0: version "2.0.0" @@ -2108,6 +2422,14 @@ p-locate@^2.0.0: dependencies: p-limit "^1.1.0" +p-map@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + parse-glob@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" @@ -2123,6 +2445,12 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" +parse-json@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-3.0.0.tgz#fa6f47b18e23826ead32f263e744d0e1e847fb13" + dependencies: + error-ex "^1.3.1" + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -2149,6 +2477,10 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: 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.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + path-key@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -2213,9 +2545,15 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" -prettier@^1.5.2: - version "1.7.4" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.7.4.tgz#5e8624ae9363c80f95ec644584ecdf55d74f93fa" +prettier-check@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prettier-check/-/prettier-check-2.0.0.tgz#edd086ee12d270579233ccb136a16e6afcfba1ae" + dependencies: + execa "^0.6.0" + +prettier@^1.8.2: + version "1.11.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75" pretty-format@^21.2.1: version "21.2.1" @@ -2228,20 +2566,34 @@ private@^0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" -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" +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + dependencies: + asap "~2.0.3" + +prop-types@^15.6.1: + version "15.6.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.3.1" + object-assign "^4.1.1" proxy-addr@~2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" + version "2.0.3" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" dependencies: forwarded "~0.1.2" - ipaddr.js "1.5.2" + ipaddr.js "1.6.0" -prr@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" pseudomap@^1.0.2: version "1.0.2" @@ -2255,7 +2607,7 @@ punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -qs@6.5.1, qs@^6.5.1, qs@~6.5.1: +qs@6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" @@ -2282,7 +2634,7 @@ range-parser@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" -raw-body@2.3.2: +raw-body@2.3.2, raw-body@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" dependencies: @@ -2292,8 +2644,8 @@ raw-body@2.3.2: unpipe "1.0.0" rc@^1.1.7: - version "1.2.2" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077" + version "1.2.5" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.5.tgz#275cd687f6e3b36cc756baa26dfee80a790301fd" dependencies: deep-extend "~0.4.0" ini "~1.3.0" @@ -2339,14 +2691,14 @@ readable-stream@^1.1.12: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" +readable-stream@^2.0.6, readable-stream@^2.1.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.4.tgz#c946c3f47fa7d8eabc0b6150f4a12f69a4574071" dependencies: core-util-is "~1.0.0" inherits "~2.0.3" isarray "~1.0.0" - process-nextick-args "~1.0.6" + process-nextick-args "~2.0.0" safe-buffer "~5.1.1" string_decoder "~1.0.3" util-deprecate "~1.0.1" @@ -2358,14 +2710,14 @@ rechoir@^0.6.2: resolve "^1.1.6" redis-commands@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.1.tgz#81d826f45fa9c8b2011f4cd7a0fe597d241d442b" + version "1.3.5" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.5.tgz#4495889414f1e886261180b1442e7295602d83a2" redis-parser@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" -redis@^2.1.0, redis@^2.7.1: +redis@^2.1.0: version "2.8.0" resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02" dependencies: @@ -2374,8 +2726,8 @@ redis@^2.1.0, redis@^2.7.1: redis-parser "^2.6.0" regenerator-runtime@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" regex-cache@^0.4.2: version "0.4.4" @@ -2459,6 +2811,10 @@ require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" +require-from-string@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.1.tgz#c545233e9d7da6616e9d59adfb39fc9f588676ff" + require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" @@ -2480,6 +2836,13 @@ resolve@^1.1.6, resolve@^1.1.7: dependencies: path-parse "^1.0.5" +restore-cursor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" + dependencies: + exit-hook "^1.0.0" + onetime "^1.0.0" + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -2492,13 +2855,19 @@ rimraf@2, rimraf@^2.5.1, rimraf@^2.6.1: dependencies: glob "^7.0.5" +rxjs@^5.4.2: + version "5.5.6" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.6.tgz#e31fb96d6fd2ff1fd84bcea8ae9c02d007179c02" + dependencies: + symbol-observable "1.0.1" + safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, 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" sane@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-2.2.0.tgz#d6d2e2fcab00e3d283c93b912b7c3a20846f1d56" + version "2.4.1" + resolved "https://registry.yarnpkg.com/sane/-/sane-2.4.1.tgz#29f991208cf28636720efdc584293e7fd66663a5" dependencies: anymatch "^1.3.0" exec-sh "^0.2.0" @@ -2514,9 +2883,9 @@ sax@^1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" -"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" +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" send@0.16.1: version "0.16.1" @@ -2549,6 +2918,10 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + setprototypeof@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" @@ -2567,7 +2940,7 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" -shellwords@^0.1.0: +shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -2579,6 +2952,10 @@ slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" +slice-ansi@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" + sntp@1.x.x: version "1.0.9" resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" @@ -2603,23 +2980,35 @@ source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.6: +source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, 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" +source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + +spdx-correct@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" dependencies: - spdx-license-ids "^1.0.2" + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" -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-exceptions@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" -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" +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" sprintf-js@~1.0.2: version "1.0.3" @@ -2646,6 +3035,10 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" +staged-git-files@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-0.0.4.tgz#d797e1b551ca7a639dec0237dc6eb4bb9be17d35" + "statuses@>= 1.3.1 < 2": version "1.4.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" @@ -2654,6 +3047,12 @@ statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" +stream-to-observable@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.2.0.tgz#59d6ea393d87c2c0ddac10aa0d561bc6ba6f0e10" + dependencies: + any-observable "^0.2.0" + string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" @@ -2686,6 +3085,14 @@ string_decoder@~1.0.3: dependencies: safe-buffer "~5.1.0" +stringify-object@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.2.2.tgz#9853052e5a88fb605a44cd27445aa257ad7ffbcd" + dependencies: + get-own-enumerable-property-symbols "^2.0.1" + is-obj "^1.0.1" + is-regexp "^1.0.0" + stringstream@~0.0.4, stringstream@~0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" @@ -2716,32 +3123,14 @@ strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" +strip-indent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" + 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" -superagent@^3.0.0: - version "3.8.1" - resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.1.tgz#2571fd921f3fcdba43ac68c3b35c91951532701f" - dependencies: - component-emitter "^1.2.0" - cookiejar "^2.1.0" - debug "^3.1.0" - extend "^3.0.0" - form-data "^2.3.1" - formidable "^1.1.1" - methods "^1.1.1" - mime "^1.4.1" - qs "^6.5.1" - readable-stream "^2.0.5" - -supertest@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/supertest/-/supertest-3.0.0.tgz#8d4bb68fd1830ee07033b1c5a5a9a4021c965296" - dependencies: - methods "~1.1.2" - superagent "^3.0.0" - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -2752,11 +3141,19 @@ supports-color@^3.1.2: dependencies: has-flag "^1.0.0" -supports-color@^4.0.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" +supports-color@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a" dependencies: - has-flag "^2.0.0" + has-flag "^3.0.0" + +symbol-observable@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" + +symbol-observable@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40" symbol-tree@^3.2.1: version "3.2.2" @@ -2784,8 +3181,8 @@ tar@^2.2.1: inherits "2" test-exclude@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" + version "4.2.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.0.tgz#07e3613609a362c74516a717515e13322ab45b3c" dependencies: arrify "^1.0.1" micromatch "^2.3.11" @@ -2812,8 +3209,8 @@ to-fast-properties@^1.0.3: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" tough-cookie@^2.3.2, tough-cookie@~2.3.0, tough-cookie@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" dependencies: punycode "^1.4.1" @@ -2842,11 +3239,15 @@ type-check@~0.3.2: prelude-ls "~1.1.2" type-is@~1.6.15: - version "1.6.15" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" + version "1.6.16" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" dependencies: media-typer "0.3.0" - mime-types "~2.1.15" + mime-types "~2.1.18" + +ua-parser-js@^0.7.9: + version "0.7.17" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" uglify-js@^2.6: version "2.8.29" @@ -2895,8 +3296,8 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" uuid@^3.0.0, uuid@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + version "3.2.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" v8flags@^2.0.2: version "2.1.1" @@ -2905,11 +3306,11 @@ v8flags@^2.0.2: user-home "^1.1.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" + version "3.0.3" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338" dependencies: - spdx-correct "~1.0.0" - spdx-expression-parse "~1.0.0" + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" vary@~1.1.2: version "1.1.2" @@ -2950,6 +3351,10 @@ whatwg-encoding@^1.0.1: dependencies: iconv-lite "0.4.19" +whatwg-fetch@>=0.10.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" + whatwg-url@^4.3.0: version "4.8.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0" @@ -2961,7 +3366,7 @@ which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" -which@^1.2.12, which@^1.2.9: +which@^1.2.10, which@^1.2.12, which@^1.2.9, which@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" dependencies: @@ -2990,11 +3395,11 @@ wordwrap@~1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" worker-farm@^1.3.1: - version "1.5.2" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.2.tgz#32b312e5dc3d5d45d79ef44acc2587491cd729ae" + version "1.5.4" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.4.tgz#4debbe46b40edefcc717ebde74a90b1ae1e909a1" dependencies: - errno "^0.1.4" - xtend "^4.0.1" + errno "~0.1.7" + xtend "~4.0.1" wrap-ansi@^2.0.0: version "2.1.0" @@ -3019,7 +3424,7 @@ xml-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" -xtend@^4.0.1: +xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"