diff --git a/lib/app.js b/lib/app.js index a810675936..878d040697 100644 --- a/lib/app.js +++ b/lib/app.js @@ -68,14 +68,11 @@ module.exports = function(config) { } // Setup API routes - const apiRouter = express.Router(); // eslint-disable-line new-cap - routes.createAPI(apiRouter, config); - app.use(`${baseUriPath}/api/`, apiRouter); - - // Setup deprecated routes - const router = express.Router(); // eslint-disable-line new-cap - routes.createLegacy(router, config); - app.use(baseUriPath, router); + const middleware = routes.router(config); + if (!middleware) { + throw new Error('Routes invalid'); + } + app.use(`${baseUriPath}/`, middleware); if (process.env.NODE_ENV !== 'production') { app.use(errorHandler()); diff --git a/lib/app.test.js b/lib/app.test.js index 16e4fad8a3..3b3717e2bc 100644 --- a/lib/app.test.js +++ b/lib/app.test.js @@ -1,11 +1,11 @@ 'use strict'; -const test = require('ava'); +const { test } = require('ava'); +const express = require('express'); const proxyquire = require('proxyquire'); const getApp = proxyquire('./app', { './routes': { - createAPI: () => {}, - createLegacy: () => {}, + router: () => express.Router(), }, }); diff --git a/lib/routes/admin-api/applications.js b/lib/routes/admin-api/applications.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/routes/admin-api/applications.test.js b/lib/routes/admin-api/applications.test.js new file mode 100644 index 0000000000..851c2cf383 --- /dev/null +++ b/lib/routes/admin-api/applications.test.js @@ -0,0 +1,39 @@ +'use strict'; + +const { test } = require('ava'); +const store = require('./../../../test/fixtures/store'); +const supertest = require('supertest'); +const logger = require('../../logger'); +const getApp = require('../../app'); + +const { EventEmitter } = require('events'); +const eventBus = new EventEmitter(); + +test.beforeEach(() => { + logger.setLevel('FATAL'); +}); + +function getSetup() { + const stores = store.createStores(); + const app = getApp({ + baseUriPath: '', + stores, + eventBus, + }); + + return { + request: supertest(app), + stores, + }; +} + +test('should return list of client applications', t => { + t.plan(1); + const { request } = getSetup(); + return request + .get('/api/admin/metrics/applications') + .expect(200) + .expect(res => { + t.true(res.body.applications.length === 0); + }); +}); diff --git a/lib/routes/admin-api/archive.js b/lib/routes/admin-api/archive.js new file mode 100644 index 0000000000..5b1791cc2f --- /dev/null +++ b/lib/routes/admin-api/archive.js @@ -0,0 +1,46 @@ +'use strict'; + +const { Router } = require('express'); + +const logger = require('../../logger'); +const { FEATURE_REVIVED } = require('../../event-type'); +const ValidationError = require('../../error/validation-error'); +const validateRequest = require('../../error/validate-request'); + +const handleErrors = (req, res, error) => { + switch (error.constructor) { + case ValidationError: + return res.status(400).json(req.validationErrors()).end(); + default: + logger.error('Server failed executing request', error); + return res.status(500).end(); + } +}; + +module.exports.router = function(config) { + const { featureToggleStore, eventStore } = config.stores; + const router = Router(); + + router.get('/features', (req, res) => { + featureToggleStore.getArchivedFeatures().then(archivedFeatures => { + res.json({ features: archivedFeatures }); + }); + }); + + router.post('/revive/:name', (req, res) => { + req.checkParams('name', 'Name is required').notEmpty(); + + validateRequest(req) + .then(() => + eventStore.store({ + type: FEATURE_REVIVED, + createdBy: req.connection.remoteAddress, + data: { name: req.params.name }, + }) + ) + .then(() => res.status(200).end()) + .catch(error => handleErrors(req, res, error)); + }); + + return router; +}; diff --git a/lib/routes/admin-api/archive.test.js b/lib/routes/admin-api/archive.test.js new file mode 100644 index 0000000000..f571a2d62a --- /dev/null +++ b/lib/routes/admin-api/archive.test.js @@ -0,0 +1,5 @@ +'use strict'; + +const { test } = require('ava'); + +test.todo('should unit test archive'); diff --git a/lib/routes/event.js b/lib/routes/admin-api/event.js similarity index 71% rename from lib/routes/event.js rename to lib/routes/admin-api/event.js index 8afb4db65d..f5842d5bfb 100644 --- a/lib/routes/event.js +++ b/lib/routes/admin-api/event.js @@ -1,19 +1,22 @@ 'use strict'; -const eventDiffer = require('../event-differ'); +const { Router } = require('express'); + +const eventDiffer = require('../../event-differ'); const version = 1; -module.exports = function (app, config) { +module.exports.router = function(config) { const { eventStore } = config.stores; + const router = Router(); - app.get('/events', (req, res) => { + router.get('/', (req, res) => { eventStore.getEvents().then(events => { eventDiffer.addDiffs(events); res.json({ version, events }); }); }); - app.get('/events/:name', (req, res) => { + router.get('/:name', (req, res) => { const toggleName = req.params.name; eventStore.getEventsFilterByName(toggleName).then(events => { if (events) { @@ -27,4 +30,6 @@ module.exports = function (app, config) { } }); }); + + return router; }; diff --git a/lib/routes/admin-api/events.test.js b/lib/routes/admin-api/events.test.js new file mode 100644 index 0000000000..b9d37f1e23 --- /dev/null +++ b/lib/routes/admin-api/events.test.js @@ -0,0 +1,39 @@ +'use strict'; + +const { test } = require('ava'); + +const store = require('./../../../test/fixtures/store'); +const supertest = require('supertest'); +const logger = require('../../logger'); +const getApp = require('../../app'); + +const { EventEmitter } = require('events'); +const eventBus = new EventEmitter(); + +function getSetup() { + const base = `/random${Math.round(Math.random() * 1000)}`; + const stores = store.createStores(); + const app = getApp({ + baseUriPath: base, + stores, + eventBus, + }); + + return { base, eventStore: stores.eventStore, request: supertest(app) }; +} + +test.beforeEach(() => { + logger.setLevel('FATAL'); +}); + +test('should get empty events list via admin', t => { + t.plan(1); + const { request, base } = getSetup(); + return request + .get(`${base}/api/admin/events`) + .expect('Content-Type', /json/) + .expect(200) + .expect(res => { + t.true(res.body.events.length === 0); + }); +}); diff --git a/lib/routes/admin-api/feature.js b/lib/routes/admin-api/feature.js new file mode 100644 index 0000000000..852e9c256b --- /dev/null +++ b/lib/routes/admin-api/feature.js @@ -0,0 +1,196 @@ +'use strict'; + +const { Router } = require('express'); +const joi = require('joi'); + +const logger = require('../../logger'); +const { + FEATURE_CREATED, + FEATURE_UPDATED, + FEATURE_ARCHIVED, +} = require('../../event-type'); +const NameExistsError = require('../../error/name-exists-error'); +const NotFoundError = require('../../error/notfound-error'); +const ValidationError = require('../../error/validation-error.js'); +const validateRequest = require('../../error/validate-request'); +const extractUser = require('../../extract-user'); + +const handleErrors = (req, res, error) => { + logger.warn('Error creating or updating feature', error); + switch (error.constructor) { + case NotFoundError: + return res.status(404).end(); + case NameExistsError: + return res + .status(403) + .json([ + { + msg: + 'A feature with this name already exists. Try re-activating it from the archive.', + }, + ]) + .end(); + case ValidationError: + return res.status(400).json(req.validationErrors()).end(); + default: + logger.error('Server failed executing request', error); + return res.status(500).end(); + } +}; + +const strategiesSchema = joi.object().keys({ + name: joi.string().regex(/^[a-zA-Z0-9\\.\\-]{3,100}$/).required(), + parameters: joi.object(), +}); + +function validateStrategy(featureToggle) { + return new Promise((resolve, reject) => { + if ( + !featureToggle.strategies || + featureToggle.strategies.length === 0 + ) { + return reject( + new ValidationError('You must define at least one strategy') + ); + } + + featureToggle.strategies = featureToggle.strategies.map( + strategyConfig => { + const result = joi.validate(strategyConfig, strategiesSchema); + if (result.error) { + throw result.error; + } + return result.value; + } + ); + + return resolve(featureToggle); + }); +} + +const version = 1; + +module.exports.router = function(config) { + const { featureToggleStore, eventStore } = config.stores; + const router = Router(); + + router.get('/', (req, res) => { + featureToggleStore + .getFeatures() + .then(features => res.json({ version, features })); + }); + + router.get('/:featureName', (req, res) => { + featureToggleStore + .getFeature(req.params.featureName) + .then(feature => res.json(feature).end()) + .catch(() => + res.status(404).json({ error: 'Could not find feature' }) + ); + }); + + function validateUniqueName(req) { + return new Promise((resolve, reject) => { + featureToggleStore + .getFeature(req.body.name) + .then(() => + reject(new NameExistsError('Feature name already exist')) + ) + .catch(() => resolve(req)); + }); + } + + router.post('/validate', (req, res) => { + req.checkBody('name', 'Name is required').notEmpty(); + req + .checkBody('name', 'Name must match format ^[0-9a-zA-Z\\.\\-]+$') + .matches(/^[0-9a-zA-Z\\.\\-]+$/i); + + validateRequest(req) + .then(validateUniqueName) + .then(() => res.status(201).end()) + .catch(error => handleErrors(req, res, error)); + }); + + router.post('/', (req, res) => { + req.checkBody('name', 'Name is required').notEmpty(); + req + .checkBody('name', 'Name must match format ^[0-9a-zA-Z\\.\\-]+$') + .matches(/^[0-9a-zA-Z\\.\\-]+$/i); + const userName = extractUser(req); + + validateRequest(req) + .then(validateUniqueName) + .then(_req => _req.body) + .then(validateStrategy) + .then(featureToggle => + eventStore.store({ + type: FEATURE_CREATED, + createdBy: userName, + data: featureToggle, + }) + ) + .then(() => res.status(201).end()) + .catch(error => handleErrors(req, res, error)); + }); + + router.put('/:featureName', (req, res) => { + const featureName = req.params.featureName; + const userName = extractUser(req); + const updatedFeature = req.body; + + updatedFeature.name = featureName; + + featureToggleStore + .getFeature(featureName) + .then(() => validateStrategy(updatedFeature)) + .then(() => + eventStore.store({ + type: FEATURE_UPDATED, + createdBy: userName, + data: updatedFeature, + }) + ) + .then(() => res.status(200).end()) + .catch(error => handleErrors(req, res, error)); + }); + + router.post('/:featureName/toggle', (req, res) => { + const featureName = req.params.featureName; + const userName = extractUser(req); + + featureToggleStore + .getFeature(featureName) + .then(feature => { + feature.enabled = !feature.enabled; + return eventStore.store({ + type: FEATURE_UPDATED, + createdBy: userName, + data: feature, + }); + }) + .then(() => res.status(200).end()) + .catch(error => handleErrors(req, res, error)); + }); + + router.delete('/:featureName', (req, res) => { + const featureName = req.params.featureName; + const userName = extractUser(req); + + featureToggleStore + .getFeature(featureName) + .then(() => + eventStore.store({ + type: FEATURE_ARCHIVED, + createdBy: userName, + data: { + name: featureName, + }, + }) + ) + .then(() => res.status(200).end()) + .catch(error => handleErrors(req, res, error)); + }); + + return router; +}; diff --git a/test/unit/routes/feature.test.js b/lib/routes/admin-api/feature.test.js similarity index 62% rename from test/unit/routes/feature.test.js rename to lib/routes/admin-api/feature.test.js index 2d17f56ed2..67a4911941 100644 --- a/test/unit/routes/feature.test.js +++ b/lib/routes/admin-api/feature.test.js @@ -1,19 +1,19 @@ 'use strict'; -const test = require('ava'); -const store = require('./fixtures/store'); +const { test } = require('ava'); +const store = require('./../../../test/fixtures/store'); const supertest = require('supertest'); -const logger = require('../../../lib/logger'); -const getApp = require('../../../lib/app'); +const logger = require('../../logger'); +const getApp = require('../../app'); const { EventEmitter } = require('events'); const eventBus = new EventEmitter(); -test.beforeEach(() => { +test.beforeEach(() => { logger.setLevel('FATAL'); }); -function getSetup () { +function getSetup() { const base = `/random${Math.round(Math.random() * 1000)}`; const stores = store.createStores(); const app = getApp({ @@ -29,60 +29,74 @@ function getSetup () { }; } -test('should get empty getFeatures', t => { +test('should get empty getFeatures via admin', t => { + t.plan(1); const { request, base } = getSetup(); return request - .get(`${base}/features`) + .get(`${base}/api/admin/features`) .expect('Content-Type', /json/) .expect(200) - .expect((res) => { + .expect(res => { t.true(res.body.features.length === 0); }); }); test('should get one getFeature', t => { + t.plan(1); const { request, featureToggleStore, base } = getSetup(); - featureToggleStore.addFeature({ name: 'test_', strategies: [{ name: 'default_' }] }); + featureToggleStore.addFeature({ + name: 'test_', + strategies: [{ name: 'default_' }], + }); return request - .get(`${base}/features`) + .get(`${base}/api/admin/features`) .expect('Content-Type', /json/) .expect(200) - .expect((res) => { + .expect(res => { t.true(res.body.features.length === 1); }); }); test('should add version numbers for /features', t => { + t.plan(1); const { request, featureToggleStore, base } = getSetup(); - featureToggleStore.addFeature({ name: 'test2', strategies: [{ name: 'default' }] }); + featureToggleStore.addFeature({ + name: 'test2', + strategies: [{ name: 'default' }], + }); return request - .get(`${base}/features`) + .get(`${base}/api/admin/features`) .expect('Content-Type', /json/) .expect(200) - .expect((res) => { + .expect(res => { t.true(res.body.version === 1); }); }); test('should require at least one strategy when creating a feature toggle', t => { + t.plan(0); const { request, base } = getSetup(); return request - .post(`${base}/features`) + .post(`${base}/api/admin/features`) .send({ name: 'sample.missing.strategy' }) .set('Content-Type', 'application/json') - .expect(400) + .expect(400); }); test('should require at least one strategy when updating a feature toggle', t => { + t.plan(0); const { request, featureToggleStore, base } = getSetup(); - featureToggleStore.addFeature({ name: 'ts', strategies: [{ name: 'default' }] }); + featureToggleStore.addFeature({ + name: 'ts', + strategies: [{ name: 'default' }], + }); return request - .put(`${base}/features/ts`) + .put(`${base}/api/admin/features/ts`) .send({ name: 'ts' }) .set('Content-Type', 'application/json') - .expect(400) + .expect(400); }); diff --git a/lib/routes/admin-api/index.js b/lib/routes/admin-api/index.js new file mode 100644 index 0000000000..72b28e07be --- /dev/null +++ b/lib/routes/admin-api/index.js @@ -0,0 +1,38 @@ +'use strict'; + +const { Router } = require('express'); + +const features = require('./feature.js'); +const featureArchive = require('./archive.js'); +const events = require('./event.js'); +const strategies = require('./strategy'); +const metrics = require('./metrics'); + +const apiDef = { + version: 2, + links: { + 'feature-toggles': { uri: '/api/admin/features' }, + 'feature-archive': { uri: '/api/admin/archive' }, + strategies: { uri: '/api/admin/strategies' }, + events: { uri: '/api/admin/events' }, + metrics: { uri: '/api/admin/metrics' }, + }, +}; + +exports.apiDef = apiDef; + +exports.router = config => { + const router = Router(); + + router.get('/', (req, res) => { + res.json(apiDef); + }); + + router.use('/features', features.router(config)); + router.use('/archive', featureArchive.router(config)); + router.use('/strategies', strategies.router(config)); + router.use('/events', events.router(config)); + router.use('/metrics', metrics.router(config)); + + return router; +}; diff --git a/lib/routes/admin-api/metrics.js b/lib/routes/admin-api/metrics.js new file mode 100644 index 0000000000..b57d6a5de6 --- /dev/null +++ b/lib/routes/admin-api/metrics.js @@ -0,0 +1,137 @@ +'use strict'; + +const { Router } = require('express'); + +const logger = require('../../logger'); +const ClientMetrics = require('../../client-metrics'); +const { catchLogAndSendErrorResponse } = require('./route-utils'); + +exports.router = function(config) { + const { + clientMetricsStore, + clientInstanceStore, + clientApplicationsStore, + strategyStore, + featureToggleStore, + } = config.stores; + + const metrics = new ClientMetrics(clientMetricsStore); + const router = Router(); + + router.get('/seen-toggles', (req, res) => { + const seenAppToggles = metrics.getAppsWithToggles(); + res.json(seenAppToggles); + }); + + router.get('/seen-apps', (req, res) => { + const seenApps = metrics.getSeenAppsPerToggle(); + clientApplicationsStore + .getApplications() + .then(toLookup) + .then(metaData => { + Object.keys(seenApps).forEach(key => { + seenApps[key] = seenApps[key].map(entry => { + if (metaData[entry.appName]) { + return Object.assign( + {}, + entry, + metaData[entry.appName] + ); + } + return entry; + }); + }); + res.json(seenApps); + }); + }); + + router.get('/feature-toggles', (req, res) => { + res.json(metrics.getTogglesMetrics()); + }); + + router.get('/feature-toggles/:name', (req, res) => { + const name = req.params.name; + const data = metrics.getTogglesMetrics(); + const lastHour = data.lastHour[name] || {}; + const lastMinute = data.lastMinute[name] || {}; + res.json({ + lastHour, + lastMinute, + }); + }); + + router.post('/applications/:appName', (req, res) => { + const input = Object.assign({}, req.body, { + appName: req.params.appName, + }); + clientApplicationsStore + .upsert(input) + .then(() => res.status(202).end()) + .catch(e => { + logger.error(e); + res.status(500).end(); + }); + }); + + function toLookup(metaData) { + return metaData.reduce((result, entry) => { + result[entry.appName] = entry; + return result; + }, {}); + } + + router.get('/applications/', (req, res) => { + clientApplicationsStore + .getApplications(req.query) + .then(applications => res.json({ applications })) + .catch(err => catchLogAndSendErrorResponse(err, res)); + }); + + router.get('/applications/:appName', (req, res) => { + const appName = req.params.appName; + const seenToggles = metrics.getSeenTogglesByAppName(appName); + + Promise.all([ + clientApplicationsStore.getApplication(appName), + clientInstanceStore.getByAppName(appName), + strategyStore.getStrategies(), + featureToggleStore.getFeatures(), + ]) + .then(([application, instances, strategies, features]) => { + const appDetails = { + appName: application.appName, + createdAt: application.createdAt, + description: application.description, + url: application.url, + color: application.color, + icon: application.icon, + strategies: application.strategies.map(name => { + const found = strategies.find( + feature => feature.name === name + ); + if (found) { + return found; + } + return { name, notFound: true }; + }), + instances, + seenToggles: seenToggles.map(name => { + const found = features.find( + feature => feature.name === name + ); + if (found) { + return found; + } + return { name, notFound: true }; + }), + links: { + self: `/api/applications/${application.appName}`, + }, + }; + res.json(appDetails); + }) + .catch(err => catchLogAndSendErrorResponse(err, res)); + }); + + return router; +}; diff --git a/lib/routes/admin-api/metrics.test.js b/lib/routes/admin-api/metrics.test.js new file mode 100644 index 0000000000..8d82389fa9 --- /dev/null +++ b/lib/routes/admin-api/metrics.test.js @@ -0,0 +1,100 @@ +'use strict'; + +const { test } = require('ava'); +const store = require('./../../../test/fixtures/store'); +const supertest = require('supertest'); +const logger = require('../../logger'); +const getApp = require('../../app'); + +const { EventEmitter } = require('events'); +const eventBus = new EventEmitter(); + +test.beforeEach(() => { + logger.setLevel('FATAL'); +}); + +function getSetup() { + const stores = store.createStores(); + const app = getApp({ + baseUriPath: '', + stores, + eventBus, + }); + + return { + request: supertest(app), + stores, + }; +} + +test('should return seen toggles even when there is nothing', t => { + t.plan(1); + const { request } = getSetup(); + return request + .get('/api/admin/metrics/seen-toggles') + .expect(200) + .expect(res => { + t.true(res.body.length === 0); + }); +}); + +test('should return list of seen-toggles per app', t => { + t.plan(3); + const { request, stores } = getSetup(); + const appName = 'asd!23'; + stores.clientMetricsStore.emit('metrics', { + appName, + instanceId: 'instanceId', + bucket: { + start: new Date(), + stop: new Date(), + toggles: { + toggleX: { yes: 123, no: 0 }, + toggleY: { yes: 123, no: 0 }, + }, + }, + }); + + return request + .get('/api/admin/metrics/seen-toggles') + .expect(200) + .expect(res => { + const seenAppsWithToggles = res.body; + t.true(seenAppsWithToggles.length === 1); + t.true(seenAppsWithToggles[0].appName === appName); + t.true(seenAppsWithToggles[0].seenToggles.length === 2); + }); +}); + +test('should return feature-toggles metrics even when there is nothing', t => { + t.plan(0); + const { request } = getSetup(); + return request.get('/api/admin/metrics/feature-toggles').expect(200); +}); + +test('should return metrics for all toggles', t => { + t.plan(2); + const { request, stores } = getSetup(); + const appName = 'asd!23'; + stores.clientMetricsStore.emit('metrics', { + appName, + instanceId: 'instanceId', + bucket: { + start: new Date(), + stop: new Date(), + toggles: { + toggleX: { yes: 123, no: 0 }, + toggleY: { yes: 123, no: 0 }, + }, + }, + }); + + return request + .get('/api/admin/metrics/feature-toggles') + .expect(200) + .expect(res => { + const metrics = res.body; + t.true(metrics.lastHour !== undefined); + t.true(metrics.lastMinute !== undefined); + }); +}); diff --git a/lib/routes/route-utils.js b/lib/routes/admin-api/route-utils.js similarity index 81% rename from lib/routes/route-utils.js rename to lib/routes/admin-api/route-utils.js index 22aac4f6e3..fcfcb29dd3 100644 --- a/lib/routes/route-utils.js +++ b/lib/routes/admin-api/route-utils.js @@ -1,6 +1,6 @@ 'use strict'; -const logger = require('../logger'); +const logger = require('../../logger'); const catchLogAndSendErrorResponse = (err, res) => { logger.error(err); diff --git a/lib/routes/strategy-schema.js b/lib/routes/admin-api/strategy-schema.js similarity index 63% rename from lib/routes/strategy-schema.js rename to lib/routes/admin-api/strategy-schema.js index a151ba2fff..ef910fe7c7 100644 --- a/lib/routes/strategy-schema.js +++ b/lib/routes/admin-api/strategy-schema.js @@ -3,18 +3,16 @@ const joi = require('joi'); const strategySchema = joi.object().keys({ - name: joi.string() - .regex(/^[a-zA-Z0-9\\.\\-]{3,100}$/) - .required(), + name: joi.string().regex(/^[a-zA-Z0-9\\.\\-]{3,100}$/).required(), description: joi.string(), - parameters: joi.array() - .required() - .items(joi.object().keys({ + parameters: joi.array().required().items( + joi.object().keys({ name: joi.string().required(), type: joi.string().required(), description: joi.string().allow(''), required: joi.boolean(), - })), + }) + ), }); module.exports = strategySchema; diff --git a/lib/routes/admin-api/strategy.js b/lib/routes/admin-api/strategy.js new file mode 100644 index 0000000000..9f19125032 --- /dev/null +++ b/lib/routes/admin-api/strategy.js @@ -0,0 +1,131 @@ +'use strict'; + +const { Router } = require('express'); +const joi = require('joi'); + +const eventType = require('../../event-type'); +const logger = require('../../logger'); +const NameExistsError = require('../../error/name-exists-error'); +const extractUser = require('../../extract-user'); +const strategySchema = require('./strategy-schema'); +const version = 1; + +const handleError = (req, res, error) => { + logger.warn('Error creating or updating strategy', error); + switch (error.name) { + case 'NotFoundError': + return res.status(404).end(); + case 'NameExistsError': + return res + .status(403) + .json([ + { + msg: `A strategy named '${req.body + .name}' already exists.`, + }, + ]) + .end(); + case 'ValidationError': + return res.status(400).json(error).end(); + default: + logger.error('Could perfom operation', error); + return res.status(500).end(); + } +}; + +exports.router = function(config) { + const { strategyStore, eventStore } = config.stores; + const router = Router(); + + router.get('/', (req, res) => { + strategyStore.getStrategies().then(strategies => { + res.json({ version, strategies }); + }); + }); + + router.get('/:name', (req, res) => { + strategyStore + .getStrategy(req.params.name) + .then(strategy => res.json(strategy).end()) + .catch(() => + res.status(404).json({ error: 'Could not find strategy' }) + ); + }); + + router.delete('/:name', (req, res) => { + const strategyName = req.params.name; + + strategyStore + .getStrategy(strategyName) + .then(() => + eventStore.store({ + type: eventType.STRATEGY_DELETED, + createdBy: extractUser(req), + data: { + name: strategyName, + }, + }) + ) + .then(() => res.status(200).end()) + .catch(error => handleError(req, res, error)); + }); + + router.post('/', (req, res) => { + const data = req.body; + validateInput(data) + .then(validateStrategyName) + .then(newStrategy => + eventStore.store({ + type: eventType.STRATEGY_CREATED, + createdBy: extractUser(req), + data: newStrategy, + }) + ) + .then(() => res.status(201).end()) + .catch(error => handleError(req, res, error)); + }); + + router.put('/:strategyName', (req, res) => { + const strategyName = req.params.strategyName; + const updatedStrategy = req.body; + + updatedStrategy.name = strategyName; + + strategyStore + .getStrategy(strategyName) + .then(() => validateInput(updatedStrategy)) + .then(() => + eventStore.store({ + type: eventType.STRATEGY_UPDATED, + createdBy: extractUser(req), + data: updatedStrategy, + }) + ) + .then(() => res.status(200).end()) + .catch(error => handleError(req, res, error)); + }); + + function validateStrategyName(data) { + return new Promise((resolve, reject) => { + strategyStore + .getStrategy(data.name) + .then(() => + reject(new NameExistsError('Feature name already exist')) + ) + .catch(() => resolve(data)); + }); + } + + function validateInput(data) { + return new Promise((resolve, reject) => { + joi.validate(data, strategySchema, (err, cleaned) => { + if (err) { + return reject(err); + } + return resolve(cleaned); + }); + }); + } + + return router; +}; diff --git a/test/unit/routes/strategies.test.js b/lib/routes/admin-api/strategy.test.js similarity index 66% rename from test/unit/routes/strategies.test.js rename to lib/routes/admin-api/strategy.test.js index b8132e32de..49375a33bb 100644 --- a/test/unit/routes/strategies.test.js +++ b/lib/routes/admin-api/strategy.test.js @@ -1,14 +1,15 @@ 'use strict'; -const test = require('ava'); -const store = require('./fixtures/store'); +const { test } = require('ava'); +const store = require('./../../../test/fixtures/store'); const supertest = require('supertest'); -const getApp = require('../../../lib/app'); +const getApp = require('../../app'); +const logger = require('../../logger'); const { EventEmitter } = require('events'); const eventBus = new EventEmitter(); -function getSetup () { +function getSetup() { const base = `/random${Math.round(Math.random() * 1000)}`; const stores = store.createStores(); const app = getApp({ @@ -24,89 +25,101 @@ function getSetup () { }; } +test.beforeEach(() => { + logger.setLevel('FATAL'); +}); + test('should add version numbers for /stategies', t => { + t.plan(1); const { request, base } = getSetup(); return request - .get(`${base}/api/strategies`) + .get(`${base}/api/admin/strategies`) .expect('Content-Type', /json/) .expect(200) - .expect((res) => { + .expect(res => { t.true(res.body.version === 1); }); }); test('should require a name when creating a new stratey', t => { + t.plan(1); const { request, base } = getSetup(); return request - .post(`${base}/api/strategies`) + .post(`${base}/api/admin/strategies`) .send({}) .expect(400) - .expect((res) => { + .expect(res => { t.true(res.body.name === 'ValidationError'); }); }); test('should require parameters array when creating a new stratey', t => { + t.plan(1); const { request, base } = getSetup(); return request - .post(`${base}/api/strategies`) + .post(`${base}/api/admin/strategies`) .send({ name: 'TestStrat' }) .expect(400) - .expect((res) => { + .expect(res => { t.true(res.body.name === 'ValidationError'); }); }); -test('should create a new stratey with empty parameters', () => { +test('should create a new stratey with empty parameters', t => { + t.plan(0); const { request, base } = getSetup(); return request - .post(`${base}/api/strategies`) + .post(`${base}/api/admin/strategies`) .send({ name: 'TestStrat', parameters: [] }) .expect(201); }); -test('should not be possible to override name', () => { +test('should not be possible to override name', t => { + t.plan(0); const { request, base, strategyStore } = getSetup(); strategyStore.addStrategy({ name: 'Testing', parameters: [] }); return request - .post(`${base}/api/strategies`) + .post(`${base}/api/admin/strategies`) .send({ name: 'Testing', parameters: [] }) .expect(403); }); -test('should update strategy', () => { +test('should update strategy', t => { + t.plan(0); const name = 'AnotherStrat'; const { request, base, strategyStore } = getSetup(); strategyStore.addStrategy({ name, parameters: [] }); return request - .put(`${base}/api/strategies/${name}`) + .put(`${base}/api/admin/strategies/${name}`) .send({ name, parameters: [], description: 'added' }) .expect(200); }); -test('should not update uknown strategy', () => { +test('should not update uknown strategy', t => { + t.plan(0); const name = 'UnknownStrat'; const { request, base } = getSetup(); return request - .put(`${base}/api/strategies/${name}`) + .put(`${base}/api/admin/strategies/${name}`) .send({ name, parameters: [], description: 'added' }) .expect(404); }); -test('should validate format when updating strategy', () => { +test('should validate format when updating strategy', t => { + t.plan(0); const name = 'AnotherStrat'; const { request, base, strategyStore } = getSetup(); strategyStore.addStrategy({ name, parameters: [] }); return request - .put(`${base}/api/strategies/${name}`) - .send({ }) + .put(`${base}/api/admin/strategies/${name}`) + .send({}) .expect(400); }); diff --git a/lib/routes/api.js b/lib/routes/api.js deleted file mode 100644 index 272018ffcd..0000000000 --- a/lib/routes/api.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -const apiDef = { - version: 1, - links: { - 'feature-toggles': { uri: '/api/features' }, - 'strategies': { uri: '/api/strategies' }, - 'events': { uri: '/api/events' }, - 'client-register': { uri: '/api/client/register' }, - 'client-metrics': { uri: '/api/client/register' }, - 'seen-toggles': { uri: '/api/client/seen-toggles' }, - }, -}; - -module.exports = (app) => { - app.get('/', (req, res) => { - res.json(apiDef); - }); -}; diff --git a/lib/routes/backstage.js b/lib/routes/backstage.js index b73b5f8e9e..4212cf2d7c 100644 --- a/lib/routes/backstage.js +++ b/lib/routes/backstage.js @@ -1,12 +1,17 @@ 'use strict'; +const { Router } = require('express'); const prometheusRegister = require('prom-client/lib/register'); -module.exports = function (app, config) { +exports.router = config => { + const router = Router(); + if (config.serverMetrics) { - app.get('/internal-backstage/prometheus', (req, res) => { + router.get('/prometheus', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(prometheusRegister.metrics()); }); } + + return router; }; diff --git a/test/unit/routes/backstage.test.js b/lib/routes/backstage.test.js similarity index 70% rename from test/unit/routes/backstage.test.js rename to lib/routes/backstage.test.js index ea04d2c61b..f2e5a9977b 100644 --- a/test/unit/routes/backstage.test.js +++ b/lib/routes/backstage.test.js @@ -1,19 +1,20 @@ 'use strict'; -const test = require('ava'); -const store = require('./fixtures/store'); +const { test } = require('ava'); +const store = require('./../../test/fixtures/store'); const supertest = require('supertest'); -const logger = require('../../../lib/logger'); -const getApp = require('../../../lib/app'); +const logger = require('../logger'); +const getApp = require('../app'); const { EventEmitter } = require('events'); const eventBus = new EventEmitter(); -test.beforeEach(() => { +test.beforeEach(() => { logger.setLevel('FATAL'); }); test('should use enable prometheus', t => { + t.plan(0); const stores = store.createStores(); const app = getApp({ baseUriPath: '', @@ -27,5 +28,5 @@ test('should use enable prometheus', t => { return request .get('/internal-backstage/prometheus') .expect('Content-Type', /text/) - .expect(200) + .expect(200); }); diff --git a/lib/routes/client-api/feature.js b/lib/routes/client-api/feature.js new file mode 100644 index 0000000000..966c3bb6dc --- /dev/null +++ b/lib/routes/client-api/feature.js @@ -0,0 +1,18 @@ +'use strict'; + +const { Router } = require('express'); + +const version = 1; + +exports.router = config => { + const router = Router(); + const { featureToggleStore } = config.stores; + + router.get('/', (req, res) => { + featureToggleStore + .getFeatures() + .then(features => res.json({ version, features })); + }); + + return router; +}; diff --git a/test/unit/routes/api.test.js b/lib/routes/client-api/feature.test.js similarity index 63% rename from test/unit/routes/api.test.js rename to lib/routes/client-api/feature.test.js index cb1dbe0994..46a2ecdf6d 100644 --- a/test/unit/routes/api.test.js +++ b/lib/routes/client-api/feature.test.js @@ -1,10 +1,10 @@ 'use strict'; -const test = require('ava'); -const store = require('./fixtures/store'); +const { test } = require('ava'); +const store = require('./../../../test/fixtures/store'); const supertest = require('supertest'); -const logger = require('../../../lib/logger'); -const getApp = require('../../../lib/app'); +const logger = require('../../logger'); +const getApp = require('../../app'); const { EventEmitter } = require('events'); const eventBus = new EventEmitter(); @@ -13,7 +13,7 @@ test.beforeEach(() => { logger.setLevel('FATAL'); }); -function getSetup () { +function getSetup() { const base = `/random${Math.round(Math.random() * 1000)}`; const stores = store.createStores(); const app = getApp({ @@ -29,13 +29,14 @@ function getSetup () { }; } -test('should get api defintion', t => { +test('should get empty getFeatures via client', t => { + t.plan(1); const { request, base } = getSetup(); return request - .get(`${base}/api/`) + .get(`${base}/api/client/features`) .expect('Content-Type', /json/) .expect(200) - .expect((res) => { - t.true(res.body.links['feature-toggles'].uri === '/api/features'); + .expect(res => { + t.true(res.body.features.length === 0); }); }); diff --git a/lib/routes/client-api/index.js b/lib/routes/client-api/index.js new file mode 100644 index 0000000000..e7c61798d7 --- /dev/null +++ b/lib/routes/client-api/index.js @@ -0,0 +1,30 @@ +'use strict'; + +const { Router } = require('express'); +const features = require('./feature.js'); +const metrics = require('./metrics.js'); +const register = require('./register.js'); + +const apiDef = { + version: 2, + links: { + 'feature-toggles': { uri: '/api/client/features' }, + register: { uri: '/api/client/register' }, + metrics: { uri: '/api/client/metrics' }, + }, +}; + +exports.apiDef = apiDef; + +exports.router = config => { + const router = Router(); + router.get('/', (req, res) => { + res.json(apiDef); + }); + + router.use('/features', features.router(config)); + router.use('/metrics', metrics.router(config)); + router.use('/register', register.router(config)); + + return router; +}; diff --git a/lib/routes/metrics-schema.js b/lib/routes/client-api/metrics-schema.js similarity index 79% rename from lib/routes/metrics-schema.js rename to lib/routes/client-api/metrics-schema.js index 249c79a448..548b70bc7b 100644 --- a/lib/routes/metrics-schema.js +++ b/lib/routes/client-api/metrics-schema.js @@ -5,8 +5,7 @@ const joi = require('joi'); const clientMetricsSchema = joi.object().keys({ appName: joi.string().required(), instanceId: joi.string().required(), - bucket: joi.object().required() - .keys({ + bucket: joi.object().required().keys({ start: joi.date().required(), stop: joi.date().required(), toggles: joi.object(), @@ -16,9 +15,7 @@ const clientMetricsSchema = joi.object().keys({ const clientRegisterSchema = joi.object().keys({ appName: joi.string().required(), instanceId: joi.string().required(), - strategies: joi.array() - .required() - .items(joi.string(), joi.any().strip()), + strategies: joi.array().required().items(joi.string(), joi.any().strip()), started: joi.date().required(), interval: joi.number().required(), }); diff --git a/lib/routes/client-api/metrics.js b/lib/routes/client-api/metrics.js new file mode 100644 index 0000000000..50ead3d286 --- /dev/null +++ b/lib/routes/client-api/metrics.js @@ -0,0 +1,39 @@ +'use strict'; + +const { Router } = require('express'); +const joi = require('joi'); +const logger = require('../../logger'); + +const { clientMetricsSchema } = require('./metrics-schema'); + +exports.router = config => { + const { clientMetricsStore, clientInstanceStore } = config.stores; + const router = Router(); + + router.post('/', (req, res) => { + const data = req.body; + const clientIp = req.ip; + + joi.validate(data, clientMetricsSchema, (err, cleaned) => { + if (err) { + logger.warn('Invalid metrics posted', err); + return res.status(400).json(err); + } + + clientMetricsStore + .insert(cleaned) + .then(() => + clientInstanceStore.insert({ + appName: cleaned.appName, + instanceId: cleaned.instanceId, + clientIp, + }) + ) + .catch(err => logger.error('failed to store metrics', err)); + + res.status(202).end(); + }); + }); + + return router; +}; diff --git a/lib/routes/client-api/metrics.test.js b/lib/routes/client-api/metrics.test.js new file mode 100644 index 0000000000..78578c626c --- /dev/null +++ b/lib/routes/client-api/metrics.test.js @@ -0,0 +1,54 @@ +'use strict'; + +const { test } = require('ava'); +const store = require('./../../../test/fixtures/store'); +const supertest = require('supertest'); +const logger = require('../../logger'); +const getApp = require('../../app'); + +const { EventEmitter } = require('events'); +const eventBus = new EventEmitter(); + +test.beforeEach(() => { + logger.setLevel('FATAL'); +}); + +function getSetup() { + const stores = store.createStores(); + const app = getApp({ + baseUriPath: '', + stores, + eventBus, + }); + + return { + request: supertest(app), + stores, + }; +} + +test('should validate client metrics', t => { + t.plan(0); + const { request } = getSetup(); + return request + .post('/api/client/metrics') + .send({ random: 'blush' }) + .expect(400); +}); + +test('should accept client metrics', t => { + t.plan(0); + const { request } = getSetup(); + return request + .post('/api/client/metrics') + .send({ + appName: 'demo', + instanceId: '1', + bucket: { + start: Date.now(), + stop: Date.now(), + toggles: {}, + }, + }) + .expect(202); +}); diff --git a/lib/routes/client-api/register.js b/lib/routes/client-api/register.js new file mode 100644 index 0000000000..0b4536acdb --- /dev/null +++ b/lib/routes/client-api/register.js @@ -0,0 +1,38 @@ +'use strict'; + +const { Router } = require('express'); +const joi = require('joi'); +const logger = require('../../logger'); + +const { clientRegisterSchema } = require('./metrics-schema'); + +exports.router = config => { + const { clientInstanceStore, clientApplicationsStore } = config.stores; + const router = Router(); + + router.post('/', (req, res) => { + const data = req.body; + + joi.validate(data, clientRegisterSchema, (err, clientRegistration) => { + if (err) { + logger.warn('Invalid client data posted', err); + return res.status(400).json(err); + } + + clientRegistration.clientIp = req.ip; + + clientApplicationsStore + .upsert(clientRegistration) + .then(() => clientInstanceStore.insert(clientRegistration)) + .then(() => + logger.info(`New client registered with + appName=${clientRegistration.appName} and instanceId=${clientRegistration.instanceId}`) + ) + .catch(err => logger.error('failed to register client', err)); + + res.status(202).end(); + }); + }); + + return router; +}; diff --git a/lib/routes/client-api/register.test.js b/lib/routes/client-api/register.test.js new file mode 100644 index 0000000000..5ba4f91e80 --- /dev/null +++ b/lib/routes/client-api/register.test.js @@ -0,0 +1,64 @@ +'use strict'; + +const { test } = require('ava'); +const store = require('./../../../test/fixtures/store'); +const supertest = require('supertest'); +const logger = require('../../logger'); +const getApp = require('../../app'); + +const { EventEmitter } = require('events'); +const eventBus = new EventEmitter(); + +test.beforeEach(() => { + logger.setLevel('FATAL'); +}); + +function getSetup() { + const stores = store.createStores(); + const app = getApp({ + baseUriPath: '', + stores, + eventBus, + }); + + return { + request: supertest(app), + stores, + }; +} + +test('should register client', t => { + t.plan(0); + const { request } = getSetup(); + return request + .post('/api/client/register') + .send({ + appName: 'demo', + instanceId: 'test', + strategies: ['default'], + started: Date.now(), + interval: 10, + }) + .expect(202); +}); + +test('should require appName field', t => { + t.plan(0); + const { request } = getSetup(); + return request.post('/api/client/register').expect(400); +}); + +test('should require strategies field', t => { + t.plan(0); + const { request } = getSetup(); + return request + .post('/api/client/register') + .send({ + appName: 'demo', + instanceId: 'test', + // strategies: ['default'], + started: Date.now(), + interval: 10, + }) + .expect(400); +}); diff --git a/lib/routes/feature-archive.js b/lib/routes/feature-archive.js deleted file mode 100644 index 89ef5a8d07..0000000000 --- a/lib/routes/feature-archive.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict'; - -const logger = require('../logger'); -const { FEATURE_REVIVED } = require('../event-type'); -const ValidationError = require('../error/validation-error'); -const validateRequest = require('../error/validate-request'); - -const handleErrors = (req, res, error) => { - switch (error.constructor) { - case ValidationError: - return res - .status(400) - .json(req.validationErrors()) - .end(); - default: - logger.error('Server failed executing request', error); - return res - .status(500) - .end(); - } -}; - -module.exports = function (app, config) { - const { featureToggleStore, eventStore } = config.stores; - - app.get('/archive/features', (req, res) => { - featureToggleStore.getArchivedFeatures().then(archivedFeatures => { - res.json({ features: archivedFeatures }); - }); - }); - - app.post('/archive/revive/:name', (req, res) => { - req.checkParams('name', 'Name is required').notEmpty(); - - validateRequest(req) - .then(() => eventStore.store({ - type: FEATURE_REVIVED, - createdBy: req.connection.remoteAddress, - data: { name: req.params.name }, - })) - .then(() => res.status(200).end()) - .catch(error => handleErrors(req, res, error)); - }); -}; diff --git a/lib/routes/feature.js b/lib/routes/feature.js deleted file mode 100644 index 7302b192ad..0000000000 --- a/lib/routes/feature.js +++ /dev/null @@ -1,178 +0,0 @@ -'use strict'; - -const joi = require('joi'); -const logger = require('../logger'); -const { FEATURE_CREATED, FEATURE_UPDATED, FEATURE_ARCHIVED } = require('../event-type'); -const NameExistsError = require('../error/name-exists-error'); -const NotFoundError = require('../error/notfound-error'); -const ValidationError = require('../error/validation-error.js'); -const validateRequest = require('../error/validate-request'); -const extractUser = require('../extract-user'); - -const legacyFeatureMapper = require('../data-helper/legacy-feature-mapper'); -const version = 1; - -const handleErrors = (req, res, error) => { - logger.warn('Error creating or updating feature', error); - switch (error.constructor) { - case NotFoundError: - return res - .status(404) - .end(); - case NameExistsError: - return res - .status(403) - .json([{ msg: 'A feature with this name already exists. Try re-activating it from the archive.' }]) - .end(); - case ValidationError: - return res - .status(400) - .json(req.validationErrors()) - .end(); - default: - logger.error('Server failed executing request', error); - return res - .status(500) - .end(); - } -}; - -module.exports = function (app, config) { - const { featureToggleStore, eventStore } = config.stores; - - app.get('/features', (req, res) => { - featureToggleStore.getFeatures() - .then((features) => features.map(legacyFeatureMapper.addOldFields)) - .then(features => res.json({ version, features })); - }); - - app.get('/features/:featureName', (req, res) => { - featureToggleStore.getFeature(req.params.featureName) - .then(legacyFeatureMapper.addOldFields) - .then(feature => res.json(feature).end()) - .catch(() => res.status(404).json({ error: 'Could not find feature' })); - }); - - app.post('/features-validate', (req, res) => { - req.checkBody('name', 'Name is required').notEmpty(); - req.checkBody('name', 'Name must match format ^[0-9a-zA-Z\\.\\-]+$').matches(/^[0-9a-zA-Z\\.\\-]+$/i); - - validateRequest(req) - .then(validateFormat) - .then(validateUniqueName) - .then(() => res.status(201).end()) - .catch(error => handleErrors(req, res, error)); - }); - - app.post('/features', (req, res) => { - req.checkBody('name', 'Name is required').notEmpty(); - req.checkBody('name', 'Name must match format ^[0-9a-zA-Z\\.\\-]+$').matches(/^[0-9a-zA-Z\\.\\-]+$/i); - const userName = extractUser(req); - - validateRequest(req) - .then(validateFormat) - .then(validateUniqueName) - .then((_req) => legacyFeatureMapper.toNewFormat(_req.body)) - .then(validateStrategy) - .then((featureToggle) => eventStore.store({ - type: FEATURE_CREATED, - createdBy: userName, - data: featureToggle, - })) - .then(() => res.status(201).end()) - .catch(error => handleErrors(req, res, error)); - }); - - app.put('/features/:featureName', (req, res) => { - const featureName = req.params.featureName; - const userName = extractUser(req); - const updatedFeature = legacyFeatureMapper.toNewFormat(req.body); - - updatedFeature.name = featureName; - - featureToggleStore.getFeature(featureName) - .then(() => validateStrategy(updatedFeature)) - .then(() => eventStore.store({ - type: FEATURE_UPDATED, - createdBy: userName, - data: updatedFeature, - })) - .then(() => res.status(200).end()) - .catch(error => handleErrors(req, res, error)); - }); - - app.post('/features/:featureName/toggle', (req, res) => { - const featureName = req.params.featureName; - const userName = extractUser(req); - - featureToggleStore.getFeature(featureName) - .then((feature) => { - feature.enabled = !feature.enabled; - return eventStore.store({ - type: FEATURE_UPDATED, - createdBy: userName, - data: feature, - }); - }) - .then(() => res.status(200).end()) - .catch(error => handleErrors(req, res, error)); - }); - - app.delete('/features/:featureName', (req, res) => { - const featureName = req.params.featureName; - const userName = extractUser(req); - - featureToggleStore.getFeature(featureName) - .then(() => eventStore.store({ - type: FEATURE_ARCHIVED, - createdBy: userName, - data: { - name: featureName, - }, - })) - .then(() => res.status(200).end()) - .catch(error => handleErrors(req, res, error)); - }); - - function validateUniqueName (req) { - return new Promise((resolve, reject) => { - featureToggleStore.getFeature(req.body.name) - .then(() => reject(new NameExistsError('Feature name already exist'))) - .catch(() => resolve(req)); - }); - } - - function validateFormat (req) { - if (req.body.strategy && req.body.strategies) { - return Promise.reject(new ValidationError('Cannot use both "strategy" and "strategies".')); - } - - return Promise.resolve(req); - } - - - const strategiesSchema = joi.object().keys({ - name: joi.string() - .regex(/^[a-zA-Z0-9\\.\\-]{3,100}$/) - .required(), - parameters: joi.object(), - }); - - function validateStrategy (featureToggle) { - return new Promise((resolve, reject) => { - if (!featureToggle.strategies || featureToggle.strategies.length === 0) { - return reject(new ValidationError('You must define at least one strategy')); - } - - featureToggle.strategies = featureToggle.strategies.map((strategyConfig) => { - const result = joi.validate(strategyConfig, strategiesSchema); - if (result.error) { - throw result.error; - } - return result.value; - }); - - return resolve(featureToggle); - }); - } -}; diff --git a/lib/routes/health-check.js b/lib/routes/health-check.js index 7598614355..578c1eb124 100644 --- a/lib/routes/health-check.js +++ b/lib/routes/health-check.js @@ -1,15 +1,24 @@ 'use strict'; const logger = require('../logger'); +const { Router } = require('express'); -module.exports = function (app, config) { - app.get('/health', (req, res) => { - config.stores.db.select(1) +exports.router = function(config) { + const router = Router(); + + router.get('/', (req, res) => { + config.stores.db + .select(1) .from('features') .then(() => res.json({ health: 'GOOD' })) .catch(err => { - logger.error('Could not select from features, error was: ', err); + logger.error( + 'Could not select from features, error was: ', + err + ); res.status(500).json({ health: 'BAD' }); }); }); + + return router; }; diff --git a/test/unit/routes/health-check.test.js b/lib/routes/health-check.test.js similarity index 50% rename from test/unit/routes/health-check.test.js rename to lib/routes/health-check.test.js index 29063a446d..322e73c665 100644 --- a/test/unit/routes/health-check.test.js +++ b/lib/routes/health-check.test.js @@ -1,20 +1,19 @@ 'use strict'; -const test = require('ava'); -const store = require('./fixtures/store'); +const { test } = require('ava'); +const store = require('./../../test/fixtures/store'); const supertest = require('supertest'); -const logger = require('../../../lib/logger'); -const getApp = require('../../../lib/app'); +const logger = require('../logger'); +const getApp = require('../app'); const { EventEmitter } = require('events'); const eventBus = new EventEmitter(); -test.beforeEach(() => { +test.beforeEach(() => { logger.setLevel('FATAL'); }); - -function getSetup () { +function getSetup() { const stores = store.createStores(); const db = stores.db; const app = getApp({ @@ -30,34 +29,28 @@ function getSetup () { } test('should give 500 when db is failing', t => { + t.plan(2); const { request, db } = getSetup(); db.select = () => ({ from: () => Promise.reject(new Error('db error')), }); - return request - .get('/health') - .expect(500) - .expect((res) => { - t.true(res.status === 500); - t.true(res.body.health === 'BAD'); - }); + return request.get('/health').expect(500).expect(res => { + t.true(res.status === 500); + t.true(res.body.health === 'BAD'); + }); }); -test('should give 200 when db is not failing', () => { +test('should give 200 when db is not failing', t => { + t.plan(0); const { request } = getSetup(); - return request - .get('/health') - .expect(200); + return request.get('/health').expect(200); }); test('should give health=GOOD when db is not failing', t => { + t.plan(2); const { request } = getSetup(); - return request - .get('/health') - .expect(200) - .expect((res) => { - t.true(res.status === 200); - t.true(res.body.health === 'GOOD'); - }); + return request.get('/health').expect(200).expect(res => { + t.true(res.status === 200); + t.true(res.body.health === 'GOOD'); + }); }); - diff --git a/lib/routes/index.js b/lib/routes/index.js index 30208efe2c..0d67bc029e 100644 --- a/lib/routes/index.js +++ b/lib/routes/index.js @@ -1,18 +1,44 @@ 'use strict'; +const { Router } = require('express'); -exports.createAPI = function (router, config) { - require('./api')(router, config); - require('./event')(router, config); - require('./feature')(router, config); - require('./feature-archive')(router, config); - require('./strategy')(router, config); - require('./health-check')(router, config); - require('./metrics')(router, config); -}; +const adminApi = require('./admin-api'); +const clientApi = require('./client-api'); +const clientFeatures = require('./client-api/feature.js'); -exports.createLegacy = function (router, config) { - require('./feature')(router, config); - require('./health-check')(router, config); - require('./backstage')(router, config); +const health = require('./health-check'); +const backstage = require('./backstage.js'); + +exports.router = function(config) { + const router = Router(); + + router.use('/health', health.router(config)); + router.use('/internal-backstage', backstage.router(config)); + + router.get('/api', (req, res) => { + res.json({ + version: 2, + links: { + admin: { + uri: '/api/admin', + links: adminApi.apiDef.links, + }, + client: { + uri: '/api/client', + links: clientApi.apiDef.links, + }, + }, + }); + }); + + router.use('/api/admin', adminApi.router(config)); + router.use('/api/client', clientApi.router(config)); + + // legacy support + // $root/features + // $root/client/register + // $root/client/metrics + router.use('/api/features', clientFeatures.router(config)); + + return router; }; diff --git a/lib/routes/index.test.js b/lib/routes/index.test.js new file mode 100644 index 0000000000..d819275903 --- /dev/null +++ b/lib/routes/index.test.js @@ -0,0 +1,91 @@ +'use strict'; + +const { test } = require('ava'); +const store = require('./../../test/fixtures/store'); +const supertest = require('supertest'); +const logger = require('../logger'); +const getApp = require('../app'); + +const { EventEmitter } = require('events'); +const eventBus = new EventEmitter(); + +test.beforeEach(() => { + logger.setLevel('FATAL'); +}); + +function getSetup() { + const base = `/random${Math.round(Math.random() * 1000)}`; + const stores = store.createStores(); + const app = getApp({ + baseUriPath: base, + stores, + eventBus, + }); + + return { + base, + featureToggleStore: stores.featureToggleStore, + request: supertest(app), + }; +} + +test('api defintion', t => { + t.plan(5); + const { request, base } = getSetup(); + return request + .get(`${base}/api/`) + .expect('Content-Type', /json/) + .expect(200) + .expect(res => { + t.truthy(res.body); + const { admin, client } = res.body.links; + t.true(admin.uri === '/api/admin'); + t.true(client.uri === '/api/client'); + t.true( + admin.links['feature-toggles'].uri === '/api/admin/features' + ); + t.true(client.links.metrics.uri === '/api/client/metrics'); + }); +}); + +test('admin api defintion', t => { + t.plan(2); + const { request, base } = getSetup(); + return request + .get(`${base}/api/admin`) + .expect('Content-Type', /json/) + .expect(200) + .expect(res => { + t.truthy(res.body); + t.true( + res.body.links['feature-toggles'].uri === '/api/admin/features' + ); + }); +}); + +test('client api defintion', t => { + t.plan(2); + const { request, base } = getSetup(); + return request + .get(`${base}/api/client`) + .expect('Content-Type', /json/) + .expect(200) + .expect(res => { + t.truthy(res.body); + t.true(res.body.links.metrics.uri === '/api/client/metrics'); + }); +}); + +test('client legacy features uri', t => { + t.plan(3); + const { request, base } = getSetup(); + return request + .get(`${base}/api/features`) + .expect('Content-Type', /json/) + .expect(200) + .expect(res => { + t.truthy(res.body); + t.true(res.body.version === 1); + t.deepEqual(res.body.features, []); + }); +}); diff --git a/lib/routes/metrics.js b/lib/routes/metrics.js deleted file mode 100644 index 6f2b232a22..0000000000 --- a/lib/routes/metrics.js +++ /dev/null @@ -1,170 +0,0 @@ -'use strict'; - -const logger = require('../logger'); -const ClientMetrics = require('../client-metrics'); -const joi = require('joi'); -const { clientMetricsSchema, clientRegisterSchema } = require('./metrics-schema'); -const { catchLogAndSendErrorResponse } = require('./route-utils'); - -module.exports = function (app, config) { - const { - clientMetricsStore, - clientInstanceStore, - clientApplicationsStore, - strategyStore, - featureToggleStore, - } = config.stores; - - const metrics = new ClientMetrics(clientMetricsStore); - - app.get('/client/seen-toggles', (req, res) => { - const seenAppToggles = metrics.getAppsWithToggles(); - res.json(seenAppToggles); - }); - - app.get('/client/seen-apps', (req, res) => { - const seenApps = metrics.getSeenAppsPerToggle(); - clientApplicationsStore.getApplications() - .then(toLookup) - .then(metaData => { - Object.keys(seenApps).forEach(key => { - seenApps[key] = seenApps[key].map(entry => { - if (metaData[entry.appName]) { - return Object.assign({}, entry, metaData[entry.appName]); - } - return entry; - }); - }); - res.json(seenApps); - }); - }); - - app.get('/client/metrics/feature-toggles', (req, res) => { - res.json(metrics.getTogglesMetrics()); - }); - - app.get('/client/metrics/feature-toggles/:name', (req, res) => { - const name = req.params.name; - const data = metrics.getTogglesMetrics(); - const lastHour = data.lastHour[name] || {}; - const lastMinute = data.lastMinute[name] || {}; - res.json({ - lastHour, - lastMinute, - }); - }); - - app.post('/client/metrics', (req, res) => { - const data = req.body; - const clientIp = req.ip; - - joi.validate(data, clientMetricsSchema, (err, cleaned) => { - if (err) { - logger.warn('Invalid metrics posted', err); - return res.status(400).json(err); - } - - clientMetricsStore - .insert(cleaned) - .then(() => clientInstanceStore.insert({ - appName: cleaned.appName, - instanceId: cleaned.instanceId, - clientIp, - })) - .catch(err => logger.error('failed to store metrics', err)); - - res.status(202).end(); - }); - }); - - app.post('/client/register', (req, res) => { - const data = req.body; - - joi.validate(data, clientRegisterSchema, (err, clientRegistration) => { - if (err) { - logger.warn('Invalid client data posted', err); - return res.status(400).json(err); - } - - clientRegistration.clientIp = req.ip; - - clientApplicationsStore - .upsert(clientRegistration) - .then(() => clientInstanceStore.insert(clientRegistration)) - .then(() => logger.info(`New client registered with - appName=${clientRegistration.appName} and instanceId=${clientRegistration.instanceId}`)) - .catch(err => logger.error('failed to register client', err)); - - res.status(202).end(); - }); - }); - - app.post('/client/applications/:appName', (req, res) => { - const input = Object.assign({}, req.body, { - appName: req.params.appName, - }); - clientApplicationsStore - .upsert(input) - .then(() => res.status(202).end()) - .catch((e) => { - logger.error(e); - res.status(500).end(); - }); - }); - - function toLookup (metaData) { - return metaData.reduce((result, entry) => { - result[entry.appName] = entry; - return result; - }, {}); - } - - app.get('/client/applications/', (req, res) => { - clientApplicationsStore - .getApplications(req.query) - .then(applications => res.json({ applications })) - .catch(err => catchLogAndSendErrorResponse(err, res)); - }); - - app.get('/client/applications/:appName', (req, res) => { - const appName = req.params.appName; - const seenToggles = metrics.getSeenTogglesByAppName(appName); - - Promise.all([ - clientApplicationsStore.getApplication(appName), - clientInstanceStore.getByAppName(appName), - strategyStore.getStrategies(), - featureToggleStore.getFeatures(), - ]) - .then(([application, instances, strategies, features]) => { - const appDetails = { - appName: application.appName, - createdAt: application.createdAt, - description: application.description, - url: application.url, - color: application.color, - icon: application.icon, - strategies: application.strategies.map(name => { - const found = strategies.find((feature) => feature.name === name); - if (found) { - return found; - } - return { name, notFound: true }; - }), - instances, - seenToggles: seenToggles.map(name => { - const found = features.find((feature) => feature.name === name); - if (found) { - return found; - } - return { name, notFound: true }; - }), - links: { - self: `/api/client/applications/${application.appName}`, - }, - }; - res.json(appDetails); - }) - .catch(err => catchLogAndSendErrorResponse(err, res)); - }); -}; diff --git a/lib/routes/strategy.js b/lib/routes/strategy.js deleted file mode 100644 index d18313f9e0..0000000000 --- a/lib/routes/strategy.js +++ /dev/null @@ -1,114 +0,0 @@ -'use strict'; - -const joi = require('joi'); -const eventType = require('../event-type'); -const logger = require('../logger'); -const NameExistsError = require('../error/name-exists-error'); -const extractUser = require('../extract-user'); -const strategySchema = require('./strategy-schema'); -const version = 1; - -const handleError = (req, res, error) => { - logger.warn('Error creating or updating strategy', error); - switch (error.name) { - case 'NotFoundError': - return res - .status(404) - .end(); - case 'NameExistsError': - return res - .status(403) - .json([{ msg: `A strategy named '${req.body.name}' already exists.` }]) - .end(); - case 'ValidationError': - return res - .status(400) - .json(error) - .end(); - default: - logger.error('Could perfom operation', error); - return res - .status(500) - .end(); - } -}; - -module.exports = function (app, config) { - const { strategyStore, eventStore } = config.stores; - - app.get('/strategies', (req, res) => { - strategyStore.getStrategies().then(strategies => { - res.json({ version, strategies }); - }); - }); - - app.get('/strategies/:name', (req, res) => { - strategyStore.getStrategy(req.params.name) - .then(strategy => res.json(strategy).end()) - .catch(() => res.status(404).json({ error: 'Could not find strategy' })); - }); - - app.delete('/strategies/:name', (req, res) => { - const strategyName = req.params.name; - - strategyStore.getStrategy(strategyName) - .then(() => eventStore.store({ - type: eventType.STRATEGY_DELETED, - createdBy: extractUser(req), - data: { - name: strategyName, - }, - })) - .then(() => res.status(200).end()) - .catch(error => handleError(req, res, error)); - }); - - app.post('/strategies', (req, res) => { - const data = req.body; - validateInput(data) - .then(validateStrategyName) - .then((newStrategy) => eventStore.store({ - type: eventType.STRATEGY_CREATED, - createdBy: extractUser(req), - data: newStrategy, - })) - .then(() => res.status(201).end()) - .catch(error => handleError(req, res, error)); - }); - - app.put('/strategies/:strategyName', (req, res) => { - const strategyName = req.params.strategyName; - const updatedStrategy = req.body; - - updatedStrategy.name = strategyName; - - strategyStore.getStrategy(strategyName) - .then(() => validateInput(updatedStrategy)) - .then(() => eventStore.store({ - type: eventType.STRATEGY_UPDATED, - createdBy: extractUser(req), - data: updatedStrategy, - })) - .then(() => res.status(200).end()) - .catch(error => handleError(req, res, error)); - }); - - function validateStrategyName (data) { - return new Promise((resolve, reject) => { - strategyStore.getStrategy(data.name) - .then(() => reject(new NameExistsError('Feature name already exist'))) - .catch(() => resolve(data)); - }); - } - - function validateInput (data) { - return new Promise((resolve, reject) => { - joi.validate(data, strategySchema, (err, cleaned) => { - if (err) { - return reject(err); - } - return resolve(cleaned); - }); - }); - } -}; diff --git a/lib/server-impl.test.js b/lib/server-impl.test.js index eed8532cd5..ae8bf042b2 100644 --- a/lib/server-impl.test.js +++ b/lib/server-impl.test.js @@ -2,11 +2,11 @@ const { test } = require('ava'); const proxyquire = require('proxyquire'); +const express = require('express'); const getApp = proxyquire('./app', { './routes': { - createAPI: () => {}, - createLegacy: () => {}, + router: () => express.Router(), }, }); @@ -35,6 +35,7 @@ const serverImpl = proxyquire('./server-impl', { test('should call preHook', async t => { let called = 0; await serverImpl.start({ + port: 0, preHook: () => { called++; }, @@ -44,8 +45,11 @@ test('should call preHook', async t => { test('should call preRouterHook', async t => { let called = 0; - await serverImpl.start({ preRouterHook: () => { - called++; - } }); + await serverImpl.start({ + port: 0, + preRouterHook: () => { + called++; + }, + }); t.true(called === 1); }); diff --git a/package.json b/package.json index c79176b274..13782f4737 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "db-migrate": "db-migrate", "lint": "eslint lib", "pretest": "npm run lint", - "test": "PORT=4243 ava test lib/*.test.js lib/**/*.test.js", + "test": "PORT=4243 ava lib/*.test.js lib/**/*.test.js lib/**/**/*.test.js lib/**/**/**/*.test.js test", "test:docker": "./scripts/docker-postgres.sh", "test:watch": "npm run test -- --watch", "test:pg-virtualenv": "pg_virtualenv npm run test:pg-virtualenv-chai", @@ -81,11 +81,13 @@ }, "devDependencies": { "@types/node": "^7.0.5", - "ava": "^0.18.2", + "ava": "^0.19.1", "coveralls": "^2.11.16", - "eslint": "^3.16.1", - "eslint-config-finn": "^1.0.0-beta.1", + "eslint": "^4.0.0", + "eslint-config-finn": "^1.0.2", + "eslint-config-finn-prettier": "^2.0.0", "nyc": "^10.1.2", + "prettier": "^1.4.4", "proxyquire": "^1.7.11", "sinon": "^1.17.7", "superagent": "^3.5.0", diff --git a/test/e2e/event-api.test.js b/test/e2e/api/admin/event.e2e.test.js similarity index 52% rename from test/e2e/event-api.test.js rename to test/e2e/api/admin/event.e2e.test.js index 46cbf5ec29..18a5ed8c84 100644 --- a/test/e2e/event-api.test.js +++ b/test/e2e/api/admin/event.e2e.test.js @@ -1,26 +1,28 @@ 'use strict'; -const test = require('ava'); -const { setupApp } = require('./helpers/test-helper'); -const logger = require('../../lib/logger'); +const { test } = require('ava'); +const { setupApp } = require('./../../helpers/test-helper'); +const logger = require('../../../../lib/logger'); -test.beforeEach(() => { +test.beforeEach(() => { logger.setLevel('FATAL'); }); -test.serial('returns events', async (t) => { +test.serial('returns events', async t => { + t.plan(0); const { request, destroy } = await setupApp('event_api_serial'); return request - .get('/api/events') + .get('/api/admin/events') .expect('Content-Type', /json/) .expect(200) .then(destroy); }); -test.serial('returns events given a name', async (t) => { +test.serial('returns events given a name', async t => { + t.plan(0); const { request, destroy } = await setupApp('event_api_serial'); return request - .get('/api/events/myname') + .get('/api/admin/events/myname') .expect('Content-Type', /json/) .expect(200) .then(destroy); diff --git a/test/e2e/feature-archive-api.test.js b/test/e2e/api/admin/feature-archive.e2e.test.js similarity index 51% rename from test/e2e/feature-archive-api.test.js rename to test/e2e/api/admin/feature-archive.e2e.test.js index 31a4d734e6..c07f47388f 100644 --- a/test/e2e/feature-archive-api.test.js +++ b/test/e2e/api/admin/feature-archive.e2e.test.js @@ -1,38 +1,38 @@ 'use strict'; -const test = require('ava'); -const { setupApp } = require('./helpers/test-helper'); -const logger = require('../../lib/logger'); +const { test } = require('ava'); +const { setupApp } = require('./../../helpers/test-helper'); +const logger = require('../../../../lib/logger'); -test.beforeEach(() => { +test.beforeEach(() => { logger.setLevel('FATAL'); }); test.serial('returns three archived toggles', async t => { + t.plan(1); const { request, destroy } = await setupApp('archive_serial'); return request - .get('/api/archive/features') + .get('/api/admin/archive/features') .expect('Content-Type', /json/) .expect(200) - .expect((res) => { + .expect(res => { t.true(res.body.features.length === 3); }) .then(destroy); }); test.serial('revives a feature by name', async t => { - const { request, destroy } = await setupApp('archive_serial'); + t.plan(0); + const { request, destroy } = await setupApp('archive_serial'); return request - .post('/api/archive/revive/featureArchivedX') + .post('/api/admin/archive/revive/featureArchivedX') .set('Content-Type', 'application/json') .expect(200) .then(destroy); }); test.serial('must set name when reviving toggle', async t => { - const { request, destroy } = await setupApp('archive_serial'); - return request - .post('/api/archive/revive/') - .expect(404) - .then(destroy); + t.plan(0); + const { request, destroy } = await setupApp('archive_serial'); + return request.post('/api/admin/archive/revive/').expect(404).then(destroy); }); diff --git a/test/e2e/api/admin/feature.e2e.test.js b/test/e2e/api/admin/feature.e2e.test.js new file mode 100644 index 0000000000..998d9e4ed5 --- /dev/null +++ b/test/e2e/api/admin/feature.e2e.test.js @@ -0,0 +1,200 @@ +'use strict'; + +const { test } = require('ava'); +const { setupApp } = require('./../../helpers/test-helper'); +const logger = require('../../../../lib/logger'); + +test.beforeEach(() => { + logger.setLevel('FATAL'); +}); + +test.serial('returns three feature toggles', async t => { + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .get('/api/admin/features') + .expect('Content-Type', /json/) + .expect(200) + .expect(res => { + t.true(res.body.features.length === 3); + }) + .then(destroy); +}); + +test.serial('gets a feature by name', async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .get('/api/admin/features/featureX') + .expect('Content-Type', /json/) + .expect(200) + .then(destroy); +}); + +test.serial('cant get feature that dose not exist', async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .get('/api/admin/features/myfeature') + .expect('Content-Type', /json/) + .expect(404) + .then(destroy); +}); + +test.serial('creates new feature toggle', async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .post('/api/admin/features') + .send({ + name: 'com.test.feature', + enabled: false, + strategies: [{ name: 'default' }], + }) + .set('Content-Type', 'application/json') + .expect(201) + .then(destroy); +}); + +test.serial('creates new feature toggle with createdBy', async t => { + t.plan(1); + const { request, destroy } = await setupApp('feature_api_serial'); + await request + .post('/api/admin/features') + .send({ + name: 'com.test.Username', + enabled: false, + strategies: [{ name: 'default' }], + }) + .set('Cookie', ['username=ivaosthu']) + .set('Content-Type', 'application/json'); + await request + .get('/api/admin/events') + .expect(res => { + t.true(res.body.events[0].createdBy === 'ivaosthu'); + }) + .then(destroy); +}); + +test.serial('require new feature toggle to have a name', async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .post('/api/admin/features') + .send({ name: '' }) + .set('Content-Type', 'application/json') + .expect(400) + .then(destroy); +}); + +test.serial( + 'can not change status of feature toggle that does not exist', + async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .put('/api/admin/features/should-not-exist') + .send({ name: 'should-not-exist', enabled: false }) + .set('Content-Type', 'application/json') + .expect(404) + .then(destroy); + } +); + +test.serial('can change status of feature toggle that does exist', async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .put('/api/admin/features/featureY') + .send({ + name: 'featureY', + enabled: true, + strategies: [{ name: 'default' }], + }) + .set('Content-Type', 'application/json') + .expect(200) + .then(destroy); +}); + +test.serial('can not toggle of feature that does not exist', async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .post('/api/admin/features/should-not-exist/toggle') + .set('Content-Type', 'application/json') + .expect(404) + .then(destroy); +}); + +test.serial('can toggle a feature that does exist', async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .post('/api/admin/features/featureY/toggle') + .set('Content-Type', 'application/json') + .expect(200) + .then(destroy); +}); + +test.serial('archives a feature by name', async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .delete('/api/admin/features/featureX') + .expect(200) + .then(destroy); +}); + +test.serial('can not archive unknown feature', async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .delete('/api/admin/features/featureUnknown') + .expect(404) + .then(destroy); +}); + +test.serial('refuses to create a feature with an existing name', async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .post('/api/admin/features') + .send({ name: 'featureX' }) + .set('Content-Type', 'application/json') + .expect(403) + .then(destroy); +}); + +test.serial('refuses to validate a feature with an existing name', async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .post('/api/admin/features/validate') + .send({ name: 'featureX' }) + .set('Content-Type', 'application/json') + .expect(403) + .then(destroy); +}); + +test.serial( + 'new strategies api can add two strategies to a feature toggle', + async t => { + t.plan(0); + const { request, destroy } = await setupApp('feature_api_serial'); + return request + .put('/api/admin/features/featureY') + .send({ + name: 'featureY', + description: 'soon to be the #14 feature', + enabled: false, + strategies: [ + { + name: 'baz', + parameters: { foo: 'bar' }, + }, + ], + }) + .set('Content-Type', 'application/json') + .expect(200) + .then(destroy); + } +); diff --git a/test/e2e/metrics-api.test.js b/test/e2e/api/admin/metrics.e2e.test.js similarity index 54% rename from test/e2e/metrics-api.test.js rename to test/e2e/api/admin/metrics.e2e.test.js index 585426f29f..df2b4d1871 100644 --- a/test/e2e/metrics-api.test.js +++ b/test/e2e/api/admin/metrics.e2e.test.js @@ -1,14 +1,16 @@ 'use strict'; -const test = require('ava'); -const { setupApp } = require('./helpers/test-helper'); -const logger = require('../../lib/logger'); -test.beforeEach(() => { +const { test } = require('ava'); +const { setupApp } = require('./../../helpers/test-helper'); +const logger = require('../../../../lib/logger'); + +test.beforeEach(() => { logger.setLevel('FATAL'); }); -test.serial('should register client', async (t) => { - const { request, destroy } = await setupApp('metrics_serial'); +test.serial('should register client', async t => { + t.plan(0); + const { request, destroy } = await setupApp('metrics_serial'); return request .post('/api/client/register') .send({ @@ -16,35 +18,39 @@ test.serial('should register client', async (t) => { instanceId: 'test', strategies: ['default'], started: Date.now(), - interval: 10 + interval: 10, }) .expect(202) .then(destroy); }); -test.serial('should allow client to register multiple times', async (t) => { - const { request, destroy } = await setupApp('metrics_serial'); +test.serial('should allow client to register multiple times', async t => { + t.plan(0); + const { request, destroy } = await setupApp('metrics_serial'); const clientRegistration = { - appName: 'multipleRegistration', - instanceId: 'test', - strategies: ['default', 'another'], - started: Date.now(), - interval: 10 + appName: 'multipleRegistration', + instanceId: 'test', + strategies: ['default', 'another'], + started: Date.now(), + interval: 10, }; return request .post('/api/client/register') .send(clientRegistration) .expect(202) - .then(() => request - .post('/api/client/register') - .send(clientRegistration) - .expect(202)) + .then(() => + request + .post('/api/client/register') + .send(clientRegistration) + .expect(202) + ) .then(destroy); }); test.serial('should accept client metrics', async t => { - const { request, destroy } = await setupApp('metrics_serial'); + t.plan(0); + const { request, destroy } = await setupApp('metrics_serial'); return request .post('/api/client/metrics') .send({ @@ -53,19 +59,20 @@ test.serial('should accept client metrics', async t => { bucket: { start: Date.now(), stop: Date.now(), - toggles: {} - } + toggles: {}, + }, }) .expect(202) .then(destroy); }); test.serial('should get application details', async t => { - const { request, destroy } = await setupApp('metrics_serial'); + t.plan(3); + const { request, destroy } = await setupApp('metrics_serial'); return request - .get('/api/client/applications/demo-app-1') + .get('/api/admin/metrics/applications/demo-app-1') .expect('Content-Type', /json/) - .expect((res) => { + .expect(res => { t.true(res.status === 200); t.true(res.body.appName === 'demo-app-1'); t.true(res.body.instances.length === 1); @@ -74,11 +81,12 @@ test.serial('should get application details', async t => { }); test.serial('should get list of applications', async t => { - const { request, destroy } = await setupApp('metrics_serial'); + t.plan(2); + const { request, destroy } = await setupApp('metrics_serial'); return request - .get('/api/client/applications') + .get('/api/admin/metrics/applications') .expect('Content-Type', /json/) - .expect((res) => { + .expect(res => { t.true(res.status === 200); t.true(res.body.applications.length === 2); }) diff --git a/test/e2e/strategy-api.test.js b/test/e2e/api/admin/strategy.e2e.test.js similarity index 54% rename from test/e2e/strategy-api.test.js rename to test/e2e/api/admin/strategy.e2e.test.js index bbfff6ac01..f0e7a37587 100644 --- a/test/e2e/strategy-api.test.js +++ b/test/e2e/api/admin/strategy.e2e.test.js @@ -1,102 +1,124 @@ 'use strict'; -const test = require('ava'); -const { setupApp } = require('./helpers/test-helper'); -const logger = require('../../lib/logger'); +const { test } = require('ava'); +const { setupApp } = require('./../../helpers/test-helper'); +const logger = require('../../../../lib/logger'); -test.beforeEach(() => { +test.beforeEach(() => { logger.setLevel('FATAL'); }); -test.serial('gets all strategies', async (t) => { +test.serial('gets all strategies', async t => { + t.plan(1); const { request, destroy } = await setupApp('strategy_api_serial'); return request - .get('/api/strategies') + .get('/api/admin/strategies') .expect('Content-Type', /json/) .expect(200) - .expect((res) => { - t.true(res.body.strategies.length === 2, 'expected to have two strategies'); + .expect(res => { + t.true( + res.body.strategies.length === 2, + 'expected to have two strategies' + ); }) .then(destroy); }); -test.serial('gets a strategy by name', async (t) => { +test.serial('gets a strategy by name', async t => { + t.plan(0); const { request, destroy } = await setupApp('strategy_api_serial'); return request - .get('/api/strategies/default') + .get('/api/admin/strategies/default') .expect('Content-Type', /json/) .expect(200) .then(destroy); }); -test.serial('cant get a strategy by name that dose not exist', async (t) => { +test.serial('cant get a strategy by name that dose not exist', async t => { + t.plan(0); const { request, destroy } = await setupApp('strategy_api_serial'); return request - .get('/api/strategies/mystrategy') + .get('/api/admin/strategies/mystrategy') .expect('Content-Type', /json/) .expect(404) .then(destroy); }); -test.serial('creates a new strategy', async (t) => { +test.serial('creates a new strategy', async t => { + t.plan(0); const { request, destroy } = await setupApp('strategy_api_serial'); return request - .post('/api/strategies') - .send({ name: 'myCustomStrategy', description: 'Best strategy ever.', parameters: [] }) + .post('/api/admin/strategies') + .send({ + name: 'myCustomStrategy', + description: 'Best strategy ever.', + parameters: [], + }) .set('Content-Type', 'application/json') .expect(201) .then(destroy); }); -test.serial('requires new strategies to have a name', async (t) => { +test.serial('requires new strategies to have a name', async t => { + t.plan(0); const { request, destroy } = await setupApp('strategy_api_serial'); return request - .post('/api/strategies') + .post('/api/admin/strategies') .send({ name: '' }) .set('Content-Type', 'application/json') .expect(400) .then(destroy); }); -test.serial('refuses to create a strategy with an existing name', async (t) => { +test.serial('refuses to create a strategy with an existing name', async t => { + t.plan(0); const { request, destroy } = await setupApp('strategy_api_serial'); return request - .post('/api/strategies') + .post('/api/admin/strategies') .send({ name: 'default', parameters: [] }) .set('Content-Type', 'application/json') .expect(403) .then(destroy); }); -test.serial('deletes a new strategy', async (t) => { +test.serial('deletes a new strategy', async t => { + t.plan(0); const { request, destroy } = await setupApp('strategy_api_serial'); return request - .delete('/api/strategies/usersWithEmail') + .delete('/api/admin/strategies/usersWithEmail') .expect(200) .then(destroy); }); -test.serial('can\'t delete a strategy that dose not exist', async (t) => { +test.serial("can't delete a strategy that dose not exist", async t => { + t.plan(0); const { request, destroy } = await setupApp('strategy_api_serial', false); return request - .delete('/api/strategies/unknown') - .expect(404); + .delete('/api/admin/strategies/unknown') + .expect(404) + .then(destroy); }); -test.serial('updates a exiting strategy', async (t) => { +test.serial('updates a exiting strategy', async t => { + t.plan(0); const { request, destroy } = await setupApp('strategy_api_serial'); return request - .put('/api/strategies/default') - .send({ name: 'default', description: 'Default is the best!', parameters: [] }) + .put('/api/admin/strategies/default') + .send({ + name: 'default', + description: 'Default is the best!', + parameters: [], + }) .set('Content-Type', 'application/json') .expect(200) .then(destroy); }); -test.serial('cant update a unknown strategy', async (t) => { +test.serial('cant update a unknown strategy', async t => { + t.plan(0); const { request, destroy } = await setupApp('strategy_api_serial'); return request - .put('/api/strategies/unknown') + .put('/api/admin/strategies/unknown') .send({ name: 'unkown', parameters: [] }) .set('Content-Type', 'application/json') .expect(404) diff --git a/test/e2e/api/client/feature.e2e.test.js b/test/e2e/api/client/feature.e2e.test.js new file mode 100644 index 0000000000..ffb6582852 --- /dev/null +++ b/test/e2e/api/client/feature.e2e.test.js @@ -0,0 +1,5 @@ +'use strict'; + +const { test } = require('ava'); + +test.todo('e2e client feature'); diff --git a/test/e2e/api/client/metrics.e2e.test.js b/test/e2e/api/client/metrics.e2e.test.js new file mode 100644 index 0000000000..a99999c7e8 --- /dev/null +++ b/test/e2e/api/client/metrics.e2e.test.js @@ -0,0 +1,5 @@ +'use strict'; + +const { test } = require('ava'); + +test.todo('e2e client metrics'); diff --git a/test/e2e/feature-api.test.js b/test/e2e/feature-api.test.js deleted file mode 100644 index 172e48840c..0000000000 --- a/test/e2e/feature-api.test.js +++ /dev/null @@ -1,208 +0,0 @@ -'use strict'; - -const { test } = require('ava'); -const { setupApp } = require('./helpers/test-helper'); -const logger = require('../../lib/logger'); - -test.beforeEach(() => { - logger.setLevel('FATAL'); -}); - -test.serial('returns three feature toggles', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - return request - .get('/features') - .expect('Content-Type', /json/) - .expect(200) - .expect((res) => { - t.true(res.body.features.length === 3); - }) - .then(destroy); -}); - -test.serial('gets a feature by name', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - return request - .get('/features/featureX') - .expect('Content-Type', /json/) - .expect(200) - .then(destroy); -}); - -test.serial('cant get feature that dose not exist', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - logger.setLevel('FATAL'); - return request - .get('/features/myfeature') - .expect('Content-Type', /json/) - .expect(404) - .then(destroy); -}); - -test.serial('creates new feature toggle', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - return request - .post('/features') - .send({ name: 'com.test.feature', enabled: false, strategies: [{name: 'default'}] }) - .set('Content-Type', 'application/json') - .expect(201) - .then(destroy); -}); - -test.serial('creates new feature toggle with createdBy', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - logger.setLevel('FATAL'); - request - .post('/features') - .send({ name: 'com.test.Username', enabled: false, strategies: [{name: 'default'}] }) - .set('Cookie', ['username=ivaosthu']) - .set('Content-Type', 'application/json') - .end(() => { - return request - .get('/api/events') - .expect((res) => { - t.true(res.body.events[0].createdBy === 'ivaosthu'); - }) - .then(destroy); - }); -}); - -test.serial('require new feature toggle to have a name', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - logger.setLevel('FATAL'); - return request - .post('/features') - .send({ name: '' }) - .set('Content-Type', 'application/json') - .expect(400) - .then(destroy); -}); - -test.serial('can not change status of feature toggle that does not exist', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - logger.setLevel('FATAL'); - return request - .put('/features/should-not-exist') - .send({ name: 'should-not-exist', enabled: false }) - .set('Content-Type', 'application/json') - .expect(404).then(destroy); -}); - -test.serial('can change status of feature toggle that does exist', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - logger.setLevel('FATAL'); - return request - .put('/features/featureY') - .send({ name: 'featureY', enabled: true, strategies: [{name: 'default'}] }) - .set('Content-Type', 'application/json') - .expect(200).then(destroy); -}); - -test.serial('can not toggle of feature that does not exist', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - logger.setLevel('FATAL'); - return request - .post('/features/should-not-exist/toggle') - .set('Content-Type', 'application/json') - .expect(404).then(destroy); -}); - -test.serial('can toggle a feature that does exist', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - logger.setLevel('FATAL'); - return request - .post('/features/featureY/toggle') - .set('Content-Type', 'application/json') - .expect(200).then(destroy); -}); - -test.serial('archives a feature by name', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - return request - .delete('/features/featureX') - .expect(200).then(destroy); -}); - -test.serial('can not archive unknown feature', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - return request - .delete('/features/featureUnknown') - .expect(404).then(destroy); -}); - -test.serial('refuses to create a feature with an existing name', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - return request - .post('/features') - .send({ name: 'featureX' }) - .set('Content-Type', 'application/json') - .expect(403).then(destroy); -}); - -test.serial('refuses to validate a feature with an existing name', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - return request - .post('/features-validate') - .send({ name: 'featureX' }) - .set('Content-Type', 'application/json') - .expect(403).then(destroy); -}); - - -test.serial('new strategies api automatically map existing strategy to strategies array', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - t.plan(3); - return request - .get('/features/featureY') - .expect('Content-Type', /json/) - .expect((res) => { - t.true(res.body.strategies.length === 1, 'expected strategy added to strategies'); - t.true(res.body.strategy === res.body.strategies[0].name); - t.deepEqual(res.body.parameters, res.body.strategies[0].parameters); - }) - .then(destroy); -}); - -test.serial('new strategies api can add two strategies to a feature toggle', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - return request - .put('/features/featureY') - .send({ - name: 'featureY', - description: 'soon to be the #14 feature', - enabled: false, - strategies: [ - { - name: 'baz', - parameters: { foo: 'bar' }, - }, - ], - }) - .set('Content-Type', 'application/json') - .expect(200) - .then(destroy); -}); - -test.serial('new strategies api should not be allowed to post both strategy and strategies', async t => { - const { request, destroy } = await setupApp('feature_api_serial'); - logger.setLevel('FATAL'); - return request - .post('/features') - .send({ - name: 'featureConfusing', - description: 'soon to be the #14 feature', - enabled: false, - strategy: 'baz', - parameters: {}, - strategies: [ - { - name: 'baz', - parameters: { foo: 'bar' }, - }, - ], - }) - .set('Content-Type', 'application/json') - .expect(400) - .then(destroy); -}); - diff --git a/test/e2e/router.test.js b/test/e2e/health.e2e.test.js similarity index 68% rename from test/e2e/router.test.js rename to test/e2e/health.e2e.test.js index d314011efa..c3447408de 100644 --- a/test/e2e/router.test.js +++ b/test/e2e/health.e2e.test.js @@ -1,16 +1,18 @@ 'use strict'; -const test = require('ava'); +const { test } = require('ava'); const { setupApp } = require('./helpers/test-helper'); const logger = require('../../lib/logger'); -test.beforeEach(() => { +test.beforeEach(() => { logger.setLevel('FATAL'); }); -test('returns health good', async (t) => { +test('returns health good', async t => { + t.plan(0); const { request, destroy } = await setupApp('health'); - return request.get('/health') + return request + .get('/health') .expect('Content-Type', /json/) .expect(200) .expect('{"health":"GOOD"}') diff --git a/test/e2e/helpers/database-config.js b/test/e2e/helpers/database-config.js index 8b6ecebf9b..800ce52331 100644 --- a/test/e2e/helpers/database-config.js +++ b/test/e2e/helpers/database-config.js @@ -1,6 +1,6 @@ 'use strict'; -function getDatabaseUrl () { +function getDatabaseUrl() { if (process.env.TEST_DATABASE_URL) { return process.env.TEST_DATABASE_URL; } else { diff --git a/test/e2e/helpers/test-helper.js b/test/e2e/helpers/test-helper.js index d84b5b1075..9e4921b4d1 100644 --- a/test/e2e/helpers/test-helper.js +++ b/test/e2e/helpers/test-helper.js @@ -18,16 +18,21 @@ delete process.env.DATABASE_URL; const { EventEmitter } = require('events'); const eventBus = new EventEmitter(); -function createApp (databaseSchema = 'test') { +function createApp(databaseSchema = 'test') { const options = { databaseUrl: require('./database-config').getDatabaseUrl(), databaseSchema, minPool: 0, maxPool: 0, }; - const db = createDb({ databaseUrl: options.databaseUrl, minPool: 0, maxPool: 0 }); + const db = createDb({ + databaseUrl: options.databaseUrl, + minPool: 0, + maxPool: 0, + }); - return db.raw(`CREATE SCHEMA IF NOT EXISTS ${options.databaseSchema}`) + return db + .raw(`CREATE SCHEMA IF NOT EXISTS ${options.databaseSchema}`) .then(() => migrator(options)) .then(() => { db.destroy(); @@ -36,14 +41,14 @@ function createApp (databaseSchema = 'test') { return { stores, request: supertest(app), - destroy () { + destroy() { return stores.db.destroy(); }, }; }); } -function createStrategies (stores) { +function createStrategies(stores) { return [ { name: 'default', @@ -52,15 +57,14 @@ function createStrategies (stores) { }, { name: 'usersWithEmail', - description: 'Active for users defined in the comma-separated emails-parameter.', - parameters: [ - { name: 'emails', type: 'string' }, - ], + description: + 'Active for users defined in the comma-separated emails-parameter.', + parameters: [{ name: 'emails', type: 'string' }], }, ].map(strategy => stores.strategyStore._createStrategy(strategy)); } -function createApplications (stores) { +function createApplications(stores) { return [ { appName: 'demo-app-1', @@ -74,7 +78,7 @@ function createApplications (stores) { ].map(client => stores.clientApplicationsStore.upsert(client)); } -function createClientInstance (stores) { +function createClientInstance(stores) { return [ { appName: 'demo-app-1', @@ -93,7 +97,7 @@ function createClientInstance (stores) { ].map(client => stores.clientInstanceStore.insert(client)); } -function createFeatures (stores) { +function createFeatures(stores) { return [ { name: 'featureX', @@ -105,23 +109,27 @@ function createFeatures (stores) { name: 'featureY', description: 'soon to be the #1 feature', enabled: false, - strategies: [{ - name: 'baz', - parameters: { - foo: 'bar', + strategies: [ + { + name: 'baz', + parameters: { + foo: 'bar', + }, }, - }], + ], }, { name: 'featureZ', description: 'terrible feature', enabled: true, - strategies: [{ - name: 'baz', - parameters: { - foo: 'rab', + strategies: [ + { + name: 'baz', + parameters: { + foo: 'rab', + }, }, - }], + ], }, { name: 'featureArchivedX', @@ -135,29 +143,33 @@ function createFeatures (stores) { description: 'soon to be the #1 feature', enabled: false, archived: true, - strategies: [{ - name: 'baz', - parameters: { - foo: 'bar', + strategies: [ + { + name: 'baz', + parameters: { + foo: 'bar', + }, }, - }], + ], }, { name: 'featureArchivedZ', description: 'terrible feature', enabled: true, archived: true, - strategies: [{ - name: 'baz', - parameters: { - foo: 'rab', + strategies: [ + { + name: 'baz', + parameters: { + foo: 'rab', + }, }, - }], + ], }, ].map(feature => stores.featureToggleStore._createFeature(feature)); } -function resetDatabase (stores) { +function resetDatabase(stores) { return Promise.all([ stores.db('strategies').del(), stores.db('features').del(), @@ -166,20 +178,22 @@ function resetDatabase (stores) { ]); } -function setupDatabase (stores) { +function setupDatabase(stores) { return Promise.all( - createStrategies(stores) - .concat(createFeatures(stores) - .concat(createClientInstance(stores)) - .concat(createApplications(stores)))); + createStrategies(stores).concat( + createFeatures(stores) + .concat(createClientInstance(stores)) + .concat(createApplications(stores)) + ) + ); } module.exports = { - setupApp (name) { - return createApp(name).then((app) => { - return resetDatabase(app.stores) + setupApp(name) { + return createApp(name).then(app => + resetDatabase(app.stores) .then(() => setupDatabase(app.stores)) - .then(() => app); - }); + .then(() => app) + ); }, }; diff --git a/test/unit/routes/fixtures/fake-client-applications-store.js b/test/fixtures/fake-client-applications-store.js similarity index 100% rename from test/unit/routes/fixtures/fake-client-applications-store.js rename to test/fixtures/fake-client-applications-store.js diff --git a/test/unit/routes/fixtures/fake-client-instance-store.js b/test/fixtures/fake-client-instance-store.js similarity index 100% rename from test/unit/routes/fixtures/fake-client-instance-store.js rename to test/fixtures/fake-client-instance-store.js diff --git a/test/fixtures/fake-event-store.js b/test/fixtures/fake-event-store.js new file mode 100644 index 0000000000..8c5618634e --- /dev/null +++ b/test/fixtures/fake-event-store.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = () => ({ + store: () => Promise.resolve(), + getEvents: () => Promise.resolve([]), +}); diff --git a/test/unit/routes/fixtures/fake-feature-toggle-store.js b/test/fixtures/fake-feature-toggle-store.js similarity index 74% rename from test/unit/routes/fixtures/fake-feature-toggle-store.js rename to test/fixtures/fake-feature-toggle-store.js index 2b2327fcaa..acfb5523ce 100644 --- a/test/unit/routes/fixtures/fake-feature-toggle-store.js +++ b/test/fixtures/fake-feature-toggle-store.js @@ -1,10 +1,9 @@ 'use strict'; - -module.exports = () => { +module.exports = () => { const _features = []; return { - getFeature: (name) => { + getFeature: name => { const toggle = _features.find(f => f.name === name); if (toggle) { return Promise.resolve(toggle); @@ -13,6 +12,6 @@ module.exports = () => { } }, getFeatures: () => Promise.resolve(_features), - addFeature: (feature) => _features.push(feature), + addFeature: feature => _features.push(feature), }; }; diff --git a/test/unit/routes/fixtures/fake-metrics-store.js b/test/fixtures/fake-metrics-store.js similarity index 68% rename from test/unit/routes/fixtures/fake-metrics-store.js rename to test/fixtures/fake-metrics-store.js index c19033ca5b..37832b6839 100644 --- a/test/unit/routes/fixtures/fake-metrics-store.js +++ b/test/fixtures/fake-metrics-store.js @@ -3,12 +3,12 @@ const { EventEmitter } = require('events'); class FakeMetricsStore extends EventEmitter { - getMetricsLastHour () { + getMetricsLastHour() { return Promise.resolve([]); } - insert () { + insert() { return Promise.resolve(); - } + } } -module.exports = FakeMetricsStore; \ No newline at end of file +module.exports = FakeMetricsStore; diff --git a/test/unit/routes/fixtures/fake-strategies-store.js b/test/fixtures/fake-strategies-store.js similarity index 73% rename from test/unit/routes/fixtures/fake-strategies-store.js rename to test/fixtures/fake-strategies-store.js index a08fdc4041..82bbc1f86d 100644 --- a/test/unit/routes/fixtures/fake-strategies-store.js +++ b/test/fixtures/fake-strategies-store.js @@ -1,15 +1,13 @@ 'use strict'; -const NotFoundError = require('../../../../lib/error/notfound-error'); - - +const NotFoundError = require('../../lib/error/notfound-error'); module.exports = () => { const _strategies = [{ name: 'default', parameters: {} }]; return { getStrategies: () => Promise.resolve(_strategies), - getStrategy: (name) => { + getStrategy: name => { const strategy = _strategies.find(s => s.name === name); if (strategy) { return Promise.resolve(strategy); @@ -17,6 +15,6 @@ module.exports = () => { return Promise.reject(new NotFoundError('Not found!')); } }, - addStrategy: (strat) => _strategies.push(strat), + addStrategy: strat => _strategies.push(strat), }; }; diff --git a/test/unit/routes/fixtures/store.js b/test/fixtures/store.js similarity index 99% rename from test/unit/routes/fixtures/store.js rename to test/fixtures/store.js index 4037f3360f..fb1083398e 100644 --- a/test/unit/routes/fixtures/store.js +++ b/test/fixtures/store.js @@ -7,8 +7,6 @@ const featureToggleStore = require('./fake-feature-toggle-store'); const eventStore = require('./fake-event-store'); const strategyStore = require('./fake-strategies-store'); - - module.exports = { createStores: () => { const db = { diff --git a/test/unit/routes/fixtures/fake-event-store.js b/test/unit/routes/fixtures/fake-event-store.js deleted file mode 100644 index 7c32668241..0000000000 --- a/test/unit/routes/fixtures/fake-event-store.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports = () => { - return { - store: () => Promise.resolve(), - }; -}; diff --git a/test/unit/routes/metrics.test.js b/test/unit/routes/metrics.test.js deleted file mode 100644 index 759b6bc036..0000000000 --- a/test/unit/routes/metrics.test.js +++ /dev/null @@ -1,168 +0,0 @@ -'use strict'; - -const test = require('ava'); -const store = require('./fixtures/store'); -const supertest = require('supertest'); -const logger = require('../../../lib/logger'); -const getApp = require('../../../lib/app'); - -const { EventEmitter } = require('events'); -const eventBus = new EventEmitter(); - -test.beforeEach(() => { - logger.setLevel('FATAL'); -}); - -function getSetup () { - const stores = store.createStores(); - const app = getApp({ - baseUriPath: '', - stores, - eventBus, - }); - - return { - request: supertest(app), - stores, - }; -} - -test('should register client', () => { - const { request } = getSetup(); - return request - .post('/api/client/register') - .send({ - appName: 'demo', - instanceId: 'test', - strategies: ['default'], - started: Date.now(), - interval: 10, - }) - .expect(202); -}); - -test('should require appName field', () => { - const { request } = getSetup(); - return request - .post('/api/client/register') - .expect(400); -}); - -test('should require strategies field', () => { - const { request } = getSetup(); - return request - .post('/api/client/register') - .send({ - appName: 'demo', - instanceId: 'test', - // strategies: ['default'], - started: Date.now(), - interval: 10, - }) - .expect(400); -}); - -test('should validate client metrics', () => { - const { request } = getSetup(); - return request - .post('/api/client/metrics') - .send({ random: 'blush' }) - .expect(400); -}); - - -test('should accept client metrics', () => { - const { request } = getSetup(); - return request - .post('/api/client/metrics') - .send({ - appName: 'demo', - instanceId: '1', - bucket: { - start: Date.now(), - stop: Date.now(), - toggles: {}, - }, - }) - .expect(202); -}); - -test('should return seen toggles even when there is nothing', t => { - const { request } = getSetup(); - return request - .get('/api/client/seen-toggles') - .expect(200) - .expect((res) => { - t.true(res.body.length === 0); - }); -}); - -test('should return list of seen-toggles per app', t => { - const { request, stores } = getSetup(); - const appName = 'asd!23'; - stores.clientMetricsStore.emit('metrics', { - appName, - instanceId: 'instanceId', - bucket: { - start: new Date(), - stop: new Date(), - toggles: { - toggleX: { yes: 123, no: 0 }, - toggleY: { yes: 123, no: 0 } - }, - }, - }); - - return request - .get('/api/client/seen-toggles') - .expect(200) - .expect((res) => { - const seenAppsWithToggles = res.body; - t.true(seenAppsWithToggles.length === 1); - t.true(seenAppsWithToggles[0].appName === appName); - t.true(seenAppsWithToggles[0].seenToggles.length === 2); - }); -}); - -test('should return feature-toggles metrics even when there is nothing', t => { - const { request } = getSetup(); - return request - .get('/api/client/metrics/feature-toggles') - .expect(200); -}); - -test('should return metrics for all toggles', t => { - const { request, stores } = getSetup(); - const appName = 'asd!23'; - stores.clientMetricsStore.emit('metrics', { - appName, - instanceId: 'instanceId', - bucket: { - start: new Date(), - stop: new Date(), - toggles: { - toggleX: { yes: 123, no: 0 }, - toggleY: { yes: 123, no: 0 }, - }, - }, - }); - - return request - .get('/api/client/metrics/feature-toggles') - .expect(200) - .expect((res) => { - const metrics = res.body; - t.true(metrics.lastHour !== undefined); - t.true(metrics.lastMinute !== undefined); - }); -}); - -test('should return list of client applications', t => { - const { request } = getSetup(); - return request - .get('/api/client/applications') - .expect(200) - .expect((res) => { - t.true(res.body.applications.length === 0); - }); -}); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000000..4be88de269 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4947 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ava/babel-plugin-throws-helper@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@ava/babel-plugin-throws-helper/-/babel-plugin-throws-helper-2.0.0.tgz#2fc1fe3c211a71071a4eca7b8f7af5842cd1ae7c" + +"@ava/babel-preset-stage-4@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@ava/babel-preset-stage-4/-/babel-preset-stage-4-1.1.0.tgz#ae60be881a0babf7d35f52aba770d1f6194f76bd" + dependencies: + babel-plugin-check-es2015-constants "^6.8.0" + babel-plugin-syntax-trailing-function-commas "^6.20.0" + babel-plugin-transform-async-to-generator "^6.16.0" + babel-plugin-transform-es2015-destructuring "^6.19.0" + babel-plugin-transform-es2015-function-name "^6.9.0" + babel-plugin-transform-es2015-modules-commonjs "^6.18.0" + babel-plugin-transform-es2015-parameters "^6.21.0" + babel-plugin-transform-es2015-spread "^6.8.0" + babel-plugin-transform-es2015-sticky-regex "^6.8.0" + babel-plugin-transform-es2015-unicode-regex "^6.11.0" + babel-plugin-transform-exponentiation-operator "^6.8.0" + package-hash "^1.2.0" + +"@ava/babel-preset-transform-test-files@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@ava/babel-preset-transform-test-files/-/babel-preset-transform-test-files-3.0.0.tgz#cded1196a8d8d9381a509240ab92e91a5ec069f7" + dependencies: + "@ava/babel-plugin-throws-helper" "^2.0.0" + babel-plugin-espower "^2.3.2" + +"@ava/pretty-format@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@ava/pretty-format/-/pretty-format-1.1.0.tgz#d0a57d25eb9aeab9643bdd1a030642b91c123e28" + dependencies: + ansi-styles "^2.2.1" + esutils "^2.0.2" + +"@types/bluebird@~3.0.36": + version "3.0.37" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.0.37.tgz#2e76b394aa9bea40d04241a31c0887a260283388" + +"@types/express-serve-static-core@*": + version "4.0.48" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.0.48.tgz#b4fa06b0fce282e582b4535ff7fac85cc90173e9" + dependencies: + "@types/node" "*" + +"@types/express@~4.0.34": + version "4.0.36" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.0.36.tgz#14eb47de7ecb10319f0a2fb1cf971aa8680758c2" + dependencies: + "@types/express-serve-static-core" "*" + "@types/serve-static" "*" + +"@types/mime@*": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.1.tgz#2cf42972d0931c1060c7d5fa6627fce6bd876f2f" + +"@types/node@*", "@types/node@^7.0.5": + version "7.0.32" + resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.32.tgz#6afe6c66520a4c316623a14aef123908d01b4bba" + +"@types/serve-static@*": + version "1.7.31" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.7.31.tgz#15456de8d98d6b4cff31be6c6af7492ae63f521a" + dependencies: + "@types/express-serve-static-core" "*" + "@types/mime" "*" + +abbrev@1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" + +accepts@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" + dependencies: + mime-types "~2.1.11" + negotiator "0.6.1" + +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + dependencies: + acorn "^3.0.4" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + +acorn@^5.0.1: + version "5.0.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d" + +ajv-keywords@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" + +ajv@^4.7.0, ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-align@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" + dependencies: + string-width "^2.0.0" + +ansi-escapes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +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.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.1.0.tgz#09c202d5c917ec23188caa5c9cb9179cd9547750" + dependencies: + color-convert "^1.0.0" + +ansi-styles@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178" + +anymatch@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" + dependencies: + arrify "^1.0.0" + micromatch "^2.1.5" + +ap@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ap/-/ap-0.2.0.tgz#ae0942600b29912f0d2b14ec60c45e8f330b6110" + +append-transform@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" + dependencies: + default-require-extensions "^1.0.0" + +aproba@^1.0.3: + version "1.1.2" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1" + +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + +are-we-there-yet@~1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + dependencies: + delegates "^1.0.0" + 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" + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + dependencies: + arr-flatten "^1.0.1" + +arr-exclude@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/arr-exclude/-/arr-exclude-1.0.0.tgz#dfc7c2e552a270723ccda04cf3128c8cbfe5c631" + +arr-flatten@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.3.tgz#a274ed85ac08849b6bd7847c4580745dc51adfb1" + +array-differ@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1, array-uniq@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + +arrify@^1.0.0, arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +asap@^2.0.3, asap@~2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" + +asn1@~0.2.0, asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + +async@^1.4.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +async@^2.1.5: + version "2.4.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7" + dependencies: + lodash "^4.14.0" + +async@~0.9.0: + version "0.9.2" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" + +async@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +auto-bind@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-1.1.0.tgz#93b864dc7ee01a326281775d5c75ca0a751e5961" + +ava-init@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/ava-init/-/ava-init-0.2.0.tgz#9304c8b4c357d66e3dfdae1fbff47b1199d5c55d" + dependencies: + arr-exclude "^1.0.0" + execa "^0.5.0" + has-yarn "^1.0.0" + read-pkg-up "^2.0.0" + write-pkg "^2.0.0" + +ava@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/ava/-/ava-0.19.1.tgz#43dd82435ad19b3980ffca2488f05daab940b273" + dependencies: + "@ava/babel-preset-stage-4" "^1.0.0" + "@ava/babel-preset-transform-test-files" "^3.0.0" + "@ava/pretty-format" "^1.1.0" + arr-flatten "^1.0.1" + array-union "^1.0.1" + array-uniq "^1.0.2" + arrify "^1.0.0" + auto-bind "^1.1.0" + ava-init "^0.2.0" + babel-code-frame "^6.16.0" + babel-core "^6.17.0" + bluebird "^3.0.0" + caching-transform "^1.0.0" + chalk "^1.0.0" + chokidar "^1.4.2" + clean-stack "^1.1.1" + clean-yaml-object "^0.1.0" + cli-cursor "^2.1.0" + cli-spinners "^1.0.0" + cli-truncate "^1.0.0" + co-with-promise "^4.6.0" + code-excerpt "^2.1.0" + common-path-prefix "^1.0.0" + convert-source-map "^1.2.0" + core-assert "^0.2.0" + currently-unhandled "^0.4.1" + debug "^2.2.0" + diff "^3.0.1" + diff-match-patch "^1.0.0" + dot-prop "^4.1.0" + empower-core "^0.6.1" + equal-length "^1.0.0" + figures "^2.0.0" + find-cache-dir "^0.1.1" + fn-name "^2.0.0" + get-port "^3.0.0" + globby "^6.0.0" + has-flag "^2.0.0" + hullabaloo-config-manager "^1.0.0" + ignore-by-default "^1.0.0" + indent-string "^3.0.0" + is-ci "^1.0.7" + is-generator-fn "^1.0.0" + is-obj "^1.0.0" + is-observable "^0.2.0" + is-promise "^2.1.0" + jest-diff "19.0.0" + jest-snapshot "19.0.2" + js-yaml "^3.8.2" + last-line-stream "^1.0.0" + lodash.debounce "^4.0.3" + lodash.difference "^4.3.0" + lodash.flatten "^4.2.0" + lodash.isequal "^4.5.0" + loud-rejection "^1.2.0" + matcher "^0.1.1" + md5-hex "^2.0.0" + meow "^3.7.0" + mkdirp "^0.5.1" + ms "^0.7.1" + multimatch "^2.1.0" + observable-to-promise "^0.5.0" + option-chain "^0.1.0" + package-hash "^2.0.0" + pkg-conf "^2.0.0" + plur "^2.0.0" + pretty-ms "^2.0.0" + require-precompiled "^0.1.0" + resolve-cwd "^1.0.0" + slash "^1.0.0" + source-map-support "^0.4.0" + stack-utils "^1.0.0" + strip-ansi "^3.0.1" + strip-bom-buf "^1.0.0" + supports-color "^3.2.3" + time-require "^0.1.2" + unique-temp-dir "^1.0.0" + update-notifier "^2.1.0" + +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + +aws4@^1.2.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + +babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" + dependencies: + chalk "^1.1.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +babel-core@^6.17.0, babel-core@^6.24.1: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.25.0.tgz#7dd42b0463c742e9d5296deb3ec67a9322dad729" + dependencies: + babel-code-frame "^6.22.0" + babel-generator "^6.25.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.25.0" + babel-traverse "^6.25.0" + babel-types "^6.25.0" + babylon "^6.17.2" + convert-source-map "^1.1.0" + debug "^2.1.1" + json5 "^0.5.0" + lodash "^4.2.0" + minimatch "^3.0.2" + path-is-absolute "^1.0.0" + private "^0.1.6" + slash "^1.0.0" + source-map "^0.5.0" + +babel-generator@^6.1.0, babel-generator@^6.18.0, babel-generator@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.25.0.tgz#33a1af70d5f2890aeb465a4a7793c1df6a9ea9fc" + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-types "^6.25.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.2.0" + source-map "^0.5.0" + trim-right "^1.0.1" + +babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" + dependencies: + babel-helper-explode-assignable-expression "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-call-delegate@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-explode-assignable-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + dependencies: + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-get-function-arity@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-hoist-variables@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-regex@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz#d36e22fab1008d79d88648e32116868128456ce8" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + lodash "^4.2.0" + +babel-helper-remap-async-to-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-check-es2015-constants@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-espower@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/babel-plugin-espower/-/babel-plugin-espower-2.3.2.tgz#5516b8fcdb26c9f0e1d8160749f6e4c65e71271e" + dependencies: + babel-generator "^6.1.0" + babylon "^6.1.0" + call-matcher "^1.0.0" + core-js "^2.0.0" + espower-location-detector "^1.0.0" + espurify "^1.6.0" + estraverse "^4.1.1" + +babel-plugin-syntax-async-functions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + +babel-plugin-syntax-exponentiation-operator@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + +babel-plugin-syntax-trailing-function-commas@^6.20.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + +babel-plugin-transform-async-to-generator@^6.16.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-functions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-destructuring@^6.19.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-function-name@^6.9.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-modules-commonjs@^6.18.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz#d3e310b40ef664a36622200097c6d440298f2bfe" + dependencies: + babel-plugin-transform-strict-mode "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-parameters@^6.21.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + dependencies: + babel-helper-call-delegate "^6.24.1" + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-spread@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-sticky-regex@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-unicode-regex@^6.11.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + regexpu-core "^2.0.0" + +babel-plugin-transform-exponentiation-operator@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" + dependencies: + babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" + babel-plugin-syntax-exponentiation-operator "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-strict-mode@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-register@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.24.1.tgz#7e10e13a2f71065bdfad5a1787ba45bca6ded75f" + dependencies: + babel-core "^6.24.1" + babel-runtime "^6.22.0" + core-js "^2.4.0" + home-or-tmp "^2.0.0" + lodash "^4.2.0" + mkdirp "^0.5.1" + source-map-support "^0.4.2" + +babel-runtime@^6.11.6, babel-runtime@^6.22.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + +babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.25.0" + babel-types "^6.25.0" + babylon "^6.17.2" + lodash "^4.2.0" + +babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.25.0.tgz#2257497e2fcd19b89edc13c4c91381f9512496f1" + dependencies: + babel-code-frame "^6.22.0" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-types "^6.25.0" + babylon "^6.17.2" + debug "^2.2.0" + globals "^9.0.0" + invariant "^2.2.0" + lodash "^4.2.0" + +babel-types@^6.18.0, babel-types@^6.24.1, babel-types@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.25.0.tgz#70afb248d5660e5d18f811d91c8303b54134a18e" + dependencies: + babel-runtime "^6.22.0" + esutils "^2.0.2" + lodash "^4.2.0" + to-fast-properties "^1.0.1" + +babylon@^6.1.0, babylon@^6.13.0, babylon@^6.17.2: + version "6.17.4" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" + +balanced-match@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +bcrypt-pbkdf@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + dependencies: + tweetnacl "^0.14.3" + +binary-extensions@^1.0.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774" + +bintrees@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + dependencies: + inherits "~2.0.0" + +bluebird@^3.0.0, bluebird@^3.1.1, bluebird@^3.4.0, bluebird@^3.4.6: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" + +body-parser@1.17.2: + version "1.17.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.17.2.tgz#f8892abc8f9e627d42aedafbca66bf5ab99104ee" + dependencies: + bytes "2.4.0" + content-type "~1.0.2" + debug "2.6.7" + depd "~1.1.0" + http-errors "~1.6.1" + iconv-lite "0.4.15" + on-finished "~2.3.0" + qs "6.4.0" + raw-body "~2.2.0" + type-is "~1.6.15" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + +boxen@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.1.0.tgz#b1b69dd522305e807a99deee777dbd6e5167b102" + dependencies: + ansi-align "^2.0.0" + camelcase "^4.0.0" + chalk "^1.1.1" + cli-boxes "^1.0.0" + string-width "^2.0.0" + term-size "^0.1.0" + widest-line "^1.0.0" + +brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + +buf-compare@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buf-compare/-/buf-compare-1.0.1.tgz#fef28da8b8113a0a0db4430b0b6467b69730b34a" + +buffer-writer@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08" + +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +bytes@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" + +caching-transform@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-1.0.1.tgz#6dbdb2f20f8d8fbce79f3e94e9d1742dcdf5c0a1" + dependencies: + md5-hex "^1.2.0" + mkdirp "^0.5.1" + write-file-atomic "^1.1.4" + +call-matcher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-matcher/-/call-matcher-1.0.1.tgz#5134d077984f712a54dad3cbf62de28dce416ca8" + dependencies: + core-js "^2.0.0" + deep-equal "^1.0.0" + espurify "^1.6.0" + estraverse "^4.0.0" + +call-signature@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/call-signature/-/call-signature-0.0.2.tgz#a84abc825a55ef4cb2b028bd74e205a65b9a4996" + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + dependencies: + callsites "^0.2.0" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + +camelcase@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + +capture-stack-trace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" + +caseless@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +chain-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" + +chalk@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" + dependencies: + ansi-styles "~1.0.0" + has-color "~0.1.0" + strip-ansi "~0.1.0" + +chalk@^1.0.0, chalk@^1.1.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: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chokidar@^1.4.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + +ci-info@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534" + +circular-json@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" + +clamp@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/clamp/-/clamp-1.0.1.tgz#66a0e64011816e37196828fdc8c8c147312c8634" + +classnames@^2.2.3: + version "2.2.5" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" + +clean-stack@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-1.3.0.tgz#9e821501ae979986c46b1d66d2d432db2fd4ae31" + +clean-yaml-object@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz#63fb110dc2ce1a84dc21f6d9334876d010ae8b68" + +cli-boxes@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.0.0.tgz#ef987ed3d48391ac3dab9180b406a742180d6e6a" + +cli-truncate@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-1.0.0.tgz#21eb91f47b3f6560f004db77a769b4668d9c5518" + dependencies: + slice-ansi "0.0.4" + string-width "^2.0.0" + +cli-width@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +co-with-promise@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co-with-promise/-/co-with-promise-4.6.0.tgz#413e7db6f5893a60b942cf492c4bec93db415ab7" + dependencies: + pinkie-promise "^1.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +code-excerpt@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/code-excerpt/-/code-excerpt-2.1.0.tgz#5dcc081e88f4a7e3b554e9e35d7ef232d47f8147" + dependencies: + convert-to-spaces "^1.0.1" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +color-convert@^1.0.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" + dependencies: + color-name "^1.1.1" + +color-name@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d" + +colors@1.0.x: + version "1.0.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" + +colors@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + +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" + dependencies: + delayed-stream "~1.0.0" + +commander@^2.2.0, commander@^2.9.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.10.0.tgz#e1f5d3245de246d1a5ca04702fa1ad1bd7e405fe" + dependencies: + graceful-readlink ">= 1.0.0" + +common-path-prefix@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-1.0.0.tgz#cd52f6f0712e0baab97d6f9732874f22f47752c0" + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + +component-emitter@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +configstore@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.0.tgz#45df907073e26dfa1cf4b2d52f5b60545eaa11d1" + dependencies: + dot-prop "^4.1.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + unique-string "^1.0.0" + write-file-atomic "^2.0.0" + xdg-basedir "^3.0.0" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + +content-type@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" + +convert-source-map@^1.1.0, convert-source-map@^1.2.0, convert-source-map@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" + +convert-to-spaces@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz#7e3e48bbe6d997b1417ddca2868204b4d3d85715" + +cookie-parser@^1.4.1: + 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" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + +cookiejar@^2.0.6: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a" + +core-assert@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/core-assert/-/core-assert-0.2.1.tgz#f85e2cf9bfed28f773cc8b3fa5c5b69bdc02fe3f" + dependencies: + buf-compare "^1.0.0" + is-error "^2.2.0" + +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.0.0, core-js@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" + +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" + +coveralls@^2.11.16: + version "2.13.1" + resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.13.1.tgz#d70bb9acc1835ec4f063ff9dac5423c17b11f178" + dependencies: + js-yaml "3.6.1" + lcov-parse "0.0.10" + log-driver "1.2.5" + minimist "1.2.0" + request "2.79.0" + +create-error-class@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" + dependencies: + capture-stack-trace "^1.0.0" + +create-react-class@^15.5.1, create-react-class@^15.5.2, create-react-class@^15.6.0: + version "15.6.0" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.0.tgz#ab448497c26566e1e29413e883207d57cfe7bed4" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + object-assign "^4.1.1" + +cross-spawn-async@^2.1.1: + version "2.2.5" + resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc" + dependencies: + lru-cache "^4.0.0" + which "^1.2.8" + +cross-spawn@^4, cross-spawn@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" + +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + +crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + dependencies: + array-find-index "^1.0.1" + +cycle@1.0.x: + version "1.0.3" + resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +date-format@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/date-format/-/date-format-0.0.0.tgz#09206863ab070eb459acea5542cbd856b11966b3" + +date-time@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/date-time/-/date-time-0.1.1.tgz#ed2f6d93d9790ce2fd66d5b5ff3edd5bbcbf3b07" + +db-migrate-base@^1.2.5: + version "1.2.7" + resolved "https://registry.yarnpkg.com/db-migrate-base/-/db-migrate-base-1.2.7.tgz#eedbc528f8053c7814feaad2469a8aa130a3009f" + dependencies: + bluebird "^3.1.1" + +db-migrate-pg@0.1.11: + version "0.1.11" + resolved "https://registry.yarnpkg.com/db-migrate-pg/-/db-migrate-pg-0.1.11.tgz#0ce87f3446b8c662afdadbb64277953a6ff31843" + dependencies: + bluebird "^3.1.1" + db-migrate-base "^1.2.5" + pg "^4.5.5" + semver "^5.0.3" + +db-migrate-shared@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/db-migrate-shared/-/db-migrate-shared-1.1.3.tgz#edb3c817b295022c61984c187267419bb36a2d58" + +db-migrate@0.10.0-beta.20: + version "0.10.0-beta.20" + resolved "https://registry.yarnpkg.com/db-migrate/-/db-migrate-0.10.0-beta.20.tgz#b5af2d4341d57f0ad693ad9af31e31601e33648c" + dependencies: + balanced-match "^0.4.2" + bluebird "^3.1.1" + db-migrate-shared "^1.1.2" + dotenv "^2.0.0" + final-fs "^1.6.0" + inflection "^1.10.0" + mkdirp "~0.5.0" + moment "^2.14.1" + optimist "~0.6.1" + parse-database-url "~0.3.0" + pkginfo "^0.4.0" + prompt "^1.0.0" + resolve "^1.1.6" + semver "^5.3.0" + tunnel-ssh "^4.0.0" + +debug-log@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" + +debug@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" + dependencies: + ms "0.7.2" + +debug@2.6.7, debug@^2.1.3, debug@^2.2.0: + version "2.6.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" + dependencies: + ms "2.0.0" + +debug@^0.7.2: + version "0.7.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" + +debug@^2.1.1, debug@^2.6.3, debug@^2.6.8: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" + dependencies: + ms "2.0.0" + +decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +deep-diff@^0.3.3: + version "0.3.8" + resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" + +deep-equal@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + +deep-equal@~0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-0.2.2.tgz#84b745896f34c684e98f2ce0e42abaf43bba017d" + +deep-extend@~0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +default-require-extensions@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" + dependencies: + strip-bom "^2.0.0" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + dependencies: + globby "^5.0.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + rimraf "^2.2.8" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +depd@1.1.0, depd@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + +detect-file@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-0.1.0.tgz#4935dedfd9488648e006b0129566e9386711ea63" + dependencies: + fs-exists-sync "^0.1.0" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + +diff-match-patch@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.0.tgz#1cc3c83a490d67f95d91e39f6ad1f2e086b63048" + +diff@^3.0.0, diff@^3.0.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" + +disposables@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/disposables/-/disposables-1.0.1.tgz#064727a25b54f502bd82b89aa2dfb8df9f1b39e3" + +dnd-core@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-2.4.0.tgz#c4a5bc2aea75164f8a295d769d5f551810e7d411" + dependencies: + asap "^2.0.3" + invariant "^2.0.0" + lodash "^4.2.0" + redux "^3.2.0" + +doctrine@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +dom-helpers@^3.0.0, dom-helpers@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a" + +dot-prop@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.1.1.tgz#a8493f0b7b5eeec82525b5c7587fa7de7ca859c1" + dependencies: + is-obj "^1.0.0" + +dotenv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-2.0.0.tgz#bd759c357aaa70365e01c96b7b0bec08a6e0d949" + +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +element-class@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/element-class/-/element-class-0.2.2.tgz#9d3bbd0767f9013ef8e1c8ebe722c1402a60050e" + +empower-core@^0.6.1: + version "0.6.2" + resolved "https://registry.yarnpkg.com/empower-core/-/empower-core-0.6.2.tgz#5adef566088e31fba80ba0a36df47d7094169144" + dependencies: + call-signature "0.0.2" + core-js "^2.0.0" + +encodeurl@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" + +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" + +equal-length@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/equal-length/-/equal-length-1.0.1.tgz#21ca112d48ab24b4e1e7ffc0e5339d31fdfc274c" + +error-ex@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + +errorhandler@^1.4.3: + version "1.5.0" + resolved "https://registry.yarnpkg.com/errorhandler/-/errorhandler-1.5.0.tgz#eaba64ca5d542a311ac945f582defc336165d9f4" + dependencies: + accepts "~1.3.3" + escape-html "~1.0.3" + +es6-error@^4.0.1, es6-error@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.0.2.tgz#eec5c726eacef51b7f6b73c20db6e1b13b069c98" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.4, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +eslint-config-finn-prettier@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-finn-prettier/-/eslint-config-finn-prettier-2.0.0.tgz#b7f6d56f66075f29440b1545265ebcdbcb98a614" + dependencies: + eslint-config-prettier "^2.0.0" + eslint-plugin-prettier "^2.0.1" + +eslint-config-finn@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/eslint-config-finn/-/eslint-config-finn-1.0.2.tgz#52e1b8a6af5593031dbf3337958863ec9e5c6fd9" + dependencies: + eslint-config-schibsted "^3.1.0" + +eslint-config-prettier@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-2.2.0.tgz#ca47663852789a75c10feba673e802cc1eff085f" + dependencies: + get-stdin "^5.0.1" + +eslint-config-schibsted@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-schibsted/-/eslint-config-schibsted-3.1.0.tgz#f6e3767434be7909dcc8f118a089bfdae1ae034c" + +eslint-plugin-prettier@^2.0.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.1.2.tgz#4b90f4ee7f92bfbe2e926017e1ca40eb628965ea" + dependencies: + fast-diff "^1.1.1" + jest-docblock "^20.0.1" + +eslint-scope@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.0.0.tgz#7277c01437fdf41dccd168d5aa0e49b75ca1f260" + dependencies: + babel-code-frame "^6.22.0" + chalk "^1.1.3" + concat-stream "^1.6.0" + debug "^2.6.8" + doctrine "^2.0.0" + eslint-scope "^3.7.1" + espree "^3.4.3" + esquery "^1.0.0" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + glob "^7.1.2" + globals "^9.17.0" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^3.0.6" + is-my-json-valid "^2.16.0" + is-resolvable "^1.0.0" + js-yaml "^3.8.4" + json-stable-stringify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^4.0.0" + progress "^2.0.0" + require-uncached "^1.0.3" + strip-json-comments "~2.0.1" + table "^4.0.1" + text-table "~0.2.0" + +espower-location-detector@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/espower-location-detector/-/espower-location-detector-1.0.0.tgz#a17b7ecc59d30e179e2bef73fb4137704cb331b5" + dependencies: + is-url "^1.2.1" + path-is-absolute "^1.0.0" + source-map "^0.5.0" + xtend "^4.0.0" + +espree@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.3.tgz#2910b5ccd49ce893c2ffffaab4fd8b3a31b82374" + dependencies: + acorn "^5.0.1" + acorn-jsx "^3.0.0" + +esprima@^2.6.0: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + +esprima@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + +espurify@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/espurify/-/espurify-1.7.0.tgz#1c5cf6cbccc32e6f639380bd4f991fab9ba9d226" + dependencies: + core-js "^2.0.0" + +esquery@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + dependencies: + estraverse "^4.1.0" + object-assign "^4.0.1" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +etag@~1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051" + +execa@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.4.0.tgz#4eb6467a36a095fabb2970ff9d5e3fb7bce6ebc3" + dependencies: + cross-spawn-async "^2.1.1" + is-stream "^1.1.0" + npm-run-path "^1.0.0" + object-assign "^4.0.1" + path-key "^1.0.0" + strip-eof "^1.0.0" + +execa@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.5.1.tgz#de3fb85cb8d6e91c85bcbceb164581785cb57b36" + dependencies: + cross-spawn "^4.0.0" + get-stream "^2.2.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" + +exenv@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.0.tgz#3835f127abf075bfe082d0aed4484057c78e3c89" + +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + dependencies: + is-posix-bracket "^0.1.0" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" + +expand-tilde@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" + dependencies: + os-homedir "^1.0.1" + +express-validator@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/express-validator/-/express-validator-3.1.2.tgz#bae23dfa11191398d5fcada3dd7c54f28428195a" + dependencies: + "@types/bluebird" "~3.0.36" + "@types/express" "~4.0.34" + bluebird "^3.4.0" + lodash "^4.16.0" + validator "~6.2.0" + +express@^4.14.1: + version "4.15.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.15.3.tgz#bab65d0f03aa80c358408972fc700f916944b662" + dependencies: + accepts "~1.3.3" + array-flatten "1.1.1" + content-disposition "0.5.2" + content-type "~1.0.2" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.7" + depd "~1.1.0" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.0" + finalhandler "~1.0.3" + fresh "0.5.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.1" + path-to-regexp "0.1.7" + proxy-addr "~1.1.4" + qs "6.4.0" + range-parser "~1.2.0" + send "0.15.3" + serve-static "1.12.3" + setprototypeof "1.0.3" + statuses "~1.3.1" + type-is "~1.6.15" + utils-merge "1.0.0" + vary "~1.1.1" + +extend@^3.0.0, extend@~3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +external-editor@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.4.tgz#1ed9199da9cbfe2ef2f7a31b2fde8b0d12368972" + dependencies: + iconv-lite "^0.4.17" + jschardet "^1.4.2" + tmp "^0.0.31" + +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + dependencies: + is-extglob "^1.0.0" + +extsprintf@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" + +eyes@0.1.x: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" + +fast-diff@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.1.tgz#0aea0e4e605b6a2189f0e936d4b7fbaf1b7cfd9b" + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + +fbjs@^0.8.9: + version "0.8.12" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" + 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@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + +fill-keys@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" + dependencies: + is-object "~1.0.1" + merge-descriptors "~1.0.0" + +fill-range@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^1.1.3" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +final-fs@^1.6.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/final-fs/-/final-fs-1.6.1.tgz#d6dcd92ef6fe4fe8c07abd568c7135610ede3236" + dependencies: + node-fs "~0.1.5" + when "~2.0.1" + +finalhandler@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89" + dependencies: + debug "2.6.7" + encodeurl "~1.0.1" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.1" + statuses "~1.3.1" + unpipe "~1.0.0" + +find-cache-dir@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" + dependencies: + commondir "^1.0.1" + mkdirp "^0.5.1" + pkg-dir "^1.0.0" + +find-up@^1.0.0, find-up@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + +findup-sync@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" + dependencies: + detect-file "^0.1.0" + is-glob "^2.0.1" + micromatch "^2.3.7" + resolve-dir "^0.1.0" + +flagged-respawn@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-0.3.2.tgz#ff191eddcd7088a675b2610fffc976be9b8074b5" + +flat-cache@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + +fn-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" + +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + dependencies: + for-in "^1.0.1" + +foreground-child@^1.3.3, foreground-child@^1.5.3: + version "1.5.6" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-1.5.6.tgz#4fd71ad2dfde96789b980a5c0a295937cb2f5ce9" + dependencies: + cross-spawn "^4" + signal-exit "^3.0.0" + +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.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.2.0.tgz#9a5e3b9295f980b2623cf64fa238b14cebca707b" + 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" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +formatio@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.1.1.tgz#5ed3ccd636551097383465d996199100e86161e9" + dependencies: + samsam "~1.1" + +formidable@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" + +forwarded@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363" + +fresh@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e" + +fs-exists-sync@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +fsevents@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" + dependencies: + nan "^2.3.0" + node-pre-gyp "^0.6.36" + +fstream-ignore@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" + dependencies: + fstream "^1.0.0" + inherits "2" + minimatch "^3.0.0" + +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +generate-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + dependencies: + is-property "^1.0.0" + +generic-pool@2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.2.tgz#886bc5bf0beb7db96e81bcbba078818de5a62683" + +generic-pool@2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff" + +generic-pool@^2.4.2: + version "2.5.4" + resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-2.5.4.tgz#38c6188513e14030948ec6e5cf65523d9779299b" + +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-port@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.1.0.tgz#ef01b18a84ca6486970ff99e54446141a73ffd3e" + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + +get-stdin@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" + +get-stream@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" + dependencies: + object-assign "^4.0.1" + pinkie-promise "^2.0.0" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + +glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" + dependencies: + global-prefix "^0.1.4" + is-windows "^0.2.0" + +global-prefix@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" + dependencies: + homedir-polyfill "^1.0.0" + ini "^1.3.4" + is-windows "^0.2.0" + which "^1.2.12" + +globals@^9.0.0, globals@^9.17.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + dependencies: + array-union "^1.0.1" + arrify "^1.0.0" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +globby@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +got@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" + dependencies: + create-error-class "^3.0.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + is-redirect "^1.0.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + lowercase-keys "^1.0.0" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + unzip-response "^2.0.1" + url-parse-lax "^1.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + +handlebars@^4.0.3: + version "4.0.10" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + +har-validator@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" + dependencies: + chalk "^1.1.1" + commander "^2.9.0" + is-my-json-valid "^2.12.4" + pinkie-promise "^2.0.0" + +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-color@~0.1.0: + version "0.1.7" + resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f" + +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-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +has-yarn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-1.0.0.tgz#89e25db604b725c8f5976fff0addc921b828a5a7" + +hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + +history@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/history/-/history-3.3.0.tgz#fcedcce8f12975371545d735461033579a6dae9c" + dependencies: + invariant "^2.2.1" + loose-envify "^1.2.0" + query-string "^4.2.2" + warning "^3.0.0" + +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + +hoek@4.x.x: + version "4.1.1" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.1.1.tgz#9cc573ffba2b7b408fb5e9c2a13796be94cddce9" + +hoist-non-react-statics@^1.0.3, hoist-non-react-statics@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +homedir-polyfill@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" + dependencies: + parse-passwd "^1.0.0" + +hosted-git-info@^2.1.4: + version "2.4.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.2.tgz#0076b9f46a270506ddbaaea56496897460612a67" + +http-errors@~1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" + dependencies: + depd "1.1.0" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +hullabaloo-config-manager@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/hullabaloo-config-manager/-/hullabaloo-config-manager-1.1.1.tgz#1d9117813129ad035fd9e8477eaf066911269fe3" + dependencies: + dot-prop "^4.1.0" + es6-error "^4.0.2" + graceful-fs "^4.1.11" + indent-string "^3.1.0" + json5 "^0.5.1" + lodash.clonedeep "^4.5.0" + lodash.clonedeepwith "^4.5.0" + lodash.isequal "^4.5.0" + lodash.merge "^4.6.0" + md5-hex "^2.0.0" + package-hash "^2.0.0" + pkg-dir "^2.0.0" + resolve-from "^3.0.0" + safe-buffer "^5.0.1" + +i@0.3.x: + version "0.3.5" + resolved "https://registry.yarnpkg.com/i/-/i-0.3.5.tgz#1d2b854158ec8169113c6cb7f6b6801e99e211d5" + +iconv-lite@0.4.15: + version "0.4.15" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" + +iconv-lite@^0.4.17, iconv-lite@~0.4.13: + version "0.4.18" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" + +ignore-by-default@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + +ignore@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d" + +immutability-helper@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.2.2.tgz#e7e9da728b3de2fad34a216f4157b326dbccc892" + dependencies: + invariant "^2.2.0" + +immutable@^3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" + +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + +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, indent-string@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.1.0.tgz#08ff4334603388399b329e6b9538dc7a3cf5de7d" + +inflection@^1.10.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + +ini@^1.3.4, ini@~1.3.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" + +inquirer@^3.0.6: + version "3.1.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.1.1.tgz#87621c4fba4072f48a8dd71c9f9df6f100b2d534" + dependencies: + ansi-escapes "^2.0.0" + chalk "^1.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.0.0" + strip-ansi "^3.0.0" + through "^2.3.6" + +install@^0.8.1: + version "0.8.9" + resolved "https://registry.yarnpkg.com/install/-/install-0.8.9.tgz#9f4b5c0d1851ef872e9df85e4f7162d4e5dcdbed" + +interpret@^0.6.5: + version "0.6.6" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-0.6.6.tgz#fecd7a18e7ce5ca6abfb953e1f86213a49f1625b" + +invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.0, invariant@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" + dependencies: + loose-envify "^1.0.0" + +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.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" + +irregular-plurals@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.2.0.tgz#38f299834ba8c00c30be9c554e137269752ff3ac" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-ci@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" + dependencies: + ci-info "^1.0.0" + +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-error@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-error/-/is-error-2.2.1.tgz#684a96d84076577c98f4cdb40c6d26a5123bf19c" + +is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + +is-finite@^1.0.0, is-finite@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-generator-fn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-1.0.0.tgz#969d49e1bb3329f6bb7f09089be26578b2ddd46a" + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" + +is-my-json-valid@^2.12.4, is-my-json-valid@^2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + jsonpointer "^4.0.0" + xtend "^4.0.0" + +is-npm@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" + +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + +is-object@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" + +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-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + dependencies: + path-is-inside "^1.0.1" + +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + +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" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-property@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + +is-redirect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" + +is-resolvable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" + dependencies: + tryit "^1.0.1" + +is-retry-allowed@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" + +is-stream@^1.0.0, 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" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +is-url@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.2.tgz#498905a593bf47cc2d9e7f738372bbf7696c7f26" + +is-utf8@^0.2.0, is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +is-windows@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isemail@2.x.x: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isemail/-/isemail-2.2.1.tgz#0353d3d9a62951080c262c2aa0a42b8ea8e9e2a6" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + 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.x, isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +istanbul-lib-coverage@^1.1.0, 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-hook@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.7.tgz#dd6607f03076578fe7d6f2a630cf143b49bacddc" + dependencies: + append-transform "^0.4.0" + +istanbul-lib-instrument@^1.7.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.2.tgz#6014b03d3470fb77638d5802508c255c06312e56" + dependencies: + babel-generator "^6.18.0" + babel-template "^6.16.0" + babel-traverse "^6.18.0" + babel-types "^6.18.0" + babylon "^6.13.0" + istanbul-lib-coverage "^1.1.1" + semver "^5.3.0" + +istanbul-lib-report@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#f0e55f56655ffa34222080b7a0cd4760e1405fc9" + dependencies: + istanbul-lib-coverage "^1.1.1" + mkdirp "^0.5.1" + path-parse "^1.0.5" + supports-color "^3.1.2" + +istanbul-lib-source-maps@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.1.tgz#a6fe1acba8ce08eebc638e572e294d267008aa0c" + dependencies: + debug "^2.6.3" + istanbul-lib-coverage "^1.1.1" + mkdirp "^0.5.1" + rimraf "^2.6.1" + source-map "^0.5.3" + +istanbul-reports@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.1.tgz#042be5c89e175bc3f86523caab29c014e77fee4e" + dependencies: + handlebars "^4.0.3" + +items@2.x.x: + version "2.1.1" + resolved "https://registry.yarnpkg.com/items/-/items-2.1.1.tgz#8bd16d9c83b19529de5aea321acaada78364a198" + +jest-diff@19.0.0, jest-diff@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-19.0.0.tgz#d1563cfc56c8b60232988fbc05d4d16ed90f063c" + dependencies: + chalk "^1.1.3" + diff "^3.0.0" + jest-matcher-utils "^19.0.0" + pretty-format "^19.0.0" + +jest-docblock@^20.0.1: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.3.tgz#17bea984342cc33d83c50fbe1545ea0efaa44712" + +jest-file-exists@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/jest-file-exists/-/jest-file-exists-19.0.0.tgz#cca2e587a11ec92e24cfeab3f8a94d657f3fceb8" + +jest-matcher-utils@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-19.0.0.tgz#5ecd9b63565d2b001f61fbf7ec4c7f537964564d" + dependencies: + chalk "^1.1.3" + pretty-format "^19.0.0" + +jest-message-util@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-19.0.0.tgz#721796b89c0e4d761606f9ba8cb828a3b6246416" + dependencies: + chalk "^1.1.1" + micromatch "^2.3.11" + +jest-mock@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-19.0.0.tgz#67038641e9607ab2ce08ec4a8cb83aabbc899d01" + +jest-snapshot@19.0.2: + version "19.0.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-19.0.2.tgz#9c1b216214f7187c38bfd5c70b1efab16b0ff50b" + dependencies: + chalk "^1.1.3" + jest-diff "^19.0.0" + jest-file-exists "^19.0.0" + jest-matcher-utils "^19.0.0" + jest-util "^19.0.2" + natural-compare "^1.4.0" + pretty-format "^19.0.0" + +jest-util@^19.0.2: + version "19.0.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-19.0.2.tgz#e0a0232a2ab9e6b2b53668bdb3534c2b5977ed41" + dependencies: + chalk "^1.1.1" + graceful-fs "^4.1.6" + jest-file-exists "^19.0.0" + jest-message-util "^19.0.0" + jest-mock "^19.0.0" + jest-validate "^19.0.2" + leven "^2.0.0" + mkdirp "^0.5.1" + +jest-validate@^19.0.2: + version "19.0.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-19.0.2.tgz#dc534df5f1278d5b63df32b14241d4dbf7244c0c" + dependencies: + chalk "^1.1.1" + jest-matcher-utils "^19.0.0" + leven "^2.0.0" + pretty-format "^19.0.0" + +joi@^10.0.1: + version "10.6.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-10.6.0.tgz#52587f02d52b8b75cdb0c74f0b164a191a0e1fc2" + dependencies: + hoek "4.x.x" + isemail "2.x.x" + items "2.x.x" + topo "2.x.x" + +js-tokens@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" + +js-yaml@3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30" + dependencies: + argparse "^1.0.7" + esprima "^2.6.0" + +js-yaml@^3.8.2, js-yaml@^3.8.4: + version "3.8.4" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.4.tgz#520b4564f86573ba96662af85a8cafa7b4b5a6f6" + dependencies: + argparse "^1.0.7" + esprima "^3.1.1" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +jschardet@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.4.2.tgz#2aa107f142af4121d145659d44f50830961e699a" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json5@^0.5.0, json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsonpointer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" + +jsprim@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" + dependencies: + assert-plus "1.0.0" + extsprintf "1.0.2" + json-schema "0.2.3" + verror "1.3.6" + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + +knex@^0.12.6: + version "0.12.9" + resolved "https://registry.yarnpkg.com/knex/-/knex-0.12.9.tgz#aa852138c09ed46181e890fd698270bbe7761124" + dependencies: + babel-runtime "^6.11.6" + bluebird "^3.4.6" + chalk "^1.0.0" + commander "^2.2.0" + debug "^2.1.3" + generic-pool "^2.4.2" + inherits "~2.0.1" + interpret "^0.6.5" + liftoff "~2.2.0" + lodash "^4.6.0" + minimist "~1.1.0" + mkdirp "^0.5.0" + pg-connection-string "^0.1.3" + readable-stream "^1.1.12" + safe-buffer "^5.0.1" + tildify "~1.0.0" + uuid "^3.0.0" + v8flags "^2.0.2" + +last-line-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/last-line-stream/-/last-line-stream-1.0.0.tgz#d1b64d69f86ff24af2d04883a2ceee14520a5600" + dependencies: + through2 "^2.0.0" + +latest-version@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" + dependencies: + package-json "^4.0.0" + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + dependencies: + invert-kv "^1.0.0" + +lcov-parse@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3" + +leven@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +liftoff@~2.2.0: + version "2.2.5" + resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.2.5.tgz#998c2876cff484b103e4423b93d356da44734c91" + dependencies: + extend "^3.0.0" + findup-sync "^0.4.2" + flagged-respawn "^0.3.2" + rechoir "^0.6.2" + resolve "^1.1.7" + +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" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash-es@^4.2.1: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7" + +lodash.assign@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + +lodash.clonedeepwith@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeepwith/-/lodash.clonedeepwith-4.5.0.tgz#6ee30573a03a1a60d670a62ef33c10cf1afdbdd4" + +lodash.debounce@^4.0.3: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + +lodash.defaults@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + +lodash.difference@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + +lodash.flatten@^4.2.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + +lodash.isequal@^4.4.0, lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + +lodash.merge@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" + +lodash@^4.0.0, lodash@^4.14.0, lodash@^4.16.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.0: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + +log-driver@1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056" + +log4js@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/log4js/-/log4js-1.1.1.tgz#c21d29c7604089e4f255833e7f94b3461de1ff43" + dependencies: + debug "^2.2.0" + semver "^5.3.0" + streamroller "^0.4.0" + +lolex@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31" + +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.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: + js-tokens "^3.0.0" + +loud-rejection@^1.0.0, loud-rejection@^1.2.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +lowercase-keys@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" + +lru-cache@^4.0.0, lru-cache@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +make-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978" + dependencies: + pify "^2.3.0" + +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + +matcher@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-0.1.2.tgz#ef20cbde64c24c50cc61af5b83ee0b1b8ff00101" + dependencies: + escape-string-regexp "^1.0.4" + +md5-hex@^1.2.0, md5-hex@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-1.3.0.tgz#d2c4afe983c4370662179b8cad145219135046c4" + dependencies: + md5-o-matic "^0.1.1" + +md5-hex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-2.0.0.tgz#d0588e9f1c74954492ecd24ac0ac6ce997d92e33" + dependencies: + md5-o-matic "^0.1.1" + +md5-o-matic@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/md5-o-matic/-/md5-o-matic-0.1.1.tgz#822bccd65e117c514fab176b25945d54100a03c3" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + +meow@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + +merge-descriptors@1.0.1, merge-descriptors@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + +merge-source-map@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.0.4.tgz#a5de46538dae84d4114cc5ea02b4772a6346701f" + dependencies: + source-map "^0.5.6" + +methods@^1.1.1, methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + +micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +mime-db@~1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" + +mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7: + version "2.1.15" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" + dependencies: + mime-db "~1.27.0" + +mime@1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" + +mime@^1.3.4: + version "1.3.6" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" + +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + +minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8, minimist@~0.0.1: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@1.2.0, minimist@^1.1.3, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +minimist@~1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8" + +mkdirp@0.x.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +module-not-found-error@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" + +moment@^2.14.1, moment@^2.15.2: + version "2.18.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" + +"mongodb-uri@>= 0.9.7": + version "0.9.7" + resolved "https://registry.yarnpkg.com/mongodb-uri/-/mongodb-uri-0.9.7.tgz#0f771ad16f483ae65f4287969428e9fbc4aa6181" + +ms@0.7.2, ms@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +multimatch@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" + dependencies: + array-differ "^1.0.0" + array-union "^1.0.1" + arrify "^1.0.0" + minimatch "^3.0.0" + +mute-stream@0.0.7, mute-stream@~0.0.4: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + +nan@^2.3.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + +ncp@1.0.x: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ncp/-/ncp-1.0.1.tgz#d15367e5cb87432ba117d2bf80fdf45aecfb4246" + +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: + version "1.7.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +node-fs@~0.1.5: + version "0.1.7" + resolved "https://registry.yarnpkg.com/node-fs/-/node-fs-0.1.7.tgz#32323cccb46c9fbf0fc11812d45021cc31d325bb" + +node-pre-gyp@^0.6.36: + version "0.6.36" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786" + dependencies: + mkdirp "^0.5.1" + nopt "^4.0.1" + npmlog "^4.0.2" + rc "^1.1.7" + request "^2.81.0" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^2.2.1" + tar-pack "^3.4.0" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: + version "2.3.8" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.8.tgz#d819eda2a9dedbd1ffa563ea4071d936782295bb" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +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" + +normalize.css@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-5.0.0.tgz#7cec875ce8178a5333c4de80b68ea9c18b9d7c37" + +npm-run-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-1.0.0.tgz#f5c32bf595fe81ae927daec52e82f8b000ac3c8f" + dependencies: + path-key "^1.0.0" + +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" + +npmlog@^4.0.2: + version "4.1.0" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.0.tgz#dc59bee85f64f00ed424efb2af0783df25d1c0b5" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +nyc@^10.1.2: + version "10.3.2" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-10.3.2.tgz#f27f4d91f2a9db36c24f574ff5c6efff0233de46" + dependencies: + archy "^1.0.0" + arrify "^1.0.1" + caching-transform "^1.0.0" + convert-source-map "^1.3.0" + debug-log "^1.0.1" + default-require-extensions "^1.0.0" + find-cache-dir "^0.1.1" + find-up "^1.1.2" + foreground-child "^1.5.3" + glob "^7.0.6" + istanbul-lib-coverage "^1.1.0" + istanbul-lib-hook "^1.0.6" + istanbul-lib-instrument "^1.7.1" + istanbul-lib-report "^1.1.0" + istanbul-lib-source-maps "^1.2.0" + istanbul-reports "^1.1.0" + md5-hex "^1.2.0" + merge-source-map "^1.0.2" + micromatch "^2.3.11" + mkdirp "^0.5.0" + resolve-from "^2.0.0" + rimraf "^2.5.4" + signal-exit "^3.0.1" + spawn-wrap "1.2.4" + test-exclude "^4.1.0" + yargs "^7.1.0" + yargs-parser "^5.0.0" + +oauth-sign@~0.8.1: + 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: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +observable-to-promise@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/observable-to-promise/-/observable-to-promise-0.5.0.tgz#c828f0f0dc47e9f86af8a4977c5d55076ce7a91f" + dependencies: + is-observable "^0.2.0" + symbol-observable "^1.0.4" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" + +once@^1.3.0, once@^1.3.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + +optimist@^0.6.1, optimist@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +option-chain@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/option-chain/-/option-chain-0.1.1.tgz#e9b811e006f1c0f54802f28295bfc8970f8dcfbd" + dependencies: + object-assign "^4.0.1" + +optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +os-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" + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + dependencies: + lcid "^1.0.0" + +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: + version "1.0.2" + 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" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-finally@^1.0.0: + version "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" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +package-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-1.2.0.tgz#003e56cd57b736a6ed6114cc2b81542672770e44" + dependencies: + md5-hex "^1.3.0" + +package-hash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-2.0.0.tgz#78ae326c89e05a4d813b68601977af05c00d2a0d" + dependencies: + graceful-fs "^4.1.11" + lodash.flattendeep "^4.4.0" + md5-hex "^2.0.0" + release-zalgo "^1.0.0" + +package-json@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" + dependencies: + got "^6.7.1" + registry-auth-token "^3.0.1" + registry-url "^3.0.3" + semver "^5.1.0" + +packet-reader@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-0.2.0.tgz#819df4d010b82d5ea5671f8a1a3acf039bcd7700" + +packet-reader@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-0.3.1.tgz#cd62e60af8d7fea8a705ec4ff990871c46871f27" + +parse-database-url@^0.3.0, parse-database-url@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/parse-database-url/-/parse-database-url-0.3.0.tgz#369666321e927c9ade63cdfc1aaaf6fb37453d0d" + dependencies: + mongodb-uri ">= 0.9.7" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +parse-ms@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-0.1.2.tgz#dd3fa25ed6c2efc7bdde12ad9b46c163aa29224e" + +parse-ms@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-1.0.1.tgz#56346d4749d78f23430ca0c713850aef91aa361d" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + +parseurl@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-is-inside@^1.0.1, path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + +path-key@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-1.0.0.tgz#5d53d578019646c0d68800db4e146e6bdc2ac7af" + +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + +pg-connection-string@0.1.3, pg-connection-string@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" + +pg-pool@1.*: + version "1.8.0" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-1.8.0.tgz#f7ec73824c37a03f076f51bfdf70e340147c4f37" + dependencies: + generic-pool "2.4.3" + object-assign "4.1.0" + +pg-types@1.*: + version "1.12.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-1.12.0.tgz#8ad3b7b897e3fd463e62de241ad5fc640b4a66f0" + dependencies: + ap "~0.2.0" + postgres-array "~1.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.0" + postgres-interval "^1.1.0" + +pg@^4.5.5: + version "4.5.6" + resolved "https://registry.yarnpkg.com/pg/-/pg-4.5.6.tgz#a1de4878afa5cb208a25815f5ff89d2e0ebf52f8" + dependencies: + buffer-writer "1.0.1" + generic-pool "2.4.2" + packet-reader "0.2.0" + pg-connection-string "0.1.3" + pg-types "1.*" + pgpass "0.0.3" + semver "^4.1.0" + +pg@^6.1.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/pg/-/pg-6.4.0.tgz#cb76ba2e7c2eab89fc64bf7a9fe648ced72436dc" + dependencies: + buffer-writer "1.0.1" + packet-reader "0.3.1" + pg-connection-string "0.1.3" + pg-pool "1.*" + pg-types "1.*" + pgpass "1.x" + semver "4.3.2" + +pgpass@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-0.0.3.tgz#12e67e343b3189c2f31206ebc9cc0befffcf9140" + dependencies: + split "~0.3" + +pgpass@1.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.2.tgz#2a7bb41b6065b67907e91da1b07c1847c877b306" + dependencies: + split "^1.0.0" + +pify@^2.0.0, pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pinkie-promise@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-1.0.0.tgz#d1da67f5482563bb7cf57f286ae2822ecfbf3670" + dependencies: + pinkie "^1.0.0" + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-1.0.0.tgz#5a47f28ba1015d0201bda7bf0f358e47bec8c7e4" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pkg-conf@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.0.0.tgz#071c87650403bccfb9c627f58751bfe47c067279" + dependencies: + find-up "^2.0.0" + load-json-file "^2.0.0" + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + dependencies: + find-up "^2.1.0" + +pkginfo@0.3.x: + version "0.3.1" + resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21" + +pkginfo@0.x.x, pkginfo@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.0.tgz#349dbb7ffd38081fcadc0853df687f0c7744cd65" + +plur@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/plur/-/plur-1.0.0.tgz#db85c6814f5e5e5a3b49efc28d604fec62975156" + +plur@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/plur/-/plur-2.1.2.tgz#7482452c1a0f508e3e344eaec312c91c29dc655a" + dependencies: + irregular-plurals "^1.0.0" + +pluralize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-4.0.0.tgz#59b708c1c0190a2f692f1c7618c446b052fd1762" + +postgres-array@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-1.0.2.tgz#8e0b32eb03bf77a5c0a7851e0441c169a256a238" + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + +postgres-date@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.3.tgz#e2d89702efdb258ff9d9cee0fe91bd06975257a8" + +postgres-interval@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.1.0.tgz#1031e7bac34564132862adc9eb6c6d2f3aa75bb4" + dependencies: + xtend "^4.0.0" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + +prepend-http@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + +prettier@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.4.4.tgz#a8d1447b14c9bf67e6d420dcadd10fb9a4fad65a" + +pretty-format@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-19.0.0.tgz#56530d32acb98a3fa4851c4e2b9d37b420684c84" + dependencies: + ansi-styles "^3.0.0" + +pretty-ms@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-0.2.2.tgz#da879a682ff33a37011046f13d627f67c73b84f6" + dependencies: + parse-ms "^0.1.0" + +pretty-ms@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-2.1.0.tgz#4257c256df3fb0b451d6affaab021884126981dc" + dependencies: + is-finite "^1.0.1" + parse-ms "^1.0.0" + plur "^1.0.0" + +private@^0.1.6: + version "0.1.7" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" + +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + +prom-client@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-7.2.0.tgz#0d65ae8a0f7a10d4afbedba4856a6e55e22a447c" + dependencies: + tdigest "^0.1.1" + util-extend "^1.0.1" + +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" + +prompt@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prompt/-/prompt-1.0.0.tgz#8e57123c396ab988897fb327fd3aedc3e735e4fe" + dependencies: + colors "^1.1.2" + pkginfo "0.x.x" + read "1.0.x" + revalidator "0.1.x" + utile "0.3.x" + winston "2.1.x" + +prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8: + version "15.5.10" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + +proxy-addr@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3" + dependencies: + forwarded "~0.1.0" + ipaddr.js "1.3.0" + +proxyquire@^1.7.11: + version "1.8.0" + resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-1.8.0.tgz#02d514a5bed986f04cbb2093af16741535f79edc" + dependencies: + fill-keys "^1.0.2" + module-not-found-error "^1.0.0" + resolve "~1.1.7" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +qs@6.4.0, qs@^6.1.0, qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + +qs@~6.3.0: + version "6.3.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" + +query-string@^4.2.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + +randomatic@^1.1.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +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.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96" + dependencies: + bytes "2.4.0" + iconv-lite "0.4.15" + unpipe "1.0.0" + +rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: + version "1.2.1" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-addons-css-transition-group@^15.3.1: + version "15.6.0" + resolved "https://registry.yarnpkg.com/react-addons-css-transition-group/-/react-addons-css-transition-group-15.6.0.tgz#69887cf6e4874d25cd66e22a699e29f0d648aba0" + dependencies: + react-transition-group "^1.2.0" + +react-dnd-html5-backend@^2.1.2: + version "2.4.1" + resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-2.4.1.tgz#439d2bcaf8bd8b87a51386beb51c128826182ddd" + dependencies: + lodash "^4.2.0" + +react-dnd@^2.1.4: + version "2.4.0" + resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-2.4.0.tgz#96f0042cd4cd375b4f0c3413f6ec84d267b7d792" + dependencies: + disposables "^1.0.1" + dnd-core "^2.4.0" + hoist-non-react-statics "^1.2.0" + invariant "^2.1.0" + lodash "^4.2.0" + prop-types "^15.5.8" + +react-dom-factories@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/react-dom-factories/-/react-dom-factories-1.0.0.tgz#f43c05e5051b304f33251618d5bc859b29e46b6d" + +react-dom@^15.3.1: + version "15.6.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.1.tgz#2cb0ed4191038e53c209eb3a79a23e2a4cf99470" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + +react-mdl@^1.9.0: + version "1.10.3" + resolved "https://registry.yarnpkg.com/react-mdl/-/react-mdl-1.10.3.tgz#f783e26a5eea4154a32129ab2562c09d5eeacf0d" + dependencies: + clamp "^1.0.1" + classnames "^2.2.3" + lodash.isequal "^4.4.0" + prop-types "^15.5.0" + +react-modal@^1.6.4: + version "1.9.7" + resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-1.9.7.tgz#07ef56790b953e3b98ef1e2989e347983c72871d" + dependencies: + create-react-class "^15.5.2" + element-class "^0.2.0" + exenv "1.2.0" + lodash.assign "^4.2.0" + prop-types "^15.5.7" + react-dom-factories "^1.0.0" + +react-redux@^4.4.5: + version "4.4.8" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-4.4.8.tgz#e7bc1dd100e8b64e96ac8212db113239b9e2e08f" + dependencies: + create-react-class "^15.5.1" + hoist-non-react-statics "^1.0.3" + invariant "^2.0.0" + lodash "^4.2.0" + loose-envify "^1.1.0" + prop-types "^15.5.4" + +react-router-scroll@^0.4.1: + version "0.4.2" + resolved "https://registry.yarnpkg.com/react-router-scroll/-/react-router-scroll-0.4.2.tgz#4b90e8707edf96eba7f066d94c5b4338bd6848b7" + dependencies: + prop-types "^15.5.6" + scroll-behavior "^0.9.3" + warning "^3.0.0" + +react-router@^3.0.0: + version "3.0.5" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.0.5.tgz#c3b7873758045a8bbc9562aef4ff4bc8cce7c136" + dependencies: + create-react-class "^15.5.1" + history "^3.0.0" + hoist-non-react-statics "^1.2.0" + invariant "^2.2.1" + loose-envify "^1.2.0" + prop-types "^15.5.6" + warning "^3.0.0" + +react-transition-group@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.0.tgz#b51fc921b0c3835a7ef7c571c79fc82c73e9204f" + dependencies: + chain-function "^1.0.0" + dom-helpers "^3.2.0" + loose-envify "^1.3.1" + prop-types "^15.5.6" + warning "^3.0.0" + +react@^15.3.1: + version "15.6.1" + resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df" + dependencies: + create-react-class "^15.6.0" + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +read@1.0.x: + version "1.0.7" + resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + dependencies: + mute-stream "~0.0.4" + +readable-stream@^1.1.12, readable-stream@^1.1.7: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.2.tgz#5a04df05e4f57fe3f0dc68fdd11dc5c97c7e6f4d" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + safe-buffer "~5.1.0" + string_decoder "~1.0.0" + util-deprecate "~1.0.1" + +readdirp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" + dependencies: + graceful-fs "^4.1.2" + minimatch "^3.0.2" + readable-stream "^2.0.2" + set-immediate-shim "^1.0.1" + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + dependencies: + resolve "^1.1.6" + +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + +redux-thunk@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5" + +redux@^3.2.0, redux@^3.6.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.0.tgz#07a623cafd92eee8abe309d13d16538f6707926f" + dependencies: + lodash "^4.2.1" + lodash-es "^4.2.1" + loose-envify "^1.1.0" + symbol-observable "^1.0.3" + +regenerate@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" + +regenerator-runtime@^0.10.0: + version "0.10.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + +regex-cache@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145" + dependencies: + is-equal-shallow "^0.1.3" + is-primitive "^2.0.0" + +regexpu-core@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +registry-auth-token@^3.0.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.1.tgz#fb0d3289ee0d9ada2cbb52af5dfe66cb070d3006" + dependencies: + rc "^1.1.6" + safe-buffer "^5.0.1" + +registry-url@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" + dependencies: + rc "^1.0.1" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + dependencies: + jsesc "~0.5.0" + +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + dependencies: + es6-error "^4.0.1" + +remove-trailing-separator@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz#69b062d978727ad14dc6b56ba4ab772fd8d70511" + +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +request@2.79.0: + version "2.79.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.11.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~2.0.6" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + qs "~6.3.0" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "~0.4.1" + uuid "^3.0.0" + +request@^2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + +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" + +require-precompiled@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/require-precompiled/-/require-precompiled-0.1.0.tgz#5a1b52eb70ebed43eb982e974c85ab59571e56fa" + +require-uncached@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +resolve-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-1.0.0.tgz#4eaeea41ed040d1702457df64a42b2b07d246f9f" + dependencies: + resolve-from "^2.0.0" + +resolve-dir@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" + dependencies: + expand-tilde "^1.2.2" + global-modules "^0.2.3" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + +resolve-from@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + +resolve@^1.1.6, resolve@^1.1.7: + version "1.3.3" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" + dependencies: + path-parse "^1.0.5" + +resolve@~1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + +response-time@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/response-time/-/response-time-2.3.2.tgz#ffa71bab952d62f7c1d49b7434355fbc68dffc5a" + dependencies: + depd "~1.1.0" + on-headers "~1.0.1" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +revalidator@0.1.x: + version "0.1.8" + resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b" + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + dependencies: + align-text "^0.1.1" + +rimraf@2, rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.3.3, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" + dependencies: + glob "^7.0.5" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + dependencies: + is-promise "^2.1.0" + +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + +safe-buffer@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" + +safe-buffer@^5.0.1, safe-buffer@~5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +samsam@1.1.2, samsam@~1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" + +scroll-behavior@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.3.tgz#e48bcc8af364f3f07176e8dbca3968bd5e71557b" + dependencies: + dom-helpers "^3.0.0" + invariant "^2.2.1" + +semver-diff@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" + dependencies: + semver "^5.0.3" + +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + +semver@4.3.2, semver@^4.1.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" + +send@0.15.3: + version "0.15.3" + resolved "https://registry.yarnpkg.com/send/-/send-0.15.3.tgz#5013f9f99023df50d1bd9892c19e3defd1d53309" + dependencies: + debug "2.6.7" + depd "~1.1.0" + destroy "~1.0.4" + encodeurl "~1.0.1" + escape-html "~1.0.3" + etag "~1.8.0" + fresh "0.5.0" + http-errors "~1.6.1" + mime "1.3.4" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.3.1" + +serve-favicon@^2.3.0: + version "2.4.3" + resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.4.3.tgz#5986b17b0502642b641c21f818b1acce32025d23" + dependencies: + etag "~1.8.0" + fresh "0.5.0" + ms "2.0.0" + parseurl "~1.3.1" + safe-buffer "5.0.1" + +serve-static@1.12.3: + version "1.12.3" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.3.tgz#9f4ba19e2f3030c547f8af99107838ec38d5b1e2" + dependencies: + encodeurl "~1.0.1" + escape-html "~1.0.3" + parseurl "~1.3.1" + send "0.15.3" + +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" + +set-immediate-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + +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" + +signal-exit@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-2.1.2.tgz#375879b1f92ebc3b334480d038dc546a6d558564" + +signal-exit@^3.0.0, signal-exit@^3.0.1, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +sinon@^1.17.7: + version "1.17.7" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" + dependencies: + formatio "1.1.1" + lolex "1.3.2" + samsam "1.1.2" + util ">=0.10.3 <1" + +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" + +slide@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" + +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + +sort-keys@^1.1.1, sort-keys@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + +source-map-support@^0.4.0, source-map-support@^0.4.2: + version "0.4.15" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" + dependencies: + source-map "^0.5.6" + +source-map@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + +spawn-wrap@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.2.4.tgz#920eb211a769c093eebfbd5b0e7a5d2e68ab2e40" + dependencies: + foreground-child "^1.3.3" + mkdirp "^0.5.0" + os-homedir "^1.0.1" + rimraf "^2.3.3" + signal-exit "^2.0.0" + which "^1.2.4" + +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" + dependencies: + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" + +split@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.0.tgz#c4395ce683abcd254bc28fe1dabb6e5c27dcffae" + dependencies: + through "2" + +split@~0.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" + dependencies: + through "2" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + +ssh2-streams@~0.1.15: + version "0.1.19" + resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.1.19.tgz#f80ececc2de1a39e1aa64469851ec32bc96b83f9" + dependencies: + asn1 "~0.2.0" + semver "^5.1.0" + streamsearch "~0.1.2" + +ssh2@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.5.4.tgz#1bf6b6b28c96eaef267f4d6c46a5a2517a599e27" + dependencies: + ssh2-streams "~0.1.15" + +sshpk@^1.7.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + +stack-utils@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620" + +"statuses@>= 1.3.1 < 2", statuses@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + +streamroller@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-0.4.1.tgz#d435bd5974373abd9bd9068359513085106cc05f" + dependencies: + date-format "^0.0.0" + debug "^0.7.2" + mkdirp "^0.5.1" + readable-stream "^1.1.7" + +streamsearch@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^3.0.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +string_decoder@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + +stringstream@~0.0.4: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991" + +strip-bom-buf@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz#1cb45aaf57530f4caf86c7f75179d2c9a51dd572" + dependencies: + is-utf8 "^0.2.1" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + dependencies: + is-utf8 "^0.2.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + dependencies: + get-stdin "^4.0.1" + +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, superagent@^3.5.0: + version "3.5.2" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.5.2.tgz#3361a3971567504c351063abeaae0faa23dbf3f8" + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.0.6" + debug "^2.2.0" + extend "^3.0.0" + form-data "^2.1.1" + formidable "^1.1.1" + methods "^1.1.1" + mime "^1.3.4" + qs "^6.1.0" + 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" + +supervisor@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/supervisor/-/supervisor-0.12.0.tgz#de7e6337015b291851c10f3538c4a7f04917ecc1" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^3.1.2, supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + dependencies: + has-flag "^1.0.0" + +symbol-observable@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40" + +symbol-observable@^1.0.3, symbol-observable@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" + +table@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.1.tgz#a8116c133fac2c61f4a420ab6cdf5c4d61f0e435" + dependencies: + ajv "^4.7.0" + ajv-keywords "^1.0.0" + chalk "^1.1.1" + lodash "^4.0.0" + slice-ansi "0.0.4" + string-width "^2.0.0" + +tar-pack@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" + dependencies: + debug "^2.2.0" + fstream "^1.0.10" + fstream-ignore "^1.0.5" + once "^1.3.3" + readable-stream "^2.1.4" + rimraf "^2.5.1" + tar "^2.2.1" + uid-number "^0.0.6" + +tar@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + +tdigest@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.1.tgz#2e3cb2c39ea449e55d1e6cd91117accca4588021" + dependencies: + bintrees "1.0.1" + +term-size@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-0.1.1.tgz#87360b96396cab5760963714cda0d0cbeecad9ca" + dependencies: + execa "^0.4.0" + +test-exclude@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" + dependencies: + arrify "^1.0.1" + micromatch "^2.3.11" + object-assign "^4.1.0" + read-pkg-up "^1.0.1" + require-main-filename "^1.0.1" + +text-table@^0.2.0, text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + +through2@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + dependencies: + readable-stream "^2.1.5" + xtend "~4.0.1" + +through@2, through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + +tildify@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.0.0.tgz#2a021db5e8fbde0a8f8b4df37adaa8fb1d39d7dd" + dependencies: + user-home "^1.0.0" + +time-require@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/time-require/-/time-require-0.1.2.tgz#f9e12cb370fc2605e11404582ba54ef5ca2b2d98" + dependencies: + chalk "^0.4.0" + date-time "^0.1.1" + pretty-ms "^0.2.1" + text-table "^0.2.0" + +timed-out@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + +tmp@^0.0.31: + version "0.0.31" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" + dependencies: + os-tmpdir "~1.0.1" + +to-fast-properties@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + +topo@2.x.x: + version "2.0.2" + resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182" + dependencies: + hoek "4.x.x" + +tough-cookie@~2.3.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" + dependencies: + punycode "^1.4.1" + +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + +tryit@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tunnel-agent@~0.4.1: + version "0.4.3" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" + +tunnel-ssh@^4.0.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/tunnel-ssh/-/tunnel-ssh-4.1.3.tgz#911216ad79df1009082c5713fed26476ed239bad" + dependencies: + debug "2.6.0" + lodash.defaults "^4.1.0" + ssh2 "0.5.4" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +type-is@~1.6.15: + version "1.6.15" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" + dependencies: + media-typer "0.3.0" + mime-types "~2.1.15" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +ua-parser-js@^0.7.9: + version "0.7.13" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.13.tgz#cd9dd2f86493b3f44dbeeef3780fda74c5ee14be" + +uglify-js@^2.6: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + +uid-number@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" + +uid2@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" + +unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + dependencies: + crypto-random-string "^1.0.0" + +unique-temp-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz#6dce95b2681ca003eebfb304a415f9cbabcc5385" + dependencies: + mkdirp "^0.5.1" + os-tmpdir "^1.0.1" + uid2 "0.0.3" + +unleash-frontend@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-2.2.6.tgz#e135ee5cd77494c6b65c02fa619ed073196d48b7" + dependencies: + debug "^2.2.0" + immutability-helper "^2.0.0" + immutable "^3.8.1" + normalize.css "^5.0.0" + react "^15.3.1" + react-addons-css-transition-group "^15.3.1" + react-dnd "^2.1.4" + react-dnd-html5-backend "^2.1.2" + react-dom "^15.3.1" + react-mdl "^1.9.0" + react-modal "^1.6.4" + react-redux "^4.4.5" + react-router "^3.0.0" + react-router-scroll "^0.4.1" + redux "^3.6.0" + redux-thunk "^2.1.0" + whatwg-fetch "^2.0.0" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + +unzip-response@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" + +update-notifier@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.2.0.tgz#1b5837cf90c0736d88627732b661c138f86de72f" + dependencies: + boxen "^1.0.0" + chalk "^1.0.0" + configstore "^3.0.0" + import-lazy "^2.1.0" + is-npm "^1.0.0" + latest-version "^3.0.0" + semver-diff "^2.0.0" + xdg-basedir "^3.0.0" + +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + dependencies: + prepend-http "^1.0.1" + +user-home@^1.0.0, user-home@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +util-extend@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f" + +"util@>=0.10.3 <1": + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + dependencies: + inherits "2.0.1" + +utile@0.3.x: + version "0.3.0" + resolved "https://registry.yarnpkg.com/utile/-/utile-0.3.0.tgz#1352c340eb820e4d8ddba039a4fbfaa32ed4ef3a" + dependencies: + async "~0.9.0" + deep-equal "~0.2.1" + i "0.3.x" + mkdirp "0.x.x" + ncp "1.0.x" + rimraf "2.x.x" + +utils-merge@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" + +uuid@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + +v8flags@^2.0.2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" + dependencies: + 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" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + +validator@~6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/validator/-/validator-6.2.1.tgz#bc575b78d15beb2e338a665ba9530c7f409ef667" + +vary@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" + +verror@1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" + dependencies: + extsprintf "1.0.2" + +warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" + dependencies: + loose-envify "^1.0.0" + +whatwg-fetch@>=0.10.0, whatwg-fetch@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" + +when@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/when/-/when-2.0.1.tgz#8d872fe15e68424c91b4b724e848e0807dab6642" + +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + +which@^1.2.12, which@^1.2.4, which@^1.2.8, which@^1.2.9: + version "1.2.14" + resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + dependencies: + string-width "^1.0.2" + +widest-line@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-1.0.0.tgz#0c09c85c2a94683d0d7eaf8ee097d564bf0e105c" + dependencies: + string-width "^1.0.1" + +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + +winston@2.1.x: + version "2.1.1" + resolved "https://registry.yarnpkg.com/winston/-/winston-2.1.1.tgz#3c9349d196207fd1bdff9d4bc43ef72510e3a12e" + dependencies: + async "~1.0.0" + colors "1.0.x" + cycle "1.0.x" + eyes "0.1.x" + isstream "0.1.x" + pkginfo "0.3.x" + stack-trace "0.0.x" + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write-file-atomic@^1.1.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + slide "^1.1.5" + +write-file-atomic@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.1.0.tgz#1769f4b551eedce419f0505deae2e26763542d37" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + slide "^1.1.5" + +write-json-file@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.2.0.tgz#51862506bbb3b619eefab7859f1fd6c6d0530876" + dependencies: + detect-indent "^5.0.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + pify "^2.0.0" + sort-keys "^1.1.1" + write-file-atomic "^2.0.0" + +write-pkg@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-2.1.0.tgz#353aa44c39c48c21440f5c08ce6abd46141c9c08" + dependencies: + sort-keys "^1.1.2" + write-json-file "^2.0.0" + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + dependencies: + mkdirp "^0.5.1" + +xdg-basedir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" + +xtend@^4.0.0, xtend@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + +yallist@^2.0.0, yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +yargs-parser@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" + dependencies: + camelcase "^3.0.0" + +yargs-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" + dependencies: + camelcase "^3.0.0" + +yargs@^6.5.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^4.2.0" + +yargs@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^5.0.0" + +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0"