diff --git a/packages/pubsweet-server/.eslintrc b/packages/pubsweet-server/.eslintrc
new file mode 100644
index 0000000000000000000000000000000000000000..0617d0de9fa64ca2cb7c56242a8d082b61148023
--- /dev/null
+++ b/packages/pubsweet-server/.eslintrc
@@ -0,0 +1,10 @@
+{
+  "extends": ["standard", "eslint:recommended"],
+  "env": {
+    "node": true
+  },
+  "parserOptions": {
+    "ecmaVersion": 2017
+  }
+}
+
diff --git a/packages/pubsweet-server/.gitignore b/packages/pubsweet-server/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..b9434650097c7d1e2d4f624b94d9480fca1e2dc3
--- /dev/null
+++ b/packages/pubsweet-server/.gitignore
@@ -0,0 +1,9 @@
+node_modules/*
+db/*
+npm-debug.log
+.DS_Store
+test.log
+uploads/
+coverage/
+logs/*
+config/local*.*
diff --git a/packages/pubsweet-server/.gitlab-ci.yml b/packages/pubsweet-server/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..22c235c6dcfd09ebfac11384500ad70935612dbe
--- /dev/null
+++ b/packages/pubsweet-server/.gitlab-ci.yml
@@ -0,0 +1,17 @@
+image: pubsweet/pubsweet-test-base
+
+before_script:
+  - yarn
+
+lint:
+  script:
+    - npm run lint
+
+test:
+  script:
+    - npm run test
+  coverage: '/^All files\s+\|\s+(\d+.\d+)\s\|.*$/'
+
+test:vulnerabilities:
+  script:
+    - npm run vuln-test
diff --git a/packages/pubsweet-server/.nsprc b/packages/pubsweet-server/.nsprc
new file mode 100644
index 0000000000000000000000000000000000000000..30f62baae0d3bef0fa96b4ebb40957d669e7edc4
--- /dev/null
+++ b/packages/pubsweet-server/.nsprc
@@ -0,0 +1,3 @@
+{
+  "exceptions": ["https://nodesecurity.io/advisories/534"]
+}
diff --git a/packages/pubsweet-server/.travis.yml b/packages/pubsweet-server/.travis.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9ff052ecfa25295b2870dcaedeb2dbc8d7470911
--- /dev/null
+++ b/packages/pubsweet-server/.travis.yml
@@ -0,0 +1,22 @@
+language: node_js
+
+env:
+  - CXX=g++-4.8
+
+addons:
+  apt:
+    sources:
+      - ubuntu-toolchain-r-test
+    packages:
+      - g++-4.8
+
+before_install:
+  - export JOBS=max
+
+node_js:
+  - "4.0"
+  - "4"
+  - "5"
+  - "stable"
+
+sudo: false
diff --git a/packages/pubsweet-server/CONTRIBUTING.md b/packages/pubsweet-server/CONTRIBUTING.md
new file mode 100644
index 0000000000000000000000000000000000000000..a5b58c83c2d8a372958e9d080a89400b3adfbb0f
--- /dev/null
+++ b/packages/pubsweet-server/CONTRIBUTING.md
@@ -0,0 +1,26 @@
+# CONTRIBUTING
+
+## Branches
+
+We maintain master as the production branch and tag it with release names. If you wish to contribute to PubSweet then you need to make a branch and then issue a pull request following this procedure:
+
+1. Create a user account on Coko GitLab : http://gitlab.coko.foundation
+2. Clone master with ```git clone git@gitlab.coko.foundation:pubsweet/pubsweet-server.git```
+3. Create a new branch and work off that. Please name the branch which sensibly identifies the feature you are working on. You can push the branch to Coko Gitlab at anytime.
+
+## Getting your contributions merged
+
+This is a two part process, first ask for comments, then ask for the changes to be merged.
+
+1. Ask for feedback generate a Merge Request (Pull Request) from the gitlab interface but do not assign this request to anyone. You do this from the Gitlab UI on your branch.
+2. Look at the feedback and alter your branch as necessary.
+3. To merge with master - generate a merge request (Pull Request) and assign to Jure Triglav. You do this from the Gitlab UI on your branch.
+
+We encourage feedback and discussion from as many people as possible on Merge Requests!
+
+## Bug reports, feature requests, support questions
+This is all done through GitLab using their native issue tracker
+
+1. Visit [the issue tracker for pubsweet-server](https://gitlab.coko.foundation/pubsweet/pubsweet-server/issues).
+2. Add an issue.
+3. Tag the issue with 'support', 'bug', or 'feature' to identify the nature of your issue
diff --git a/packages/pubsweet-server/LICENSE b/packages/pubsweet-server/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..e218890defd2f464caae083d38bc1b29e0d78a45
--- /dev/null
+++ b/packages/pubsweet-server/LICENSE
@@ -0,0 +1,7 @@
+Copyright (c) 2015 Adam Hyde
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/pubsweet-server/README.md b/packages/pubsweet-server/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ecdac03f90707a4cc331a2da18ed9f3ca9858110
--- /dev/null
+++ b/packages/pubsweet-server/README.md
@@ -0,0 +1,14 @@
+# Build status
+
+[![build status](https://gitlab.coko.foundation/pubsweet/pubsweet-server/badges/master/build.svg)](https://gitlab.coko.foundation/pubsweet/pubsweet-server/builds)
+[![coverage report](https://gitlab.coko.foundation/pubsweet/pubsweet-server/badges/master/coverage.svg)](https://gitlab.coko.foundation/pubsweet/pubsweet-server/commits/master)
+
+# Description
+
+This is the PubSweet server, to be used as a dependency in PubSweet apps.
+
+# Development
+- Check out our GitLab repository: https://gitlab.coko.foundation/pubsweet/pubsweet-server
+
+# Roadmap
+- Check out the [PubSweet 2.0 RFC](https://gitlab.coko.foundation/pubsweet/pubsweet/issues/16)
diff --git a/packages/pubsweet-server/config/custom-environment-variables.js b/packages/pubsweet-server/config/custom-environment-variables.js
new file mode 100644
index 0000000000000000000000000000000000000000..8b6b0277d26d0b33a87559ed306183be6caba21c
--- /dev/null
+++ b/packages/pubsweet-server/config/custom-environment-variables.js
@@ -0,0 +1,6 @@
+module.exports = {
+  'pubsweet-server': {
+    silent: 'PUBSWEET_BACKEND_SILENT',
+    secret: 'PUBSWEET_SECRET'
+  }
+}
diff --git a/packages/pubsweet-server/config/test.js b/packages/pubsweet-server/config/test.js
new file mode 100644
index 0000000000000000000000000000000000000000..b60d9a3905062f72218b894f105861722e700c55
--- /dev/null
+++ b/packages/pubsweet-server/config/test.js
@@ -0,0 +1,24 @@
+const path = require('path')
+const winston = require('winston')
+
+module.exports = {
+  'pubsweet-server': {
+    logger: winston,
+    secret: 'test',
+    sse: false
+  },
+  validations: path.join(__dirname, 'validations'),
+  authsome: {
+    mode: path.resolve(__dirname, '..', 'test', 'helpers', 'authsome_mode'),
+    teams: {
+      teamContributors: {
+        name: 'Contributors',
+        permissions: 'POST'
+      },
+      teamCoauthors: {
+        name: 'Coauthors',
+        permissions: 'PATCH'
+      }
+    }
+  }
+}
diff --git a/packages/pubsweet-server/config/validations.js b/packages/pubsweet-server/config/validations.js
new file mode 100644
index 0000000000000000000000000000000000000000..1167eb689230e51a7f981d4aa8754fec9f630bfb
--- /dev/null
+++ b/packages/pubsweet-server/config/validations.js
@@ -0,0 +1,26 @@
+const Joi = require('joi')
+
+module.exports = {
+  fragment: [
+    {
+      fragmentType: Joi.valid('blogpost').required(),
+      source: Joi.string(),
+      kind: Joi.string(),
+      title: Joi.string(),
+      presentation: Joi.string(),
+      published: Joi.boolean(),
+      filtered: Joi.string()
+    },
+    {
+      fragmentType: Joi.valid('file').required(),
+      path: Joi.string().required()
+    }
+  ],
+  collection: {
+    published: Joi.boolean(),
+    nonPublicProperty: Joi.string(),
+    filtered: Joi.string(),
+    created: Joi.date().default(Date.now, 'creation time'),
+    title: Joi.string()
+  }
+}
\ No newline at end of file
diff --git a/packages/pubsweet-server/db/.gitkeep b/packages/pubsweet-server/db/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/packages/pubsweet-server/logs/.gitkeep b/packages/pubsweet-server/logs/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/packages/pubsweet-server/package.json b/packages/pubsweet-server/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..a641c411ed9f40fb8533d084b828421db75893a2
--- /dev/null
+++ b/packages/pubsweet-server/package.json
@@ -0,0 +1,78 @@
+{
+  "name": "pubsweet-server",
+  "version": "1.0.0-beta.3",
+  "scripts": {
+    "lint": "eslint src/ test/ config/",
+    "test": "jest",
+    "test:docker": "gitlab-runner exec docker test",
+    "vuln-test": "nsp check"
+  },
+  "engines": {
+    "node": ">=7.6"
+  },
+  "author": "Collaborative Knowledge Foundation",
+  "license": "MIT",
+  "main": "src/index.js",
+  "dependencies": {
+    "@pubsweet/logger": "^0.0.1",
+    "authsome": "0.0.9",
+    "bcrypt": "^1.0.2",
+    "bluebird": "^3.5.1",
+    "body-parser": "^1.15.2",
+    "colors": "^1.1.2",
+    "config": "^1.26.2",
+    "cookie-parser": "^1.4.3",
+    "dotenv": "^4.0.0",
+    "express": "^4.16.1",
+    "helmet": "^3.8.1",
+    "http-status-codes": "^1.0.6",
+    "joi": "^10.0.6",
+    "jsonwebtoken": "^7.1.7",
+    "lodash": "^4.0.0",
+    "minimist": "^1.2.0",
+    "morgan": "^1.8.2",
+    "multer": "^1.1.0",
+    "passport": "^0.3.2",
+    "passport-anonymous": "^1.0.1",
+    "passport-http-bearer": "^1.0.1",
+    "passport-local": "^1.0.0",
+    "pouchdb-adapter-http": "^6.2.0",
+    "pouchdb-adapter-leveldb": "^6.1.1",
+    "pouchdb-adapter-memory": "^6.1.1",
+    "pouchdb-core": "^6.1.1",
+    "pouchdb-find": "^0.10.3",
+    "pouchdb-upsert": "^2.0.0",
+    "promise-queue": "^2.2.3",
+    "prompt": "^1.0.0",
+    "pubsweet-sse": "^0.1.1",
+    "relational-pouch": "^1.4.5",
+    "uuid": "^3.0.1",
+    "winston": "^2.2.0"
+  },
+  "devDependencies": {
+    "eslint": "^4.0.0",
+    "eslint-config-standard": "^10.2.1",
+    "eslint-plugin-import": "^2.7.0",
+    "eslint-plugin-node": "^5.2.0",
+    "eslint-plugin-promise": "^3.5.0",
+    "eslint-plugin-standard": "^3.0.1",
+    "eventsource": "^1.0.4",
+    "jest-cli": "^21.2.1",
+    "nsp": "^2.7.0",
+    "standard": "^10.0.2",
+    "supertest": "^2.0.0"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://gitlab.coko.foundation/pubsweet/pubsweet-server"
+  },
+  "jest": {
+    "verbose": true,
+    "testEnvironment": "node",
+    "testRegex": "/test/.*_test.js$",
+    "collectCoverage": true,
+    "collectCoverageFrom": [
+      "src/**/*.js"
+    ]
+  }
+}
diff --git a/packages/pubsweet-server/src/.eslintrc b/packages/pubsweet-server/src/.eslintrc
new file mode 100644
index 0000000000000000000000000000000000000000..326e6af6b9ada0431fd0668417a7126829ce2879
--- /dev/null
+++ b/packages/pubsweet-server/src/.eslintrc
@@ -0,0 +1,6 @@
+{
+  "globals": {
+    "db": true,
+    "acl": true
+  }
+}
diff --git a/packages/pubsweet-server/src/authentication.js b/packages/pubsweet-server/src/authentication.js
new file mode 100644
index 0000000000000000000000000000000000000000..e13dbc5f186e3c8263c40f148c50a826ffd1ce8c
--- /dev/null
+++ b/packages/pubsweet-server/src/authentication.js
@@ -0,0 +1,71 @@
+const logger = require('@pubsweet/logger')
+const jwt = require('jsonwebtoken')
+
+const BearerStrategy = require('passport-http-bearer').Strategy
+const AnonymousStrategy = require('passport-anonymous').Strategy
+const LocalStrategy = require('passport-local').Strategy
+
+const User = require('./models/User')
+const config = require('config')
+
+const createToken = (user) => {
+  logger.debug('Creating token for', user.username)
+
+  return jwt.sign(
+    {
+      username: user.username,
+      id: user.id
+    },
+    config.get('pubsweet-server.secret'),
+    { expiresIn: 24 * 3600 }
+  )
+}
+
+const verifyToken = (token, done) => {
+  jwt.verify(token, config.get('pubsweet-server.secret'), (err, decoded) => {
+    if (err) return done(null)
+
+    return done(null, decoded.id, {
+      username: decoded.username,
+      id: decoded.id,
+      token: token
+    })
+  })
+}
+
+const verifyPassword = (username, password, done) => {
+  let errorMessage = 'Wrong username or password.'
+  logger.debug('User finding:', username)
+
+  User.findByUsername(username).then(user => {
+    logger.debug('User found:', user.username)
+    return Promise.all([user, user.validPassword(password)])
+  }).then(([user, isValid]) => {
+    if (isValid) {
+      return done(null, user, { id: user.id })
+    } else {
+      logger.debug('Invalid password for user:', username)
+      return done(null, false, { message: errorMessage })
+    }
+  }).catch((err) => {
+    logger.debug('User not found', err)
+    if (err) { return done(null, false, { message: errorMessage }) }
+  })
+}
+
+module.exports = {
+  token: {
+    create: createToken,
+    verify: verifyToken
+  },
+  strategies: {
+    // no credentials
+    anonymous: new AnonymousStrategy(),
+
+    // JSON web token in "Bearer" HTTP header
+    bearer: new BearerStrategy(verifyToken),
+
+    // email + password
+    local: new LocalStrategy(verifyPassword)
+  }
+}
diff --git a/packages/pubsweet-server/src/db.js b/packages/pubsweet-server/src/db.js
new file mode 100644
index 0000000000000000000000000000000000000000..e07d1fe64edd2448f0ac396bc4d811b582ded7cf
--- /dev/null
+++ b/packages/pubsweet-server/src/db.js
@@ -0,0 +1,51 @@
+const uuid = require('uuid')
+const config = require('config')
+const _ = require('lodash/fp')
+
+const PouchDB = require('pouchdb-core')
+  .plugin(require('pouchdb-find'))
+  .plugin(require('pouchdb-upsert'))
+  .plugin(require('relational-pouch'))
+
+const getAdapterIdentifier = (dbPath) => {
+  if (config.has('pubsweet-server.adapter')) {
+    return config.get('pubsweet-server.adapter')
+  }
+  // deprecated: should be set via config and next 3 lines removed
+  if (config.util.getEnv('NODE_ENV') === 'test') {
+    return 'memory'
+  }
+
+  if (dbPath.match(/^http/)) {
+    return 'http'
+  }
+
+  return 'leveldb'
+}
+
+const preparePouchConfig = (adapter) => {
+  switch (adapter) {
+    case 'memory':
+      PouchDB.plugin(require('pouchdb-adapter-memory'))
+      // a new database for each test
+      return { name: uuid(), adapter }
+
+    case 'http':
+      PouchDB.plugin(require('pouchdb-adapter-http'))
+      return { name: dbPath, adapter }
+
+    case 'leveldb':
+      PouchDB.plugin(require('pouchdb-adapter-leveldb'))
+      return { name: dbPath, adapter }
+  }
+}
+
+const dbPath = _.get('pubsweet-server.dbPath', config)
+const adapterIdentifier = getAdapterIdentifier(dbPath)
+const pouchConfig = preparePouchConfig(adapterIdentifier)
+
+module.exports = () => {
+  // Pass name as first arg because passing { name } on options
+  // seems to produce different result (see Pouch issue #1137 ?) 
+  return new PouchDB(pouchConfig.name, { adapter: pouchConfig.adapter })
+}
diff --git a/packages/pubsweet-server/src/db/.gitignore b/packages/pubsweet-server/src/db/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d
--- /dev/null
+++ b/packages/pubsweet-server/src/db/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/packages/pubsweet-server/src/errors/AuthorizationError.js b/packages/pubsweet-server/src/errors/AuthorizationError.js
new file mode 100644
index 0000000000000000000000000000000000000000..8a3a35ce7e2180519b4e2c4a4af9aa18df997e7e
--- /dev/null
+++ b/packages/pubsweet-server/src/errors/AuthorizationError.js
@@ -0,0 +1,14 @@
+'use strict'
+const STATUS = require('http-status-codes')
+
+class AuthorizationError extends Error {
+  constructor (message, status) {
+    super(message)
+    Error.captureStackTrace(this, 'AuthorizationError')
+    this.name = 'AuthorizationError'
+    this.message = message
+    this.status = status || STATUS.FORBIDDEN
+  }
+}
+
+module.exports = AuthorizationError
diff --git a/packages/pubsweet-server/src/errors/ConflictError.js b/packages/pubsweet-server/src/errors/ConflictError.js
new file mode 100644
index 0000000000000000000000000000000000000000..30567759402db0c4024eb768864bbc8499b4a6b4
--- /dev/null
+++ b/packages/pubsweet-server/src/errors/ConflictError.js
@@ -0,0 +1,15 @@
+'use strict'
+
+const STATUS = require('http-status-codes')
+
+class ConflictError extends Error {
+  constructor (message, status) {
+    super(message)
+    Error.captureStackTrace(this, 'ConflictError')
+    this.name = 'ConflictError'
+    this.message = message
+    this.status = status || STATUS.CONFLICT
+  }
+}
+
+module.exports = ConflictError
diff --git a/packages/pubsweet-server/src/errors/NotFoundError.js b/packages/pubsweet-server/src/errors/NotFoundError.js
new file mode 100644
index 0000000000000000000000000000000000000000..9e1ee1970212080a5295ab5a7ec60090f49e34d2
--- /dev/null
+++ b/packages/pubsweet-server/src/errors/NotFoundError.js
@@ -0,0 +1,14 @@
+'use strict'
+const STATUS = require('http-status-codes')
+
+class NotFoundError extends Error {
+  constructor (message, status) {
+    super(message)
+    Error.captureStackTrace(this, 'NotFoundError')
+    this.name = 'NotFoundError'
+    this.message = message || 'Not found'
+    this.status = status || STATUS.NOT_FOUND
+  }
+}
+
+module.exports = NotFoundError
diff --git a/packages/pubsweet-server/src/errors/ValidationError.js b/packages/pubsweet-server/src/errors/ValidationError.js
new file mode 100644
index 0000000000000000000000000000000000000000..5ec42347144636919d0e7e63a6343e2233726ccb
--- /dev/null
+++ b/packages/pubsweet-server/src/errors/ValidationError.js
@@ -0,0 +1,14 @@
+'use strict'
+const STATUS = require('http-status-codes')
+
+class ValidationError extends Error {
+  constructor (message, status) {
+    super(message)
+    Error.captureStackTrace(this, 'ValidationError')
+    this.name = 'ValidationError'
+    this.message = message
+    this.status = status || STATUS.CONFLICT
+  }
+}
+
+module.exports = ValidationError
diff --git a/packages/pubsweet-server/src/helpers/authsome.js b/packages/pubsweet-server/src/helpers/authsome.js
new file mode 100644
index 0000000000000000000000000000000000000000..55a4dc7442774cb68456c11d1f179f8919afb91e
--- /dev/null
+++ b/packages/pubsweet-server/src/helpers/authsome.js
@@ -0,0 +1,26 @@
+
+const config = require('config')
+const Authsome = require('authsome')
+const models = require('../models')
+const mode = require(config.get('authsome.mode'))
+
+const authsome = new Authsome({...config.authsome, mode}, {
+  // restrict methods passed to mode since these have to be shimmed on client
+  // any changes here should be reflected in the `withAuthsome` component of `pubsweet-client`
+  models: {
+    Collection: {
+      find: id => models.Collection.find(id)
+    },
+    Fragment: {
+      find: id => models.Fragment.find(id)
+    },
+    User: {
+      find: id => models.User.find(id)
+    },
+    Team: {
+      find: id => models.Team.find(id)
+    }
+  }
+})
+
+module.exports = authsome
diff --git a/packages/pubsweet-server/src/index.js b/packages/pubsweet-server/src/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..4b0f52eb91b5681031aed3e67a22fcc2683a67e7
--- /dev/null
+++ b/packages/pubsweet-server/src/index.js
@@ -0,0 +1,92 @@
+const path = require('path')
+const config = require('config')
+const dotenvPath = path.resolve(`.env.${config.util.getEnv('NODE_ENV')}`)
+require('dotenv').config({ path: dotenvPath })
+
+const express = require('express')
+const morgan = require('morgan')
+const helmet = require('helmet')
+const cookieParser = require('cookie-parser')
+const bodyParser = require('body-parser')
+const passport = require('passport')
+const index = require('./routes/index')
+const api = require('./routes/api')
+const logger = require('@pubsweet/logger')
+const sse = require('pubsweet-sse')
+const authentication = require('./authentication')
+const models = require('./models')
+const _ = require('lodash/fp')
+const STATUS = require('http-status-codes')
+const registerComponents = require('./register-components')
+const startServer = require('./start-server')
+
+const configureApp = (app) => {
+  global.versions = {}
+
+  app.locals.models = models
+
+  app.use(morgan('combined', { 'stream': logger.stream }))
+  app.use(bodyParser.json({ limit: '50mb' }))
+
+  app.use(bodyParser.urlencoded({ extended: false }))
+  app.use(cookieParser())
+  app.use(helmet())
+  app.use(express.static(path.resolve('.', '_build')))
+  app.use('/uploads', express.static(path.resolve('.', 'uploads')))
+
+  // Passport strategies
+  app.use(passport.initialize())
+  passport.use('bearer', authentication.strategies.bearer)
+  passport.use('anonymous', authentication.strategies.anonymous)
+  passport.use('local', authentication.strategies.local)
+
+  registerComponents(app)
+
+  // Main API
+  app.use('/api', api)
+
+  // SSE update stream
+  if (_.get('pubsweet-server.sse', config)) {
+    app.get('/updates', passport.authenticate('bearer', { session: false }), sse.connect)
+  }
+
+  // Serve the index page for front end
+  app.use('/manage', index)
+  app.use('/', index)
+
+  app.use((err, req, res, next) => {
+    // development error handler, will print stacktrace
+    if (app.get('env') === 'development' || app.get('env') === 'test') {
+      logger.error(err)
+      logger.error(err.stack)
+    }
+
+    if (err.name === 'ValidationError') {
+      return res.status(STATUS.BAD_REQUEST).json({ message: err.message })
+    } else if (err.name === 'ConflictError') {
+      return res.status(STATUS.CONFLICT).json({ message: err.message })
+    } else if (err.name === 'AuthorizationError') {
+      return res.status(err.status).json({ message: err.message })
+    } else if (err.name === 'AuthenticationError') {
+      return res.status(STATUS.UNAUTHORIZED).json({ message: err.message })
+    } else {
+      return res.status(err.status || STATUS.INTERNAL_SERVER_ERROR).json({ message: err.message })
+    }
+  })
+
+  return app
+}
+
+let server
+
+const start = async (app = express()) => {
+  if (server) return server
+  const configuredApp = configureApp(app)
+  server = await startServer(configuredApp)
+  server.app = configuredApp
+  return server
+}
+
+start.configureApp = configureApp
+
+module.exports = start
diff --git a/packages/pubsweet-server/src/models/Collection.js b/packages/pubsweet-server/src/models/Collection.js
new file mode 100644
index 0000000000000000000000000000000000000000..01df3ddf8e12b666ab85cf308e73079bbb7c54a4
--- /dev/null
+++ b/packages/pubsweet-server/src/models/Collection.js
@@ -0,0 +1,66 @@
+'use strict'
+const Model = require('./Model')
+const Fragment = require('./Fragment')
+const Team = require('./Team')
+const without = require('lodash/without')
+
+class Collection extends Model {
+  constructor (properties) {
+    super(properties)
+    this.type = 'collection'
+    this.fragments = this.fragments || []
+  }
+
+  // Gets fragments in a collection, supports filtering by function e.g.
+  // collection.getFragments({filter: fragment => {Authorize.can(req.user, 'read', fragment)})
+  getFragments (options) {
+    options = options || {}
+    options.filter = options.filter || (() => Promise.resolve(true))
+
+    const fragments = Promise.all(this.fragments.map((id) => Fragment.find(id)))
+
+    return fragments.then(
+      fragments => {
+        let filters = Promise.all(
+          fragments.map(
+            fragment => options.filter(fragment).catch(() => false)
+          )
+        )
+        return Promise.all([fragments, filters])
+      }
+    ).then(
+      ([fragments, filters]) => fragments.filter(fragment => filters.shift())
+    )
+  }
+
+  addFragment (fragment) {
+    this.fragments = this.fragments.map(fragment => {
+      if (typeof fragment === 'object') {
+        return fragment
+      } else {
+        return new Fragment({id: fragment})
+      }
+    })
+    this.fragments.push(fragment)
+  }
+
+  removeFragment (fragment) {
+    this.fragments = this.fragments.map(fragment => {
+      if (typeof fragment === 'object') {
+        return fragment.id
+      } else {
+        return fragment
+      }
+    })
+    this.fragments = without(this.fragments, fragment.id)
+  }
+
+  async delete () {
+    await Team.deleteAssociated(this.type, this.id)
+    return super.delete()
+  }
+}
+
+Collection.type = 'collection'
+
+module.exports = Collection
diff --git a/packages/pubsweet-server/src/models/Fragment.js b/packages/pubsweet-server/src/models/Fragment.js
new file mode 100644
index 0000000000000000000000000000000000000000..42f18e55f6a009a09ace8ac3a6f455b09d8420f1
--- /dev/null
+++ b/packages/pubsweet-server/src/models/Fragment.js
@@ -0,0 +1,19 @@
+'use strict'
+const Model = require('./Model')
+const Team = require('./Team')
+
+class Fragment extends Model {
+  constructor (properties) {
+    super(properties)
+    this.type = 'fragment'
+  }
+
+  async delete () {
+    await Team.deleteAssociated(this.type, this.id)
+    return super.delete()
+  }
+}
+
+Fragment.type = 'fragment'
+
+module.exports = Fragment
diff --git a/packages/pubsweet-server/src/models/Model.js b/packages/pubsweet-server/src/models/Model.js
new file mode 100644
index 0000000000000000000000000000000000000000..faed34cc641478b5ace8bec5cfce46a71f2e9da3
--- /dev/null
+++ b/packages/pubsweet-server/src/models/Model.js
@@ -0,0 +1,180 @@
+'use strict'
+
+const uuid = require('uuid')
+const Joi = require('joi')
+const _ = require('lodash')
+
+const schema = require('./schema')
+const NotFoundError = require('../errors/NotFoundError')
+const ValidationError = require('../errors/ValidationError')
+const logger = require('@pubsweet/logger')
+
+const config = require('config')
+const appValidations = require(config.validations)
+const validations = require('./validations')(appValidations)
+
+schema()
+
+class Model {
+  constructor (properties) {
+    schema()
+    this.id = Model.uuid()
+    Object.assign(this, properties)
+  }
+
+  static validations () {
+    return validations[this.type]
+  }
+
+  validate () {
+    let validation = Joi.validate(this, this.constructor.validations())
+
+    if (validation.error) {
+      logger.error(validation.error)
+      throw validation.error
+    }
+
+    return true
+  }
+
+  async save () {
+    logger.debug('Saving', this.type, this.id)
+
+    this.validate()
+
+    if (!this.rev /*is create*/ && typeof this.isUniq === 'function') {
+      await this.isUniq(this) // throws an exception if not unique
+    }
+    return this._put()
+  }
+
+  async _put () {
+    await db.rel.save(this.constructor.type, this)
+    logger.debug('Actually _put', this.type, this.id, this)
+    return this
+  }
+
+  async delete () {
+    const object = await this.constructor.find(this.id)
+    await db.rel.del(this.type, object)
+    logger.debug('Deleted', this.type, this.id)
+    return this
+  }
+
+  async updateProperties (properties) {
+    // These properties are modified through setters
+    delete properties.owners
+
+    logger.debug('Updating properties to', properties)
+
+    const validation = Joi.validate(properties, { rev: Joi.string().required() }, { allowUnknown: true })
+    if (validation.error) throw validation.error
+
+    Object.assign(this, properties)
+    return this
+  }
+
+  setOwners (owners) {
+    if (Array.isArray(owners)) {
+      owners.forEach(owner => this.validateOwner(owner))
+      this.owners = owners
+    } else {
+      throw new ValidationError('owners should be an array')
+    }
+  }
+
+  validateOwner (owner) {
+    if (typeof owner !== 'string') throw new ValidationError('owner should be an id')
+  }
+
+  isOwner (userId) {
+    return Array.isArray(this.owners) && this.owners.includes(userId)
+  }
+
+  static uuid () {
+    return uuid.v4()
+  }
+
+  // Find all of a certain type e.g.
+  // User.all()
+  static async all () {
+    const results = await db.rel.find(this.type)
+
+    return results[this.type + 's'].map(result => new this(result))
+  }
+
+  // Find by id e.g.
+  // User.find('394')
+  static async find (id) {
+    let plural = this.type + 's'
+    let results
+
+    try {
+      results = await db.rel.find(this.type, id)
+    } catch (err) {
+      if (err.name === 'NotFoundError') {
+        throw new NotFoundError(`Object not found: ${this.type} with id ${id}`)
+      } else {
+        throw err
+      }
+    }
+
+    let result = results[plural].find(result => result.id === id)
+
+    if (!result) {
+      throw new NotFoundError(`Object not found: ${this.type} with id ${id}`)
+    }
+
+    return new this(result)
+  }
+
+  // `field` is a string
+  // `value` is a primitive, or a query object
+  // or
+  // `field` is an object of field, value pairs
+  static async findByField (field, value) {
+    logger.debug('Finding', field, value)
+
+    let selector = {
+      type: this.type
+    }
+
+    if (value !== undefined) {
+      selector[field] = value
+    } else {
+      Object.assign(selector, field)
+    }
+
+    selector = _.mapKeys(selector, (_, key) => `data.${key}`)
+
+    await db.createIndex({
+      index: {
+        fields: Object.keys(selector)
+      }
+    })
+
+    const results = await db.find({
+      selector
+    })
+
+    if (!results.docs.length) {
+      throw new NotFoundError()
+    }
+
+    return results.docs.map(result => {
+      let id = db.rel.parseDocID(result._id).id
+      let foundObject = result.data
+      foundObject.id = id
+      foundObject.rev = result._rev
+      return new this(foundObject)
+    })
+  }
+
+  static async findOneByField (field, value) {
+    const results = await this.findByField(field, value)
+
+    return results.length ? results[0] : null
+  }
+}
+
+module.exports = Model
diff --git a/packages/pubsweet-server/src/models/Team.js b/packages/pubsweet-server/src/models/Team.js
new file mode 100644
index 0000000000000000000000000000000000000000..864340fc74b7d5c1029b69b2274ecd9f8603954f
--- /dev/null
+++ b/packages/pubsweet-server/src/models/Team.js
@@ -0,0 +1,81 @@
+'use strict'
+
+const _ = require('lodash')
+
+const Model = require('./Model')
+const User = require('./User')
+
+class Team extends Model {
+  constructor (properties) {
+    super(properties)
+
+    this.type = 'team'
+
+    if (!Array.isArray(this.members)) {
+      this.members = []
+    }
+  }
+
+  static async deleteAssociated (type, id) {
+    const teams = await Team.all()
+
+    return Promise.all(
+      teams
+        .filter(team => team.object &&
+          team.object.type === type &&
+          team.object.id === id)
+        .map(team => team.delete())
+    )
+  }
+
+  async updateProperties (properties) {
+    let currentMembers = new Set(this.members)
+    let newMembers = new Set(properties.members)
+    let removedMembers = new Set([...currentMembers].filter(x => !newMembers.has(x)))
+
+    await Promise.all(
+      [...removedMembers].map(userId => {
+        return User.find(userId).then(user => {
+          user.teams = user.teams.filter(teamId => teamId !== this.id)
+          return user.save()
+        })
+      })
+    )
+
+    return super.updateProperties(properties)
+  }
+
+  async save () {
+    await Promise.all(
+      this.members.map(member => {
+        return User.find(member).then(user => {
+          if (!(user.teams).includes(this.id)) {
+            user.teams.push(this.id)
+            return user.save()
+          }
+        })
+      })
+    )
+
+    return super.save()
+  }
+
+  async delete () {
+    await Promise.all(
+      this.members.map(member => {
+        return User.find(member).then(user => {
+          if (user.teams.includes(this.id)) {
+            user.teams = _.without(user.teams, this.id)
+            return user.save()
+          }
+        })
+      })
+    )
+
+    return super.delete()
+  }
+}
+
+Team.type = 'team'
+
+module.exports = Team
diff --git a/packages/pubsweet-server/src/models/User.js b/packages/pubsweet-server/src/models/User.js
new file mode 100644
index 0000000000000000000000000000000000000000..bcec03cc7ce8555e869a0e4ddbb675dd21a620bf
--- /dev/null
+++ b/packages/pubsweet-server/src/models/User.js
@@ -0,0 +1,84 @@
+'use strict'
+const Model = require('./Model')
+const ConflictError = require('../errors/ConflictError')
+const bcrypt = require('bcrypt')
+const omit = require('lodash/omit')
+const pick = require('lodash/pick')
+const config = require('config')
+
+const BCRYPT_COST = config.util.getEnv('NODE_ENV') === 'test' ? 1 : 12
+
+class User extends Model {
+  constructor (properties) {
+    super(properties)
+
+    this.type = 'user'
+    this.email = properties.email
+    this.username = properties.username
+  }
+
+  toJSON () {
+    return omit(this, ['passwordHash'])
+  }
+
+  async save () {
+    if (this.password) {
+      this.passwordHash = await this.hashPassword(this.password)
+      delete this.password
+    }
+
+    return Model.prototype.save.call(this)
+  }
+
+  validPassword (password) {
+    return bcrypt.compare(password, this.passwordHash)
+  }
+
+  hashPassword (password) {
+    return bcrypt.hash(password, BCRYPT_COST)
+  }
+
+  async isUniq (user) {
+    let result
+
+    const swallowNotFound = e => {
+      if (e.name !== 'NotFoundError') throw e
+    }
+
+    result = await User.findByEmail(user.email).catch(swallowNotFound)
+
+    if (result) {
+      throw new ConflictError('User already exists')
+    }
+
+    result = await User.findByUsername(user.username).catch(swallowNotFound)
+
+    if (result) {
+      throw new ConflictError('User already exists')
+    }
+  }
+
+  static findByEmail (email) {
+    return this.findByField('email', email).then(function (users) {
+      return users[0]
+    })
+  }
+
+  static findByUsername (username) {
+    return this.findByField('username', username).then(function (users) {
+      return users[0]
+    })
+  }
+
+  // For API display/JSON purposes only
+  static ownersWithUsername (object) {
+    return Promise.all(object.owners.map(async ownerId => {
+      const owner = await this.find(ownerId)
+      return pick(owner, ['id', 'username'])
+    }))
+  }
+}
+
+User.type = 'user'
+
+module.exports = User
diff --git a/packages/pubsweet-server/src/models/index.js b/packages/pubsweet-server/src/models/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb6b5bd23293f4dddc0ee29b83812e02cbe02034
--- /dev/null
+++ b/packages/pubsweet-server/src/models/index.js
@@ -0,0 +1,6 @@
+module.exports = {
+  Collection: require('./Collection'),
+  Fragment: require('./Fragment'),
+  User: require('./User'),
+  Team: require('./Team')
+}
diff --git a/packages/pubsweet-server/src/models/schema.js b/packages/pubsweet-server/src/models/schema.js
new file mode 100644
index 0000000000000000000000000000000000000000..0af88f69bc105461a925e77f9781e3d3af3f37aa
--- /dev/null
+++ b/packages/pubsweet-server/src/models/schema.js
@@ -0,0 +1,42 @@
+'use strict'
+
+global.db = require('../db')()
+
+module.exports = function () {
+  if (!db.rel) {
+    return db.setSchema([
+      {
+        singular: 'collection',
+        plural: 'collections',
+        relations: {
+          fragments: {hasMany: 'fragment'},
+          owners: {hasMany: 'user'}
+        }
+      },
+      {
+        singular: 'fragment',
+        plural: 'fragments',
+        relations: {
+          collection: {belongsTo: 'collection'},
+          owners: {hasMany: 'user'}
+        }
+      },
+      {
+        singular: 'user',
+        plural: 'users',
+        relations: {
+          collections: {hasMany: 'collection'},
+          fragments: {hasMany: 'fragment'},
+          teams: {hasMany: 'team'}
+        }
+      },
+      {
+        singular: 'team',
+        plural: 'teams',
+        relations: {
+          members: {hasMany: 'user'}
+        }
+      }
+    ])
+  }
+}
diff --git a/packages/pubsweet-server/src/models/validations.js b/packages/pubsweet-server/src/models/validations.js
new file mode 100644
index 0000000000000000000000000000000000000000..2fcf36548496310e4f7eff1ac1339efe9797bf11
--- /dev/null
+++ b/packages/pubsweet-server/src/models/validations.js
@@ -0,0 +1,81 @@
+'use strict'
+
+// This module is used for communicating validation requirements to the
+// client and server, it sits in the middle.
+
+const Joi = require('joi')
+
+// These are fixed/required validations, they are combined with configurable
+// validations later
+
+let validations = {
+  fragment: {
+    id: Joi.string().guid().required(),
+    type: Joi.string().required(),
+    fragmentType: Joi.string().required(),
+    rev: Joi.string(),
+    fragments: Joi.array().items(Joi.string().guid()),
+    owners: Joi.array().items(Joi.string().guid())
+  },
+  collection: {
+    id: Joi.string().guid().required(),
+    type: Joi.string().required(),
+    rev: Joi.string(),
+    owners: Joi.array().items(Joi.string().guid()),
+    fragments: Joi.array().items(
+      Joi.alternatives().try(
+        // a fragment ID
+        Joi.string(),
+        // or a fragment object
+        Joi.object({ type: Joi.string().valid('fragment') }).unknown(true)
+      )
+    )
+  },
+  user: {
+    id: Joi.string().guid().required(),
+    type: Joi.string(),
+    username: Joi.string().alphanum().required(),
+    email: Joi.string().email().required(),
+    passwordHash: Joi.string().required(),
+    admin: Joi.boolean(),
+    rev: Joi.string(),
+    fragments: Joi.array().items(Joi.string().guid()),
+    collections: Joi.array().items(Joi.string().guid()),
+    teams: Joi.array().items(Joi.string().guid()),
+    passwordResetToken: Joi.string(),
+    passwordResetTimestamp: Joi.date().timestamp()
+  },
+  team: {
+    id: Joi.string().guid().required(),
+    type: Joi.string().required(),
+    name: Joi.string().required(),
+    object: Joi.object().required(),
+    teamType: Joi.object().required(),
+    rev: Joi.string(),
+    members: Joi.array().items(Joi.string().guid())
+  }
+}
+
+let allValidations = function (type, appValidations) {
+  let appValidationsForType = {}
+
+  if (appValidations && appValidations[type]) {
+    appValidationsForType = appValidations[type]
+  }
+
+  if (Array.isArray(appValidationsForType)) {
+    const alternatives = appValidationsForType.map(alternative => ({...validations[type], ...alternative}))
+    return Joi.alternatives().try(...alternatives)
+  }
+
+  return Joi.object().keys({...validations[type], ...appValidationsForType})
+}
+
+module.exports = function (appValidations) {
+  return {
+    fragment: allValidations('fragment', appValidations),
+    collection: allValidations('collection', appValidations),
+    user: allValidations('user', appValidations),
+    team: allValidations('team', appValidations)
+  }
+}
diff --git a/packages/pubsweet-server/src/register-components.js b/packages/pubsweet-server/src/register-components.js
new file mode 100644
index 0000000000000000000000000000000000000000..f10f43cc60786f18e9c26dec35ffe8daaa33f03a
--- /dev/null
+++ b/packages/pubsweet-server/src/register-components.js
@@ -0,0 +1,16 @@
+const logger = require('@pubsweet/logger')
+const config = require('config')
+
+module.exports = app => {
+  if (config.has('pubsweet.components')) {
+    config.get('pubsweet.components').forEach(name => {
+      const component = require(name)
+      logger.info('Registered component', name)
+      const backendComponent = component.server || component.backend
+      if (backendComponent) {
+        backendComponent()(app)
+        logger.info('Registered server component', name)
+      }
+    })
+  }
+}
diff --git a/packages/pubsweet-server/src/routes/api.js b/packages/pubsweet-server/src/routes/api.js
new file mode 100644
index 0000000000000000000000000000000000000000..7237dd7fdf7591b0485bba2faf57551f0d146a35
--- /dev/null
+++ b/packages/pubsweet-server/src/routes/api.js
@@ -0,0 +1,28 @@
+const express = require('express')
+const helmet = require('helmet')
+
+const api = express.Router({mergeParams: true})
+
+api.use(helmet())
+
+// Collections
+const collections = require('./api_collections')
+api.use(collections)
+
+// Fragments
+const fragments = require('./api_fragments')
+api.use(fragments)
+
+// File upload API
+const upload = require('./api_upload')
+api.use(upload)
+
+// Users API
+const users = require('./api_users')
+api.use(users)
+
+// Teams
+const teams = require('./api_teams')
+api.use(teams)
+
+module.exports = api
diff --git a/packages/pubsweet-server/src/routes/api_collections.js b/packages/pubsweet-server/src/routes/api_collections.js
new file mode 100644
index 0000000000000000000000000000000000000000..787c68c8de8d51294924aab02c777d7b4b65fd46
--- /dev/null
+++ b/packages/pubsweet-server/src/routes/api_collections.js
@@ -0,0 +1,151 @@
+'use strict'
+
+const STATUS = require('http-status-codes')
+
+const Collection = require('../models/Collection')
+const Team = require('../models/Team')
+const User = require('../models/User')
+
+const express = require('express')
+const api = express.Router()
+
+const sse = require('pubsweet-sse')
+const {
+  createFilterFromQuery,
+  objectId,
+  buildChangeData,
+  fieldSelector,
+  getTeams,
+  applyPermissionFilter
+} = require('./util')
+
+const passport = require('passport')
+const authBearer = passport.authenticate('bearer', { session: false })
+const authBearerAndPublic = passport.authenticate(['bearer', 'anonymous'], { session: false })
+
+// List collections
+api.get('/collections', authBearerAndPublic, async (req, res, next) => {
+  try {
+    const collections = await Collection.all()
+    const filteredCollections = await applyPermissionFilter({
+      req,
+      target: req.route,
+      filterable: collections
+    })
+
+    const collectionsWithSelectedFields = (await Promise.all(filteredCollections.map(async collection => {
+      collection.owners = await User.ownersWithUsername(collection)
+      const properties = await applyPermissionFilter({ req, target: collection })
+      return fieldSelector(req)(properties)
+    })))
+      .filter(createFilterFromQuery(req.query))
+
+    res.status(STATUS.OK).json(collectionsWithSelectedFields)
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Create a collection
+api.post('/collections', authBearer, async (req, res, next) => {
+  try {
+    const properties = await applyPermissionFilter({
+      req,
+      target: req.route,
+      filterable: req.body
+    })
+
+    const collection = new Collection(properties)
+    collection.created = Date.now()
+    collection.setOwners([req.user])
+
+    await collection.save()
+
+    // TODO: filter the output?
+
+    res.status(STATUS.CREATED).json(collection)
+    sse.send({ action: 'collection:create', data: { collection } })
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Retrieve a collection
+api.get('/collections/:collectionId', authBearerAndPublic, async (req, res, next) => {
+  try {
+    const collection = await Collection.find(req.params.collectionId)
+    collection.owners = await User.ownersWithUsername(collection)
+    const properties = await applyPermissionFilter({ req, target: collection })
+
+    return res.status(STATUS.OK).json(properties)
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Update a collection
+api.patch('/collections/:collectionId', authBearer, async (req, res, next) => {
+  try {
+    const collection = await Collection.find(req.params.collectionId)
+    const properties = await applyPermissionFilter({
+      req,
+      target: collection,
+      filterable: req.body
+    })
+
+    await collection.updateProperties(properties)
+    await collection.save()
+
+    const updated = buildChangeData(properties, collection)
+
+    res.status(STATUS.OK).json(updated)
+    sse.send({ action: 'collection:patch', data: { collection: objectId(collection), updated } })
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Delete a collection
+api.delete('/collections/:collectionId', authBearer, async (req, res, next) => {
+  try {
+    const collection = await Collection.find(req.params.collectionId)
+    const output = await applyPermissionFilter({ req, target: collection })
+
+    // TODO: filter the output, or return nothing?
+
+    await collection.delete()
+
+    res.status(STATUS.OK).json(output)
+    sse.send({ action: 'collection:delete', data: { collection: objectId(collection) } })
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Retrieve teams for a collection
+api.get('/collections/:collectionId/teams', authBearerAndPublic, async (req, res, next) => {
+  const collection = await Collection.find(req.params.collectionId)
+  await applyPermissionFilter({ req, target: collection })
+
+  try {
+    const teams = (await getTeams({
+      req,
+      Team,
+      id: collection.id,
+      type: 'collection'
+    }))
+      .filter(createFilterFromQuery(req.query))
+
+    res.status(STATUS.OK).json(teams)
+  } catch (err) {
+    next(err)
+  }
+})
+
+
+// Teams
+// TODO: Nested teams API to be deprecated
+const teams = require('./api_teams')
+api.use('/collections/:collectionId/', teams)
+
+module.exports = api
diff --git a/packages/pubsweet-server/src/routes/api_fragments.js b/packages/pubsweet-server/src/routes/api_fragments.js
new file mode 100644
index 0000000000000000000000000000000000000000..2a970ebdec4839c6a4fdae81f3e044cfaf41b16c
--- /dev/null
+++ b/packages/pubsweet-server/src/routes/api_fragments.js
@@ -0,0 +1,311 @@
+'use strict'
+const User = require('../models/User')
+const Collection = require('../models/Collection')
+const Team = require('../models/Team')
+const Fragment = require('../models/Fragment')
+
+const authsome = require('../helpers/authsome')
+const AuthorizationError = require('../errors/AuthorizationError')
+const STATUS = require('http-status-codes')
+
+const express = require('express')
+const api = express.Router()
+const passport = require('passport')
+const sse = require('pubsweet-sse')
+const {
+  objectId,
+  createFilterFromQuery,
+  buildChangeData,
+  fieldSelector,
+  authorizationError,
+  getTeams,
+  applyPermissionFilter,
+  getFragment
+} = require('./util')
+const authBearer = passport.authenticate('bearer', { session: false })
+const authBearerAndPublic = passport.authenticate(['bearer', 'anonymous'], { session: false })
+
+// Create a fragment and update the collection with the fragment
+api.post('/collections/:collectionId/fragments', authBearer, async (req, res, next) => {
+  try {
+    const collection = await Collection.find(req.params.collectionId)
+
+    const object = {
+      path: req.route.path,
+      collection,
+      fragment: req.body
+    }
+
+    let filteredProperties = await applyPermissionFilter({
+      req,
+      target: object,
+      filterable: req.body
+    })
+
+    const fragment = new Fragment(filteredProperties)
+
+    fragment.setOwners([req.user])
+    await fragment.save()
+
+    collection.addFragment(fragment)
+    await collection.save()
+
+    fragment.owners = await User.ownersWithUsername(fragment)
+
+    res.status(STATUS.CREATED).json(fragment)
+    sse.send({ action: 'fragment:create', data: { collection: objectId(collection), fragment } })
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Get all fragments
+api.get('/collections/:collectionId/fragments', authBearerAndPublic, async (req, res, next) => {
+  try {
+    const collection = await Collection.find(req.params.collectionId)
+    let fragments = await collection.getFragments()
+
+    // Filter fragments and their properties
+    fragments = await Promise.all(fragments.map(async fragment => {
+      try {
+        return await applyPermissionFilter({ req, target: fragment })
+      } catch (e) {
+        if (e instanceof AuthorizationError) {
+          return undefined
+        }
+
+        throw e
+      }
+    }))
+
+    fragments = fragments.filter(fragment => fragment !== undefined)
+      .filter(createFilterFromQuery(req.query))
+
+    // Decorate owners with usernames
+    await Promise.all(fragments.map(async fragment => {
+      fragment.owners = await User.ownersWithUsername(fragment)
+    }))
+
+    fragments = fragments.map(fieldSelector(req))
+
+    return res.status(STATUS.OK).json(fragments)
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Retrieve a fragment
+api.get('/collections/:collectionId/fragments/:fragmentId', authBearerAndPublic, async (req, res, next) => {
+  try {
+    const fragment = await getFragment({ req, Collection, Fragment })
+    fragment.owners = await User.ownersWithUsername(fragment)
+    const properties = await applyPermissionFilter({ req, target: fragment })
+
+    return res.status(STATUS.OK).json(properties)
+  } catch (err) {
+    res.status(STATUS.NOT_FOUND).json(err.message)
+  }
+})
+
+// Update a fragment
+api.patch('/collections/:collectionId/fragments/:fragmentId', authBearer, async (req, res, next) => {
+  try {
+    const fragment = await getFragment({ req, Collection, Fragment })
+    const properties = await applyPermissionFilter({
+      req,
+      target: fragment,
+      filterable: req.body
+    })
+
+    await fragment.updateProperties(properties)
+    await fragment.save()
+    fragment.owners = await User.ownersWithUsername(fragment)
+
+    const update = buildChangeData(properties, fragment)
+
+    res.status(STATUS.OK).json(update)
+    sse.send({ action: 'fragment:patch', data: { fragment: objectId(fragment), update } })
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Delete a fragment
+api.delete('/collections/:collectionId/fragments/:fragmentId', authBearer, async (req, res, next) => {
+  try {
+    const collection = await Collection.find(req.params.collectionId)
+    const fragment = await getFragment({ req, Collection, Fragment })
+    await applyPermissionFilter({ req, target: fragment })
+
+    await fragment.delete()
+    collection.removeFragment(fragment)
+    await collection.save()
+
+    res.status(STATUS.OK).json(fragment)
+    sse.send({ action: 'fragment:delete', data: { collection: objectId(collection), fragment } })
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Retrieve teams for a fragment
+api.get('/collections/:collectionId/fragments/:fragmentId/teams', authBearerAndPublic, async (req, res, next) => {
+  try {
+    const fragment = await getFragment({ req, Collection, Fragment })
+    await applyPermissionFilter({ req, target: fragment })
+
+    const teams = (await getTeams({
+      req,
+      Team,
+      id: fragment.id,
+      type: 'fragment'
+    }))
+      .filter(createFilterFromQuery(req.query))
+
+
+    res.status(STATUS.OK).json(teams)
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Get all fragments
+api.get('/fragments', authBearerAndPublic, async (req, res, next) => {
+  try {
+    const fragments = (await Fragment.all())
+      .filter(createFilterFromQuery(req.query))
+
+    // Filter fragments and their properties
+    const propertyFilter = fieldSelector(req)
+    const filteredFragments = await Promise.all(fragments.map(async fragment => {
+      try {
+        return await applyPermissionFilter({ req, target: propertyFilter(fragment) })
+      } catch (e) {
+        if (e instanceof AuthorizationError) {
+          return undefined
+        }
+
+        throw e
+      }
+    }))
+
+    res.status(STATUS.OK).json(filteredFragments)
+  } catch (err) {
+    next(err)
+  }
+})
+
+api.post('/fragments', authBearer, async (req, res, next) => {
+  try {
+    const permission = await authsome.can(req.user, req.method, {
+      path: req.route.path,
+      fragment: req.body
+    })
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, req.body)
+    }
+
+    if (permission.filter) {
+      req.body = permission.filter(req.body)
+    }
+
+    let fragment = new Fragment(req.body)
+
+    fragment.setOwners([req.user])
+    fragment = await fragment.save()
+
+    // How to address this?
+    fragment.owners = await User.ownersWithUsername(fragment)
+
+    res.status(STATUS.CREATED).json(fragment)
+    sse.send({ action: 'fragment:create', data: { fragment } })
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Retrieve a fragment
+api.get('/fragments/:fragmentId', authBearerAndPublic, async (req, res, next) => {
+  try {
+    let fragment = await Fragment.find(req.params.fragmentId)
+    let permission = await authsome.can(req.user, req.method, fragment)
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, fragment)
+    }
+
+    return Fragment.find(req.params.fragmentId).then(fragment => {
+      if (permission.filter) {
+        fragment = permission.filter(fragment)
+      }
+      return res.status(STATUS.OK).json(fragment)
+    })
+  } catch (err) {
+    res.status(STATUS.NOT_FOUND).json(err.message)
+  }
+})
+
+// Update a fragment
+api.patch('/fragments/:fragmentId', authBearer, async (req, res, next) => {
+  try {
+    let fragment = await Fragment.find(req.params.fragmentId)
+    const permission = await authsome.can(req.user, req.method, fragment)
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, fragment)
+    }
+
+    if (permission.filter) {
+      req.body = permission.filter(req.body)
+    }
+
+    fragment.updateProperties(req.body)
+    fragment = await fragment.save()
+    fragment.owners = await User.ownersWithUsername(fragment)
+
+    const update = buildChangeData(req.body, fragment)
+    res.status(STATUS.OK).json(update)
+
+    sse.send({ action: 'fragment:patch', data: { fragment: objectId(fragment), update } })
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Delete a fragment
+api.delete('/fragments/:fragmentId', authBearer, async (req, res, next) => {
+  try {
+    let fragment = await Fragment.find(req.params.fragmentId)
+    const permission = await authsome.can(req.user, req.method, fragment)
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, fragment)
+    }
+
+    fragment = await fragment.delete()
+
+    res.status(STATUS.OK).json(fragment)
+    sse.send({ action: 'fragment:delete', data: { fragment } })
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Retrieve teams for a fragment
+api.get('/fragments/:fragmentId/teams', authBearerAndPublic, async (req, res, next) => {
+  try {
+    let teams = (await getTeams({
+      req: req,
+      Team: Team,
+      id: req.params.fragmentId,
+      type: 'fragment' }))
+      .filter(createFilterFromQuery(req.query))
+
+    res.status(STATUS.OK).json(teams)
+  } catch (err) {
+    next(err)
+  }
+})
+
+module.exports = api
diff --git a/packages/pubsweet-server/src/routes/api_teams.js b/packages/pubsweet-server/src/routes/api_teams.js
new file mode 100644
index 0000000000000000000000000000000000000000..50354943553538b316fd85105d46511b8d3387ad
--- /dev/null
+++ b/packages/pubsweet-server/src/routes/api_teams.js
@@ -0,0 +1,109 @@
+'use strict'
+const STATUS = require('http-status-codes')
+const express = require('express')
+const passport = require('passport')
+
+const authsome = require('../helpers/authsome')
+const Team = require('../models/Team')
+const { createFilterFromQuery, authorizationError } = require('./util')
+
+const authBearer = passport.authenticate('bearer', { session: false })
+const api = express.Router({mergeParams: true})
+
+api.get('/teams', authBearer, async (req, res, next) => {
+  try {
+    const permission = await authsome.can(
+      req.user,
+      req.method,
+      {path: req.path, params: req.params}
+    )
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, req)
+    }
+
+    const teams = (await Team.all())
+      .filter(createFilterFromQuery(req.query))
+
+    res.status(STATUS.OK).json(teams)
+  } catch (err) {
+    next(err)
+  }
+})
+
+api.post('/teams', authBearer, async (req, res, next) => {
+  try {
+    const permission = await authsome.can(req.user, req.method, req.body)
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, req.params)
+    }
+
+    if (permission.filter) {
+      req.body = permission.filter(req.body)
+    }
+
+    let team = new Team(req.body)
+    team = await team.save()
+
+    res.status(STATUS.CREATED).json(team)
+  } catch (err) {
+    next(err)
+  }
+})
+
+api.get('/teams/:teamId', authBearer, async (req, res, next) => {
+  try {
+    let team = await Team.find(req.params.teamId)
+    const permission = await authsome.can(req.user, req.method, team)
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, req.params)
+    }
+
+    if (permission.filter) {
+      team = permission.filter(team)
+    }
+
+    res.status(STATUS.OK).json(team)
+  } catch (err) {
+    next(err)
+  }
+})
+
+api.delete('/teams/:teamId', authBearer, async (req, res, next) => {
+  try {
+    let team = await Team.find(req.params.teamId)
+    const permission = await authsome.can(req.user, req.method, team)
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, req.params)
+    }
+
+    team = await team.delete()
+
+    res.status(STATUS.OK).json(team)
+  } catch (err) {
+    next(err)
+  }
+})
+
+api.patch('/teams/:teamId', authBearer, async (req, res, next) => {
+  try {
+    let team = await Team.find(req.params.teamId)
+    const permission = await authsome.can(req.user, req.method, team)
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, req.params)
+    }
+
+    team = await team.updateProperties(req.body)
+    team = await team.save()
+
+    res.status(STATUS.OK).json(team)
+  } catch (err) {
+    next(err)
+  }
+})
+
+module.exports = api
diff --git a/packages/pubsweet-server/src/routes/api_upload.js b/packages/pubsweet-server/src/routes/api_upload.js
new file mode 100644
index 0000000000000000000000000000000000000000..36b2aca80c3e67ba053b27d92844fec911eb435d
--- /dev/null
+++ b/packages/pubsweet-server/src/routes/api_upload.js
@@ -0,0 +1,30 @@
+const path = require('path')
+const crypto = require('crypto')
+const multer = require('multer')
+const passport = require('passport')
+const express = require('express')
+const api = express.Router()
+
+const authBearer = passport.authenticate('bearer', {session: false})
+
+const storage = multer.diskStorage({
+  destination: 'uploads/',
+  filename: function (req, file, cb) {
+    crypto.pseudoRandomBytes(16, function (err, raw) {
+      if (err) return cb(err)
+
+      cb(null, raw.toString('hex') + path.extname(file.originalname))
+    })
+  }
+})
+
+const upload = multer({
+  storage: storage,
+  limits: {fileSize: 10000000, files: 1}
+})
+
+api.post('/upload', authBearer, upload.single('file'), (req, res, next) => {
+  return res.send('/' + req.file.path)
+})
+
+module.exports = api
diff --git a/packages/pubsweet-server/src/routes/api_users.js b/packages/pubsweet-server/src/routes/api_users.js
new file mode 100644
index 0000000000000000000000000000000000000000..0dcddb53f6a797d1237c512434429bb76d1064c9
--- /dev/null
+++ b/packages/pubsweet-server/src/routes/api_users.js
@@ -0,0 +1,137 @@
+'use strict'
+
+const STATUS = require('http-status-codes')
+const passport = require('passport')
+const express = require('express')
+
+const User = require('../models/User')
+
+const authsome = require('../helpers/authsome')
+const { createFilterFromQuery, authorizationError } = require('./util')
+
+const Team = require('../models/Team')
+const AuthorizationError = require('../errors/AuthorizationError')
+const ValidationError = require('../errors/ValidationError')
+
+const authLocal = passport.authenticate('local', { failWithError: true, session: false })
+const authBearer = passport.authenticate('bearer', { session: false })
+const api = express.Router()
+const authentication = require('../authentication')
+
+// Issue a token
+api.post('/users/authenticate', authLocal, (req, res) => {
+  return res.status(
+    STATUS.CREATED
+  ).json(
+    Object.assign({ token: authentication.token.create(req.user) }, req.user)
+  )
+})
+
+// Verify a token
+api.get('/users/authenticate', authBearer, async (req, res, next) => {
+  try {
+    const user = await User.find(req.user)
+    user.token = req.authInfo.token
+    user.teams = await Promise.all(
+      user.teams.map((teamId) => Team.find(teamId))
+    )
+    return res.status(STATUS.OK).json(user)
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Create a user
+api.post('/users', async (req, res, next) => {
+  try {
+    let user = new User(req.body)
+    if (req.body.admin) throw new ValidationError('invalid property: admin')
+
+    user = await user.save()
+    return res.status(STATUS.CREATED).json(user)
+  } catch (err) {
+    next(err)
+  }
+})
+
+// List users
+api.get('/users', authBearer, async (req, res, next) => {
+  try {
+    const permission = await authsome.can(req.user, req.method, req.path)
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, req.path)
+    }
+
+    const users = (await User.all())
+      .filter(createFilterFromQuery(req.query))
+
+    return res.status(STATUS.OK).json({users: users})
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Get a user
+api.get('/users/:id', authBearer, async (req, res, next) => {
+  try {
+    const user = await User.find(req.params.id)
+    const permission = await authsome.can(req.user, req.method, user)
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, req.path)
+    }
+
+    return res.status(STATUS.OK).json(user)
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Delete a user
+api.delete('/users/:id', authBearer, async (req, res, next) => {
+  try {
+    let user = await User.find(req.params.id)
+    const permission = await authsome.can(req.user, req.method, user)
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, req.path)
+    }
+    user = await user.delete()
+    return res.status(STATUS.OK).json(user)
+  } catch (err) {
+    next(err)
+  }
+})
+
+// Patch a user
+api.patch('/users/:id', authBearer, async (req, res, next) => {
+  try {
+    let user = await User.find(req.params.id)
+    const permission = await authsome.can(req.user, req.method, user)
+
+    if (!permission) {
+      throw authorizationError(req.user, req.method, req.path)
+    }
+
+    // TODO: Move this to the authorization mode
+    const authenticatedUser = await User.find(req.user)
+    if (req.body.admin && !authenticatedUser.admin) {
+      throw new AuthorizationError('only admins can set other admins')
+    }
+
+    if (permission.filter) {
+      req.body = permission.filter(req.body)
+    }
+
+    user = await user.updateProperties(req.body)
+    user = await user.save()
+    user = await User.find(req.params.id)
+
+    return res.status(STATUS.OK).json(user)
+  } catch (err) {
+    next(err)
+  }
+})
+
+module.exports = api
diff --git a/packages/pubsweet-server/src/routes/index.js b/packages/pubsweet-server/src/routes/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..a55a2d974ac7046c52ed6c83f1ece1afb8d85684
--- /dev/null
+++ b/packages/pubsweet-server/src/routes/index.js
@@ -0,0 +1,14 @@
+const express = require('express')
+const router = express.Router({mergeParams: true})
+const path = require('path')
+const config = require('config')
+
+const filename = config.util.getEnv('NODE_ENV') === 'production'
+  ? path.join('.', '_build', 'assets')
+  : path.join('.', 'app')
+
+router.get('*', (req, res, next) => {
+  return res.sendFile('index.html', { root: filename })
+})
+
+module.exports = router
diff --git a/packages/pubsweet-server/src/routes/util.js b/packages/pubsweet-server/src/routes/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..72b60f58a718fa53fc6c053bed981498286dd986
--- /dev/null
+++ b/packages/pubsweet-server/src/routes/util.js
@@ -0,0 +1,123 @@
+const _ = require('lodash')
+const AuthorizationError = require('../errors/AuthorizationError')
+const NotFoundError = require('../errors/NotFoundError')
+const authsome = require('../helpers/authsome')
+
+const Util = {}
+
+Util.authorizationError = (username, operation, object) => {
+    username = username || 'public'
+    const msg = `User ${username} is not allowed to ${operation} ${object}`
+    return new AuthorizationError(msg)
+  },
+
+// Build an object containing only the id
+Util.objectId = object => ({ id: object.id }),
+
+// Build an object containing only the fields of `output` that were in `input`
+// TODO: build a real diff, in case other fields were updated indirectly?
+Util.buildChangeData =  (input, output) => {
+  const data = {}
+
+  Object.keys(input).forEach(key => {
+    // TODO: compare and only add if changed?
+    data[key] = output[key]
+  })
+
+  return data
+}
+
+Util.createFilterFromQuery = query => {
+  const filterPaths = _.difference(_.keys(query), ['fields'])
+  return (item) => {
+    return filterPaths.every(filterPath => {
+      return _.has(item, filterPath) && _.get(item, filterPath) === query[filterPath]
+    })
+  }
+}
+
+Util.fieldSelector = req => {
+  const fields = req.query.fields ? req.query.fields.split(/\s*,\s*/) : null
+
+  return item => fields ? _.pick(item, fields.concat('id', 'rev')) : item
+}
+
+Util.getTeams = async (opts) => {
+  let teams
+  try {
+    teams = await opts.Team.findByField({
+      'object.id': opts.id,
+      'object.type': opts.type
+    })
+
+    teams = await Promise.all(teams.map(async team => {
+      let permission = await authsome.can(opts.req.user, opts.req.method, team)
+      if (permission) {
+        return team
+      }
+    }))
+
+    teams = teams.filter(team => team !== undefined)
+  } catch (err) {
+    if (err instanceof NotFoundError) {
+      teams = []
+    } else {
+      throw err
+    }
+  }
+
+  return teams
+}
+
+/**
+ * Load a fragment from the database, using `:fragmentId` from the route.
+ *
+ * @param {object} opts Options
+ * @param {object} opts.req Request
+ * @param {object} opts.Collection Collection model
+ * @param {object} opts.Fragment Fragment model
+ *
+ * @throws {NotFoundError} Thrown if the Collection doesn't exist, the collection doesn't contain the given Fragment, or the fragment doesn't exist.
+ *
+ * @returns {Promise<Fragment>}
+ */
+Util.getFragment = async opts => {
+  const collection = await opts.Collection.find(opts.req.params.collectionId)
+  const fragmentId = opts.req.params.fragmentId
+  if (!collection.fragments.includes(fragmentId)) {
+    throw new NotFoundError(`collection ${collection.id} does not contain fragment ${fragmentId}`)
+  }
+
+  return opts.Fragment.find(fragmentId)
+}
+
+/**
+ * Check that the current user can perform this action (HTTP verb) on the given object or route.
+ *
+ * If required, the output is filtered.
+ * @param {object} opts Options
+ * @param {object} opts.req Request
+ * @param {*} opts.target The subject of the permissions check
+ * @param {*} opts.filterable An optional thing to be filtered instead of the target
+ * @param {*} opts.authorizationError AuthorizationError
+ *
+ * @throws {AuthorizationError} if permission is not granted.
+ *
+ * @returns {Promise} The (possibly filtered) target, if permission is granted
+ */
+Util.applyPermissionFilter = async (opts) => {
+  const permission = await authsome.can(
+    opts.req.user, opts.req.method, opts.target)
+
+  if (!permission) {
+    throw Util.authorizationError(
+      opts.req.user, opts.req.method, opts.target
+    )
+  }
+
+  const object = opts.filterable || opts.target
+  return permission.filter ? permission.filter(object) : object
+}
+
+
+module.exports = Util
diff --git a/packages/pubsweet-server/src/setup-base.js b/packages/pubsweet-server/src/setup-base.js
new file mode 100644
index 0000000000000000000000000000000000000000..13e6f6315322529bf3d0ce5a41090b84f7a5beb6
--- /dev/null
+++ b/packages/pubsweet-server/src/setup-base.js
@@ -0,0 +1,34 @@
+'use strict'
+
+const Collection = require('./models/Collection')
+const User = require('./models/User')
+
+const logger = require('@pubsweet/logger')
+
+class Setup {
+  static async setup (user, collection) {
+    logger.info('Starting setup')
+
+    let admin = new User({
+      username: user.username,
+      email: user.email,
+      password: user.password,
+      admin: true
+    })
+
+    admin = await admin.save()
+    logger.info('Created admin user: ', admin)
+
+    collection = new Collection(collection)
+    collection.setOwners([admin.id])
+    collection = await collection.save()
+    logger.info('Created initial collection: ', collection.title)
+
+    return {
+      user: admin,
+      collection: collection
+    }
+  }
+}
+
+module.exports = Setup
diff --git a/packages/pubsweet-server/src/setup.js b/packages/pubsweet-server/src/setup.js
new file mode 100755
index 0000000000000000000000000000000000000000..329e400b63484283841a8ecfaad930b8754b1765
--- /dev/null
+++ b/packages/pubsweet-server/src/setup.js
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+'use strict'
+
+const Setup = require('./setup-base.js')
+
+const prompt = require('prompt')
+const argvs = require('minimist')(process.argv.slice(2))
+const colors = require('colors/safe')
+
+const logger = require('@pubsweet/logger')
+
+prompt.override = argvs
+prompt.start()
+
+prompt.message = colors.rainbow('Question!')
+prompt.delimiter = colors.green('><')
+
+// Get two properties from the user: admin email and password
+prompt.get(
+  {
+    properties: {
+      username: {
+        description: colors.magenta("What is the admin's username?")
+      },
+      email: {
+        description: colors.yellow("What is the admin's email?")
+      },
+      password: {
+        description: colors.blue("What is the admin's password?")
+      },
+      collectionTitle: {
+        description: colors.cyan("What is the collection's title?")
+      }
+    }
+  },
+  (err, result) => {
+    if (err) return logger.info(err)
+
+    logger.info('Received the following answers:')
+    logger.info('  username: ' + result.username)
+    logger.info('  email: ' + result.email)
+    logger.info('  password: ' + result.password)
+    logger.info('  collection: ' + result.collectionTitle)
+
+    // Setup
+    let admin = {
+      username: result.username,
+      email: result.email,
+      password: result.password
+    }
+
+    Setup.setup(
+      admin,
+      {title: result.collectionTitle}
+    ).then(
+      () => { logger.info(colors.rainbow('Your PubSweet is now ready!')) }
+    )
+  }
+)
diff --git a/packages/pubsweet-server/src/start-server.js b/packages/pubsweet-server/src/start-server.js
new file mode 100644
index 0000000000000000000000000000000000000000..20d4239dc35a7f2503931192012b1f4222a4ba82
--- /dev/null
+++ b/packages/pubsweet-server/src/start-server.js
@@ -0,0 +1,17 @@
+const http = require('http')
+const config = require('config')
+const Promise = require('bluebird')
+const logger = require('@pubsweet/logger')
+
+const startServer = async app => {
+  const port = config['pubsweet-server'].port || /*deprecated:-->*/process.env.PORT || 3000
+  app.set('port', port)
+  const server = http.createServer(app)
+  logger.info(`Starting HTTP server`)
+  const startListening = Promise.promisify(server.listen, { context: server })
+  await startListening(port)
+  logger.info(`App is listening on port ${port}`)
+  return server
+}
+
+module.exports = startServer
diff --git a/packages/pubsweet-server/test/.eslintrc b/packages/pubsweet-server/test/.eslintrc
new file mode 100644
index 0000000000000000000000000000000000000000..60bdfea9770c249066ccb79beaa09664a06a0846
--- /dev/null
+++ b/packages/pubsweet-server/test/.eslintrc
@@ -0,0 +1,6 @@
+{
+  "env": {
+    "jest": true,
+    "jasmine": true
+  }
+}
diff --git a/packages/pubsweet-server/test/api_admin_test.js b/packages/pubsweet-server/test/api_admin_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..bd61f7c097294a43a35fccb1b999cb5b2ab21f4e
--- /dev/null
+++ b/packages/pubsweet-server/test/api_admin_test.js
@@ -0,0 +1,84 @@
+const STATUS = require('http-status-codes')
+
+const createBasicCollection = require('./helpers/basic_collection')
+const cleanDB = require('./helpers/db_cleaner')
+const fixtures = require('./fixtures/fixtures')
+
+const User = require('../src/models/User')
+const Fragment = require('../src/models/Fragment')
+
+const api = require('./helpers/api')
+
+describe('admin api', () => {
+  let otherUser
+  let collection
+  let fragment
+
+  beforeEach(() => {
+    // Create collection with admin user and one non-admin user
+    return cleanDB().then(
+      createBasicCollection
+    ).then(
+      (userAndCol) => {
+        collection = userAndCol.collection
+      }
+    ).then(
+      () => {
+        // Create another user without any roles
+        otherUser = new User(fixtures.updatedUser)
+        return otherUser.save()
+      }
+    ).then(
+      () => {
+        // Create fragment and add fragment to collection
+        fragment = new Fragment(fixtures.fragment)
+        fragment.setOwners([otherUser.id])
+        return fragment.save().then(
+          fragment => {
+            collection.addFragment(fragment)
+            return collection.save()
+          }
+        )
+      }
+    )
+  })
+
+  afterEach(cleanDB)
+
+  it('creates a fragment in the protected collection if authenticated', () => {
+    return api.users.authenticate.post(
+      fixtures.user
+    ).then(
+      token => api.fragments.post({
+        fragment: fixtures.fragment, collection, token
+      })
+    ).then(
+      res => expect(res.body.source).toEqual(fixtures.fragment.source)
+    )
+  })
+
+  it('reads all fragments', () => {
+    return api.users.authenticate.post(
+      fixtures.user
+    ).then(
+      token => api.fragments.get({ collection, token })
+    ).then(
+      res => expect(res.body.length).toEqual(1)
+    )
+  })
+
+  it('updates a fragment owned by someone else', () => {
+    const updatedFragment = Object.assign({}, fragment, fixtures.updatedFragment)
+
+    return api.users.authenticate.post(
+      fixtures.user
+    ).then(
+      token => api.fragments.patch({
+        fragmentId: fragment.id,
+        update: updatedFragment,
+        collection,
+        token
+      }).expect(STATUS.OK)
+    )
+  })
+})
diff --git a/packages/pubsweet-server/test/api_authenticated_test.js b/packages/pubsweet-server/test/api_authenticated_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..4c169f0a91d99df4a2b1b4c1a9efb56d6dd7ee15
--- /dev/null
+++ b/packages/pubsweet-server/test/api_authenticated_test.js
@@ -0,0 +1,216 @@
+const STATUS = require('http-status-codes')
+
+const createBasicCollection = require('./helpers/basic_collection')
+const dbCleaner = require('./helpers/db_cleaner')
+const api = require('./helpers/api')
+const setTeamForCollection = require('./helpers/set_team')
+const fixtures = require('./fixtures/fixtures')
+
+const Fragment = require('../src/models/Fragment')
+const User = require('../src/models/User')
+
+describe('authenticated api', function () {
+  let otherUser
+  let user
+  let collection
+
+  beforeEach(async () => {
+    // Create collection with admin user and one non-admin user
+    await dbCleaner()
+    ;({user, collection} = await createBasicCollection())
+    // Create another user without any roles
+    otherUser = new User(fixtures.updatedUser)
+    await otherUser.save()
+  })
+
+  it(`fails to create a fragment in a protected
+    collection if authenticated as user without permissions`, () => {
+      return api.users.authenticate.post(
+        fixtures.updatedUser
+      ).then(
+        (token) => {
+          return api.fragments.post({
+            fragment: fixtures.fragment, collection, token
+          }).expect(
+            STATUS.FORBIDDEN
+          )
+        }
+      )
+    })
+
+  describe('a non-admin user with a contributor role', () => {
+    beforeEach(() => {
+      return setTeamForCollection(
+        [otherUser.id],
+        collection,
+        fixtures.contributorTeam
+      )
+    })
+
+    afterEach(() => {
+      return setTeamForCollection(
+        [],
+        collection,
+        fixtures.contributorTeam
+      )
+    })
+
+    it('creates a fragment in a protected collection', () => {
+      return api.users.authenticate.post(
+        fixtures.updatedUser
+      ).then(
+        token => {
+          return api.fragments.post({
+            fragment: fixtures.fragment, collection, token
+          }).expect(
+            STATUS.CREATED
+          )
+        }
+      ).then(
+        res => {
+          expect(res.body.owners).toContainEqual({
+            id: otherUser.id,
+            username: otherUser.username
+          })
+        }
+      )
+    })
+
+    describe('a fragment owned by the same user', () => {
+      let fragment
+
+      beforeEach(async () => {
+        fragment = new Fragment(fixtures.fragment)
+        fragment.setOwners([otherUser.id])
+        fragment = await fragment.save()
+
+        collection.addFragment(fragment)
+        collection = await collection.save()
+      })
+
+      afterEach(async () => {
+        fragment = await fragment.delete()
+        collection.removeFragment(fragment)
+        collection = await collection.save()
+      })
+
+      it('updates a fragment in a protected collection if an owner', () => {
+        return api.users.authenticate.post(
+          fixtures.updatedUser
+        ).then(
+          (token) => {
+            return api.fragments.patch({
+              fragmentId: fragment.id,
+              update: {...fixtures.updatedFragment, rev: fragment.rev},
+              collection,
+              token
+            }).expect(
+              STATUS.OK
+            )
+          }
+        )
+      })
+    })
+
+    describe('actions on a fragment owned by a different user', () => {
+      let fragment
+
+      beforeEach(async () => {
+        const Fragment = require('../src/models/Fragment')
+        fragment = new Fragment(fixtures.fragment)
+        fragment.setOwners([user.id])
+        await fragment.save()
+        collection.addFragment(fragment)
+        await collection.save()
+      })
+
+      afterEach(async () => {
+        await fragment.delete()
+        collection.removeFragment(fragment)
+        await collection.save()
+      })
+
+      it('cannot read a fragment in a protected collection if it is not published', () => {
+        return api.users.authenticate.post(
+          fixtures.updatedUser
+        ).then(
+          token => api.fragments.get({
+            collection: collection, token: token
+          }).expect(STATUS.OK)
+        ).then(
+          res => expect(res.body).toEqual([])
+        )
+      })
+
+      it('cannot update a fragment in a protected collection', async () => {
+        const token = await api.users.authenticate.post(fixtures.updatedUser)
+        return api.fragments.patch({
+          fragmentId: fragment.id,
+          update: fixtures.updatedFragment,
+          collection,
+          token
+        }).expect(
+          STATUS.FORBIDDEN
+        )
+      })
+    })
+  })
+
+  describe('a non-admin user with a reader role', () => {
+    beforeEach(() => {
+      return setTeamForCollection(
+        [otherUser.id],
+        collection,
+        fixtures.readerTeam
+      )
+    })
+
+    afterEach(() => {
+      return setTeamForCollection(
+        [],
+        collection,
+        fixtures.readerTeam
+      )
+    })
+
+    it('can not create a fragment', () => {
+      return api.users.authenticate.post(
+        fixtures.updatedUser
+      ).then(
+        token => {
+          return api.fragments.post({
+            fragment: fixtures.fragment, collection, token
+          }).expect(
+            STATUS.FORBIDDEN
+          )
+        }
+      )
+    })
+
+    it('can read a fragment', function () {
+      return api.users.authenticate.post(
+        fixtures.updatedUser
+      ).then(
+        token => {
+          return api.fragments.get({ collection, token })
+        }
+      )
+    })
+  })
+
+  it('fails to create a fragment in the protected collection if not authenticated', function () {
+    return api.fragments.post({
+      fragment: fixtures.fragment, collection
+    }).expect(
+      STATUS.UNAUTHORIZED
+    )
+  })
+
+  it('fails to create a fragment in the protected collection if authentication wrong', function () {
+    return api.fragments.post({
+      fragment: fixtures.fragment, collection, token: 'wrong'
+    }).expect(
+      STATUS.UNAUTHORIZED
+    )
+  })
+})
diff --git a/packages/pubsweet-server/test/api_collections_test.js b/packages/pubsweet-server/test/api_collections_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..394bbe405fe77597703b933125c438fca2ad3677
--- /dev/null
+++ b/packages/pubsweet-server/test/api_collections_test.js
@@ -0,0 +1,789 @@
+const STATUS = require('http-status-codes')
+
+const User = require('../src/models/User')
+
+const cleanDB = require('./helpers/db_cleaner')
+const fixtures = require('./fixtures/fixtures')
+
+const api = require('./helpers/api')
+
+const authenticateAdmin = () => {
+  return api.users.authenticate.post(fixtures.adminUser)
+}
+
+const authenticateUser = () => {
+  return api.users.authenticate.post(fixtures.user)
+}
+
+describe('Collections API', () => {
+  beforeEach(() => {
+    return cleanDB().then(() => Promise.all([
+      new User(fixtures.adminUser).save(),
+      new User(fixtures.user).save()
+    ]))
+  })
+
+  describe('admin', () => {
+    it('should display an initially empty list of collections', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // list all the collections
+      const collections = await api.collections.list(adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(collections).toEqual([])
+    })
+
+    it('should allow an admin user to create a collection (without filtering properties)', async () => {
+      const adminToken = await authenticateAdmin()
+
+      const collection = await api.collections.create(
+        Object.assign({}, fixtures.collection, {filtered: 'example'}), adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+      expect(collection.type).toEqual(fixtures.collection.type)
+      expect(collection.title).toEqual(fixtures.collection.title)
+      expect(collection.filtered).toEqual('example')
+    })
+
+    it('should allow an admin user to list all collections', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+
+      // list all the collections
+      const collections = await api.collections.list(adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(collections).toHaveLength(1)
+
+      const collection = collections[0]
+      expect(collection.type).toEqual(fixtures.collection.type)
+      expect(collection.title).toEqual(fixtures.collection.title)
+    })
+
+    it('should allow an admin user to filter collections with query params', async () => {
+      const adminToken = await authenticateAdmin()
+
+      await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+      await api.collections.create(fixtures.collection2, adminToken)
+        .expect(STATUS.CREATED)
+
+      const query1 = {'owners.0.username': 'admin'}
+
+      const collections1 = await api.collections.list(adminToken, query1)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(collections1).toHaveLength(2)
+
+      const query2 = {'title': fixtures.collection.title}
+
+      const collections2 = await api.collections.list(adminToken, query2)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(collections2).toHaveLength(1)
+    })
+
+    it('should return empty array if filtering on non-existent property', async () => {
+      const adminToken = await authenticateAdmin()
+
+      await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+      await api.collections.create(fixtures.collection2, adminToken)
+        .expect(STATUS.CREATED)
+
+      const query = {'nonexistent': 'x'}
+
+      const collections = await api.collections.list(adminToken, query)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(collections).toHaveLength(0)
+    })
+
+    it('collection.owners should be augmented with usernames (both singular and plural endpoints)', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // retrieve the collection
+      const retrievedCollection = await api.collections.retrieve(collection.id, adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(retrievedCollection.owners[0]).toHaveProperty('username', fixtures.adminUser.username)
+
+      // list all the collections
+      const collections = await api.collections.list(adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(collections[0].owners[0]).toHaveProperty('username', fixtures.adminUser.username)
+    })
+
+    it('should allow an admin user to retrieve only some fields of collections', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+
+      // list all the collections, asking for all fields
+      const collections = await api.collections.list(adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(collections).toHaveLength(1)
+
+      const collection = collections[0]
+      expect(collection).toHaveProperty('id')
+      expect(collection).toHaveProperty('type')
+      expect(collection).toHaveProperty('title')
+      expect(collection).toHaveProperty('created')
+
+      // list all the collections, asking for only two fields
+      const filteredCollections = await api.collections.list(adminToken, { fields: ['type', 'title'] })
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(filteredCollections).toHaveLength(1)
+
+      const filteredCollection = filteredCollections[0]
+      expect(filteredCollection).toHaveProperty('id') // the ID field must always be present
+      expect(filteredCollection).toHaveProperty('type')
+      expect(filteredCollection).toHaveProperty('title')
+      expect(filteredCollection).not.toHaveProperty('created')
+    })
+
+    it('should allow an admin user to update a collection (without filtering properties)', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // update the collection
+      const title = 'Updated title'
+      const filtered = 'example'
+
+      const result = await api.collections.update(collection.id, { title, filtered, rev: collection.rev }, adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(result.title).toEqual(title)
+      expect(result.filtered).toEqual('example')
+    })
+
+    it('should return conflict error if collection has changed before update', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      const otherUserUpdate = { title: 'Causes conflict', filtered: 'example' }
+      const Collection = require('../src/models/Collection.js')
+      const collectionHandle = new Collection(Object.assign({}, collection, otherUserUpdate))
+      collectionHandle.save()
+
+      const myUpdate = { title: 'Outdated revision', filtered: 'example', rev: collection.rev }
+
+      await api.collections.update(collection.id, myUpdate, adminToken)
+        .expect(STATUS.CONFLICT)
+    })
+
+    it('should return bad request if rev property is not provided with an update', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      const myUpdate = { title: 'Untagged revision', filtered: 'example' }
+
+      await api.collections.update(collection.id, myUpdate, adminToken)
+        .expect(STATUS.BAD_REQUEST)
+    })
+
+    it('should allow an admin user to update a collection with some fragments', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create some fragments
+      await api.collections.createFragment(collection.id, fixtures.fragment, adminToken)
+      await api.collections.createFragment(collection.id, fixtures.updatedFragment, adminToken)
+
+      const result = await api.collections.retrieve(collection.id, adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(result.fragments).toHaveLength(2)
+    })
+
+    it('should allow an admin user to delete a collection', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // delete the collection
+      await api.collections.delete(collection.id, adminToken)
+        .expect(STATUS.OK)
+
+      // try to retrieve the deleted collection
+      await api.collections.retrieve(collection.id, adminToken)
+        .expect(STATUS.NOT_FOUND)
+    })
+
+    it('should allow an admin user to list fragments in a collection', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // list fragments in this collection
+      const fragments = await api.collections.listFragments(collection.id, adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(fragments).toHaveLength(0)
+    })
+
+    it('fragment.owners should be returned augmented with usernames (both plural and singular endpoints)', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create a fragment in the collection
+      const fragment = await api.collections.createFragment(collection.id, fixtures.fragment, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // retrieve the created fragment
+      const retrievedFragment = await api.collections.retrieveFragment(collection.id, fragment.id, adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(retrievedFragment.owners[0]).toHaveProperty('username', fixtures.adminUser.username)
+
+      // list the created fragments
+      const fragments = await api.collections.listFragments(collection.id, adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(fragments[0].owners[0]).toHaveProperty('username', fixtures.adminUser.username)
+    })
+
+    it('should allow an admin user to create a fragment in a collection', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create a fragment in the collection
+      const fragment = await api.collections.createFragment(collection.id, fixtures.fragment, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // retrieve the created fragment
+      const retrievedFragment = await api.collections.retrieveFragment(collection.id, fragment.id, adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(retrievedFragment.presentation).toEqual(fixtures.fragment.presentation)
+    })
+
+    it('should allow an admin user to update a fragment in a collection', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create a fragment in the collection
+      const fragment = await api.collections.createFragment(collection.id, fixtures.fragment, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      const source = '<blog>test</blog>'
+
+      // update the fragment
+      await api.collections.updateFragment(collection.id, fragment.id, { source, rev: fragment.rev }, adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      // retrieve the updated fragment
+      const retrievedFragment = await api.collections.retrieveFragment(collection.id, fragment.id, adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(retrievedFragment.source).toEqual(source)
+    })
+
+    it('should allow an admin user to delete a fragment in a collection', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create a fragment in the collection
+      const fragment = await api.collections.createFragment(collection.id, fixtures.fragment, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create another fragment
+      await api.collections.createFragment(collection.id, fixtures.fragment, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // delete the first fragment
+      await api.collections.deleteFragment(collection.id, fragment.id, adminToken)
+        .expect(STATUS.OK)
+
+      // retrieve the updated fragment
+      await api.collections.retrieveFragment(collection.id, fragment.id, adminToken)
+        .expect(STATUS.NOT_FOUND)
+
+      // list fragments in this collection
+      const fragments = await api.collections.listFragments(collection.id, adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(fragments).toHaveLength(1)
+    })
+
+    it('should allow an admin user to retrieve only some fields when listing fragments in a collection', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create a fragment in the collection
+      await api.collections.createFragment(collection.id, fixtures.fragment, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // list fragments in this collection
+      const fragments = await api.collections.listFragments(collection.id, adminToken)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(fragments).toHaveLength(1)
+
+      const fragment = fragments[0]
+
+      expect(fragment).toHaveProperty('id') // the ID field must always be present
+      expect(fragment).toHaveProperty('type')
+      expect(fragment).toHaveProperty('presentation')
+      expect(fragment).toHaveProperty('source')
+
+      // list fragments in this collection, requesting only certain fields
+      const filteredFragments = await api.collections.listFragments(collection.id, adminToken, { fields: ['type', 'presentation'] })
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(filteredFragments).toHaveLength(1)
+
+      const filteredFragment = filteredFragments[0]
+
+      expect(filteredFragment).toHaveProperty('id') // the ID field must always be present
+      expect(filteredFragment).toHaveProperty('type')
+      expect(filteredFragment).toHaveProperty('presentation')
+      expect(filteredFragment).not.toHaveProperty('source')
+    })
+
+    it('should allow admin to retrieve teams for a fragment', async () => {
+      const token = await authenticateAdmin()
+
+      // create a collection
+      const collection = await api.collections.create(fixtures.collection, token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create a fragment in the collection
+      const fragment = await api.collections.createFragment(collection.id, fixtures.fragment, token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create the teams
+      await api.teams.post(fixtures.readerTeam, token)
+      const teamFixture = Object.assign({}, fixtures.contributorTeam, {object: {type: 'fragment', id: fragment.id}})
+      await api.teams.post(teamFixture, token)
+
+      // retrieve the fragment team(s)
+      const teams = await api.collections.listFragmentTeams(collection.id, fragment.id, token)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(teams).toHaveLength(1)
+      expect(teams[0]).toMatchObject({
+        name: 'My contributors',
+        object: {
+          type: 'fragment'
+        }
+      })
+    })
+  })
+
+  describe('anonymous', () => {
+    it('should not allow an anonymous user to create a collection', async () => {
+      await api.collections.create(fixtures.collection)
+        .expect(STATUS.UNAUTHORIZED)
+    })
+
+    it('should not allow an anonymous user to create a fragment', async () => {
+      await api.collections.createFragment(fixtures.collection, fixtures.fragment)
+        .expect(STATUS.UNAUTHORIZED)
+    })
+  })
+
+  describe('user', () => {
+    it('should allow a user to list all of the collections', async () => {
+      const token = await authenticateUser()
+
+      await api.collections.create(fixtures.collection, token)
+        .expect(STATUS.CREATED)
+
+      const collections = await api.collections.list(token)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(collections).toHaveLength(1)
+    })
+
+    it('should allow a user to create a collection', async () => {
+      const token = await authenticateUser()
+
+      // create the collection
+      await api.collections.create(fixtures.collection, token)
+        .expect(STATUS.CREATED)
+    })
+
+    it('should allow a user to retrieve a collection', async () => {
+      const token = await authenticateUser()
+
+      // create the collection
+      const collection = await api.collections.create(fixtures.collection, token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // retrieve the collection
+      await api.collections.retrieve(collection.id, token)
+        .expect(STATUS.OK)
+    })
+
+    it('should allow a user to retrieve teams for a collection', async () => {
+      const token = await authenticateUser()
+
+      // create the collection
+      const collection = await api.collections.create(fixtures.collection, token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create the teams
+      const teamFixture = Object.assign({}, fixtures.contributorTeam, {object: {type: 'collection', id: collection.id}})
+      await api.teams.post(teamFixture, token)
+      await api.teams.post(fixtures.readerTeam, token)
+
+      // retrieve the collection team(s)
+      const teams = await api.collections.listTeams(collection.id, token)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(teams).toHaveLength(1)
+      expect(teams[0]).toMatchObject({
+        name: 'My contributors',
+        object: {
+          type: 'collection'
+        }
+      })
+    })
+
+    it('should allow a user to update a collection they own', async () => {
+      const token = await authenticateUser()
+
+      // create the collection
+      const collection = await api.collections.create(fixtures.collection, token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // update the collection
+      const title = 'Updated title'
+
+      const result = await api.collections.update(collection.id, { title, rev: collection.rev }, token)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(result.title).toEqual(title)
+    })
+
+    it('should not allow a user to update a collection they do not own', async () => {
+      const userToken = await authenticateUser()
+      const adminToken = await authenticateAdmin()
+
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      const title = 'Updated title'
+
+      await api.collections.update(collection.id, { title, rev: collection.rev }, userToken)
+        .expect(STATUS.FORBIDDEN)
+    })
+
+    it('should allow a user to delete any collection they own', async () => {
+      const token = await authenticateUser()
+
+      const collection = await api.collections.create(fixtures.collection, token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      expect(collection.title).toEqual(fixtures.collection.title)
+
+      await api.collections.delete(collection.id, token)
+        .expect(STATUS.OK)
+
+      await api.collections.retrieve(collection.id, token)
+        .expect(STATUS.NOT_FOUND)
+    })
+
+    it('should not allow a user to delete a collection they do not own', async () => {
+      const userToken = await authenticateUser()
+      const adminToken = await authenticateAdmin()
+
+      const collection = await api.collections.create(fixtures.collection, adminToken)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      await api.collections.delete(collection.id, userToken)
+        .expect(STATUS.FORBIDDEN)
+    })
+
+    it('should filter collection properties based on authorization on creation', async () => {
+      const token = await authenticateUser()
+
+      let collection = await api.collections.create(
+        Object.assign({}, fixtures.collection, { filtered: 'example' }), token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      collection = await api.collections.retrieve(collection.id, token).expect(STATUS.OK)
+
+      expect(Object.keys(collection)).not.toContain('filtered')
+    })
+
+    it('should return empty array if trying to filter with query params on properties for which user lacks authorization', async () => {
+      const token = await authenticateUser()
+
+      await api.collections.create(
+        Object.assign({}, fixtures.collection, { filtered: 'example' }), token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      const collections = await api.collections.list(token, { filtered: 'example' })
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(collections).toHaveLength(0)
+    })
+
+    it('should filter collection properties based on authorization on update', async () => {
+      const token = await authenticateUser()
+
+      let collection = await api.collections.create(fixtures.collection, token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      const filtered = 'example'
+
+      collection = await api.collections.update(collection.id, { filtered, rev: collection.rev }, token)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      // collection = await api.collections.retrieve(collection.id, token).expect(STATUS.OK)
+
+      expect(Object.keys(collection)).not.toContain('filtered')
+    })
+
+    it('should delete teams associated with a collection when the collection is deleted', async () => {
+      // NOTE: need to authenticate as admin to be able to retrieve teams
+      const token = await authenticateAdmin()
+
+      const collection = await api.collections.create(fixtures.collection, token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      const teamData = {
+        name: 'bar',
+        teamType: {
+          name: 'Test',
+          permissions: 'read'
+        },
+        object: {
+          type: 'collection',
+          id: collection.id
+        }
+      }
+
+      await api.teams.post(teamData, token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create a different team on a different collection
+
+      const otherCollection = await api.collections.create(fixtures.collection, token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      const otherTeamData = {
+        name: 'foo',
+        teamType: {
+          name: 'Foo',
+          permissions: 'read'
+        },
+        object: {
+          type: 'collection',
+          id: otherCollection.id
+        }
+      }
+
+      await api.teams.post(otherTeamData, token)
+        .expect(STATUS.CREATED)
+
+      const collectionTeams = await api.collections.listTeams(collection.id, token)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(collectionTeams).toHaveLength(1)
+
+      const teamsBeforeDeletion = await api.teams.list(token)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(teamsBeforeDeletion).toHaveLength(2)
+
+      await api.collections.delete(collection.id, token)
+        .expect(STATUS.OK)
+
+      await api.collections.retrieve(collection.id, token)
+        .expect(STATUS.NOT_FOUND)
+
+      // await api.collections.listTeams(collection.id, token)
+      //   .expect(STATUS.NOT_FOUND)
+
+      const teamsAfterDeletion = await api.teams.list(token)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(teamsAfterDeletion).toHaveLength(1)
+    })
+
+    it('should delete teams associated with a fragment when the fragment is deleted', async () => {
+      // NOTE: need to authenticate as admin to be able to retrieve teams
+      const token = await authenticateAdmin()
+
+      const collection = await api.collections.create(fixtures.collection, token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      const fragment = await api.fragments.post({fragment: fixtures.fragment, collection, token})
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      const teamData = {
+        name: 'bar',
+        teamType: {
+          name: 'Test',
+          permissions: 'read'
+        },
+        object: {
+          type: 'fragment',
+          id: fragment.id
+        }
+      }
+
+      await api.teams.post(teamData, token)
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create a different team on a different fragment
+
+      const otherFragment = await api.fragments.post({fragment: fixtures.fragment, collection, token})
+        .expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      const otherTeamData = {
+        name: 'foo',
+        teamType: {
+          name: 'Foo',
+          permissions: 'read'
+        },
+        object: {
+          type: 'fragment',
+          id: otherFragment.id
+        }
+      }
+
+      await api.teams.post(otherTeamData, token)
+        .expect(STATUS.CREATED)
+
+      const fragmentTeams = await api.collections.listFragmentTeams(collection.id, fragment.id, token)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(fragmentTeams).toHaveLength(1)
+
+      const teamsBeforeDeletion = await api.teams.list(token)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(teamsBeforeDeletion).toHaveLength(2)
+
+      await api.collections.deleteFragment(collection.id, fragment.id, token)
+        .expect(STATUS.OK)
+
+      await api.collections.retrieveFragment(collection.id, fragment.id, token)
+        .expect(STATUS.NOT_FOUND)
+
+      // await api.collections.listFragmentTeams(collection.id, fragment.id, token)
+      //   .expect(STATUS.NOT_FOUND)
+
+      const teamsAfterDeletion = await api.teams.list(token)
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(teamsAfterDeletion).toHaveLength(1)
+    })
+  })
+})
diff --git a/packages/pubsweet-server/test/api_file_upload_test.js b/packages/pubsweet-server/test/api_file_upload_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..2d056000f95e290efdc9ee51609bd5749ab03fdd
--- /dev/null
+++ b/packages/pubsweet-server/test/api_file_upload_test.js
@@ -0,0 +1,46 @@
+const fs = require('fs')
+const path = require('path')
+const api = require('./helpers/api')
+const fixtures = require('./fixtures/fixtures')
+const cleanDB = require('./helpers/db_cleaner')
+const User = require('../src/models/User')
+
+function fileName (name) {
+  return path.join(__dirname, 'fixtures', name)
+}
+
+function file (name) {
+  return fs.createReadStream(fileName(name))
+}
+
+function fileBuffer (name) {
+  return fs.readFileSync(fileName(name))
+}
+
+const authenticateUser = () => {
+  return api.users.authenticate.post(fixtures.user)
+}
+
+describe('File upload/download', () => {
+  beforeEach(async () => {
+    await cleanDB()
+    await new User(fixtures.user).save()
+  })
+
+  it('should upload a file and preserve the extension and serve the file (if authenticated)', async () => {
+    const userToken = await authenticateUser()
+
+    const res = await api.upload.post(file('fixture.jpg'), userToken)
+    expect(res.statusCode).toBe(200)
+    expect(path.extname(res.text)).toBe('.jpg')
+
+    const download = await api.upload.get(res.text)
+    expect(download.body.equals(fileBuffer('fixture.jpg'))).toBe(true)
+  })
+
+  it('should serve a 404 if the file does not exist', async () => {
+    const res = await api.upload.get('/uploads/nofilehere')
+
+    expect(res.statusCode).toBe(404)
+  })
+})
diff --git a/packages/pubsweet-server/test/api_fragments_test.js b/packages/pubsweet-server/test/api_fragments_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..2d231263e29da2b2d31df8cccdc5eac33f226d2d
--- /dev/null
+++ b/packages/pubsweet-server/test/api_fragments_test.js
@@ -0,0 +1,161 @@
+const STATUS = require('http-status-codes')
+
+const User = require('../src/models/User')
+
+const cleanDB = require('./helpers/db_cleaner')
+const fixtures = require('./fixtures/fixtures')
+
+const api = require('./helpers/api')
+
+const authenticateAdmin = () => {
+  return api.users.authenticate.post(fixtures.adminUser)
+}
+
+describe('Fragments API', () => {
+  beforeEach(() => {
+    return cleanDB().then(() => Promise.all([
+      new User(fixtures.adminUser).save(),
+      new User(fixtures.user).save()
+    ]))
+  })
+
+  describe('admin', () => {
+    it('should have an initially empty list of fragments', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // list all the collections
+      const collections = await api.fragments.get({ token: adminToken })
+        .expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(collections).toEqual([])
+    })
+
+    it('should allow an admin user to create a fragment', async () => {
+      const adminToken = await authenticateAdmin()
+
+      const fragment = await api.fragments.post({
+        fragment: {...fixtures.fragment, filtered: 'example'},
+        token: adminToken
+      }).expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      expect(fragment.type).toEqual(fixtures.fragment.type)
+      expect(fragment.title).toEqual(fixtures.fragment.title)
+      expect(fragment.filtered).toEqual('example')
+    })
+
+    it('should allow an admin user to update a fragment (without filtering properties)', async () => {
+      const adminToken = await authenticateAdmin()
+
+      const fragment = await api.fragments.post({
+        fragment: fixtures.fragment,
+        token: adminToken
+      }).expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // update the collection
+      const title = 'Updated title'
+      const filtered = 'example'
+
+      const result = await api.fragments.patch({
+        fragmentId: fragment.id,
+        update: { title, filtered, rev: fragment.rev },
+        token: adminToken
+      }).expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(result.title).toEqual(title)
+      expect(result.filtered).toEqual('example')
+    })
+
+    it('should allow an admin user to delete a fragment', async () => {
+      const adminToken = await authenticateAdmin()
+
+      // create a collection
+      const fragment = await api.fragments.post({
+        fragment: fixtures.fragment, token: adminToken
+      }).expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // delete the collection
+      await api.fragments.delete({
+        fragmentId: fragment.id,
+        token: adminToken
+      }).expect(STATUS.OK)
+
+      // try to retrieve the deleted collection
+      await api.fragments.get({
+        fragmentId: fragment.id,
+        token: adminToken
+      }).expect(STATUS.NOT_FOUND)
+    })
+
+    it('should allow an admin user to retrieve only some fields when getting fragments', async () => {
+      const adminToken = await authenticateAdmin()
+
+      await api.fragments.post({
+        fragment: fixtures.fragment,
+        token: adminToken
+      }).expect(STATUS.CREATED)
+
+      const fragments = await api.fragments.get({
+        token: adminToken
+      }).expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(fragments).toHaveLength(1)
+
+      const fragment = fragments[0]
+
+      expect(fragment).toHaveProperty('id') // the ID field must always be present
+      expect(fragment).toHaveProperty('type')
+      expect(fragment).toHaveProperty('presentation')
+      expect(fragment).toHaveProperty('source')
+
+      // list fragments in this collection, requesting only certain fields
+      const filteredFragments = await api.fragments.get({
+        token: adminToken,
+        fields: ['type', 'presentation']
+      }).expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(filteredFragments).toHaveLength(1)
+
+      const filteredFragment = filteredFragments[0]
+
+      expect(filteredFragment).toHaveProperty('id') // the ID field must always be present
+      expect(filteredFragment).toHaveProperty('type')
+      expect(filteredFragment).toHaveProperty('presentation')
+      expect(filteredFragment).not.toHaveProperty('source')
+    })
+
+    it('should allow admin to retrieve teams for a fragment', async () => {
+      const token = await authenticateAdmin()
+
+      const fragment = await api.fragments.post({
+        fragment: fixtures.fragment,
+        token
+      }).expect(STATUS.CREATED)
+        .then(res => res.body)
+
+      // create the teams
+      const teamFixture = Object.assign({}, fixtures.contributorTeam, {object: {type: 'fragment', id: fragment.id}})
+      await api.teams.post(teamFixture, token)
+
+      // retrieve the fragment team(s)
+      const teams = await api.fragments.teams({
+        fragmentId: fragment.id, token
+      }).expect(STATUS.OK)
+        .then(res => res.body)
+
+      expect(teams).toHaveLength(1)
+      expect(teams[0]).toMatchObject({
+        name: 'My contributors',
+        object: {
+          type: 'fragment'
+        }
+      })
+    })
+  })
+})
diff --git a/packages/pubsweet-server/test/api_locals_test.js b/packages/pubsweet-server/test/api_locals_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..a89320c830465e241cc3088b3292c341b2a44b41
--- /dev/null
+++ b/packages/pubsweet-server/test/api_locals_test.js
@@ -0,0 +1,23 @@
+const User = require('../src/models/User')
+const fixtures = require('./fixtures/fixtures')
+const cleanDB = require('./helpers/db_cleaner')
+const api = require('../src').configureApp(require('express')())
+
+describe('api/app locals', () => {
+  beforeEach(async () => {
+    await cleanDB()
+    return new User(fixtures.adminUser).save()
+  })
+
+  afterEach(cleanDB)
+
+  it('exposes models', async () => {
+    expect(api.locals.models.User.type).toEqual('user')
+    expect(api.locals.models.Team.type).toEqual('team')
+    expect(api.locals.models.Fragment.type).toEqual('fragment')
+    expect(api.locals.models.Collection.type).toEqual('collection')
+
+    const user = await api.locals.models.User.findByEmail(fixtures.adminUser.email)
+    expect(user.username).toEqual(fixtures.adminUser.username)
+  })
+})
diff --git a/packages/pubsweet-server/test/api_sse_disabled_test.js b/packages/pubsweet-server/test/api_sse_disabled_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..1bf41f6d914dbd2f26561d3aabd0c973eb0f2778
--- /dev/null
+++ b/packages/pubsweet-server/test/api_sse_disabled_test.js
@@ -0,0 +1,40 @@
+const STATUS = require('http-status-codes')
+const EventSource = require('eventsource')
+
+const User = require('../src/models/User')
+
+const cleanDB = require('./helpers/db_cleaner')
+const fixtures = require('./fixtures/fixtures')
+const api = require('./helpers/api')
+
+const port = 30646
+
+describe('API SSE disabled', () => {
+  let es
+  let server
+
+  beforeEach(async () => {
+    await cleanDB()
+    await new User(fixtures.adminUser).save()
+    await new Promise((resolve, reject) => {
+      server = api.api.listen(port, err => err ? reject(err) : resolve())
+    })
+  })
+
+  afterEach(() => {
+    if (es) es.close()
+    if (server) server.close()
+  })
+
+  it('should not send an event if not configured', async () => {
+    const token = await api.users.authenticate.post(fixtures.adminUser)
+    es = new EventSource(`http://localhost:${port}/updates?access_token=${encodeURIComponent(token)}`)
+
+    const eventPromise = new Promise((resolve, reject) => {
+      es.addEventListener('message', resolve)
+      es.addEventListener('error', reject)
+    })
+
+    await expect(eventPromise).rejects.toEqual({type: 'error', status: STATUS.NOT_FOUND})
+  })
+})
diff --git a/packages/pubsweet-server/test/api_sse_enabled_test.js b/packages/pubsweet-server/test/api_sse_enabled_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..409fc4ddf5771c13eb11493d83db87395bd3adaf
--- /dev/null
+++ b/packages/pubsweet-server/test/api_sse_enabled_test.js
@@ -0,0 +1,55 @@
+const STATUS = require('http-status-codes')
+const EventSource = require('eventsource')
+const config = require('config')
+
+// override config for test
+config['pubsweet-server'].sse = true
+
+const User = require('../src/models/User')
+
+const cleanDB = require('./helpers/db_cleaner')
+const fixtures = require('./fixtures/fixtures')
+
+const api = require('./helpers/api')
+
+const port = 30645
+
+describe('API SSE enabled', () => {
+  let es
+  let server
+
+  beforeEach(async () => {
+    await cleanDB()
+    await new User(fixtures.adminUser).save()
+    await new Promise((resolve, reject) => {
+      server = api.api.listen(port, err => err ? reject(err) : resolve())
+    })
+  })
+
+  afterEach(() => {
+    if (es) es.close()
+    if (server) server.close()
+  })
+
+  it('should send an event if configured', async () => {
+    const token = await api.users.authenticate.post(fixtures.adminUser)
+    es = new EventSource(`http://localhost:${port}/updates?access_token=${encodeURIComponent(token)}`)
+
+    // wrap event listener in promise
+    const eventPromise = new Promise(resolve => es.addEventListener('message', resolve))
+
+    // perform action
+    await api.collections.create(fixtures.collection, token)
+      .expect(STATUS.CREATED)
+
+    // await event
+    const event = await eventPromise
+    const eventData = JSON.parse(event.data)
+    expect(eventData).toMatchObject({
+      action: 'collection:create',
+      data: {
+        collection: fixtures.collection
+      }
+    })
+  })
+})
diff --git a/packages/pubsweet-server/test/api_teams_test.js b/packages/pubsweet-server/test/api_teams_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..2e33acca6b94b868d848076e12eb5cb01e1dc0b4
--- /dev/null
+++ b/packages/pubsweet-server/test/api_teams_test.js
@@ -0,0 +1,211 @@
+const STATUS = require('http-status-codes')
+const cloneDeep = require('lodash/cloneDeep')
+
+const Collection = require('../src/models/Collection')
+const User = require('../src/models/User')
+const Team = require('../src/models/Team')
+
+const cleanDB = require('./helpers/db_cleaner')
+const fixtures = require('./fixtures/fixtures')
+const contributors = fixtures.teams.contributors
+const teamFixture = fixtures.contributorTeam
+const api = require('./helpers/api')
+
+describe('Teams API - admin', () => {
+  beforeEach(() => {
+    return cleanDB().then(
+      () => { return new User(fixtures.adminUser).save() }
+    ).then(
+      () => { return new User(fixtures.user).save() }
+    )
+  })
+
+  it('should display an initially empty list of teams if user is admin', () => {
+    return api.users.authenticate.post(
+      fixtures.adminUser
+    ).then(
+      token => api.teams.list(token).expect(STATUS.OK)
+    ).then(
+      res => expect(res.body).toEqual([])
+    )
+  })
+
+  it('should display the existing teams if user is admin', () => {
+    return new Team(
+      teamFixture
+    ).save().then(
+      () => api.users.authenticate.post(fixtures.adminUser)
+    ).then(
+      token => api.teams.list(token).expect(STATUS.OK)
+    ).then(
+      res => {
+        let team = res.body[0]
+        expect(team.teamType.name).toEqual(contributors.name)
+        expect(team.members).toEqual([])
+      }
+    )
+  })
+
+  it('should allow retrieval of a team by id', () => {
+    return new Team(
+      teamFixture
+    ).save().then(
+      () => api.users.authenticate.post(fixtures.adminUser)
+    ).then(
+      token => api.teams.list(token).then(res => ({teamId: res.body[0].id, token}))
+    ).then(
+      ({teamId, token}) => api.teams.get(token, teamId).expect(STATUS.OK)
+    ).then(
+      res => {
+        const team = res.body
+        expect(team.teamType.name).toEqual(contributors.name)
+        expect(team.members).toEqual([])
+      })
+  })
+
+  it('should not allow listing all teams if user is not an admin', () => {
+    return api.users.authenticate.post(
+      fixtures.user
+    ).then(
+      token => api.teams.list(token).expect(STATUS.FORBIDDEN)
+    )
+  })
+})
+
+describe('Teams API - per collection or fragment', () => {
+  describe('Collection teams', () => {
+    describe('owners', () => {
+      let collectionId
+      let otherUserId
+      let team
+
+      beforeEach(() => {
+        return cleanDB().then(
+          () => new User(fixtures.user).save()
+        ).then(
+          user => {
+            let collection = new Collection(fixtures.collection)
+            collection.setOwners([user.id])
+            return collection.save()
+          }
+        ).then(
+          collection => { collectionId = collection.id }
+        ).then(
+          () => new User(fixtures.updatedUser).save()
+        ).then(
+          otherUser => { otherUserId = otherUser.id }
+        ).then(
+          () => { team = cloneDeep(teamFixture) }
+        )
+      })
+
+      it('can display an initially empty list of teams', () => {
+        return api.users.authenticate.post(
+          fixtures.user
+        ).then(
+          token => api.teams.list(token, collectionId).expect(STATUS.OK)
+        ).then(
+          res => expect(res.body).toEqual([])
+        )
+      })
+
+      it('can add a team with a team member to a collection and this team member can then create fragments', () => {
+        team.name = 'Test team'
+        team.members = [otherUserId]
+        team.object = {
+          id: collectionId,
+          type: 'collection'
+        }
+
+        return api.users.authenticate.post(
+          fixtures.user
+        ).then(
+          token => api.teams.post(
+            team, token
+          ).expect(
+            STATUS.CREATED
+          )
+        ).then(
+          res => {
+            expect(res.body.name).toEqual(team.name)
+          }
+        ).then(
+          () => api.users.authenticate.post(fixtures.updatedUser)
+        ).then(
+          token => api.fragments.post({
+            fragment: fixtures.fragment, collection: collectionId, token
+          }).expect(
+            STATUS.CREATED
+          )
+        )
+      })
+
+      it('can remove a team member and that removed team member can no longer create fragments', () => {
+        team.name = 'Test team'
+        team.members = [otherUserId]
+        team.object = {
+          id: collectionId,
+          type: 'collection'
+        }
+
+        return api.users.authenticate.post(
+          fixtures.user
+        ).then(
+          token => api.teams.post(
+            team, token
+          ).expect(
+            STATUS.CREATED
+          ).then(
+            res => [res, token]
+          )
+        ).then(
+          ([res, token]) => {
+            const savedTeam = res.body
+            savedTeam.members = []
+            return api.teams.patch(
+              savedTeam, savedTeam.id, token
+            ).expect(
+              STATUS.OK
+            )
+          }
+        ).then(
+          () => api.users.authenticate.post(fixtures.updatedUser)
+        ).then(
+          token => api.fragments.post({
+            fragment: fixtures.fragment, collection: collectionId, token
+          }).expect(
+            STATUS.FORBIDDEN
+          )
+        )
+      })
+    })
+
+    describe('non-owners', () => {
+      let collectionId
+
+      beforeEach(() => {
+        return cleanDB().then(
+          () => new User(fixtures.user).save()
+        ).then(
+          user => {
+            let collection = new Collection(fixtures.collection)
+            collection.owners = []
+            return collection.save()
+          }
+        ).then(
+          collection => { collectionId = collection.id }
+        )
+      })
+
+      it('should not see teams in a collection', () => {
+        return api.users.authenticate.post(
+          fixtures.user
+        ).then(
+          token => api.teams.list(token, collectionId).expect(STATUS.OK)
+        ).then(res => {
+          expect(res.body).toEqual([])
+        })
+      })
+    })
+  })
+})
diff --git a/packages/pubsweet-server/test/api_unauthenticated_test.js b/packages/pubsweet-server/test/api_unauthenticated_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..ee8631ff41a623d5cba60aad4438817f79022245
--- /dev/null
+++ b/packages/pubsweet-server/test/api_unauthenticated_test.js
@@ -0,0 +1,94 @@
+const STATUS = require('http-status-codes')
+
+const api = require('./helpers/api')
+const createBasicCollection = require('./helpers/basic_collection')
+const createFragment = require('./helpers/fragment')
+const cleanDB = require('./helpers/db_cleaner')
+const Collection = require('../src/models/Collection')
+
+describe('unauthenticated/public api', () => {
+  let fragment
+  let unpublishedFragment
+  let collection
+
+  beforeEach(cleanDB)
+
+  async function setNewFragment (opts) {
+    const userAndCollection = await createBasicCollection()
+    collection = userAndCollection.collection
+    fragment = await createFragment(opts, collection)
+    unpublishedFragment = await createFragment({}, collection)
+  }
+
+  describe('published fragment', () => {
+    beforeEach(() => setNewFragment({ published: true }))
+
+    it('can see a published fragment in a collection', () => {
+      return api.fragments.get({ collection }).expect(STATUS.OK).then(
+        res => expect(res.body[0].id).toEqual(fragment.id)
+      )
+    })
+
+    it('can only see the published fragment in a collection', () => {
+      return api.fragments.get({ collection }).expect(STATUS.OK).then(
+        res => expect(res.body.map(f => f.id)).not.toContain(unpublishedFragment.id)
+      )
+    })
+
+    it('can only see the filtered list of properties for a fragment', () => {
+      return api.collections.retrieveFragment(collection.id, fragment.id).expect(STATUS.OK).then(
+        res => expect(Object.keys(res.body)).toEqual(['id', 'title', 'source', 'presentation', 'owners'])
+      )
+    })
+
+    it('can only see the filtered list of properties for a collection', () => {
+      return api.collections.retrieve(collection.id).expect(STATUS.OK).then(
+        res => expect(Object.keys(res.body)).toEqual(['id', 'title', 'owners'])
+      )
+    })
+  })
+
+  describe('unpublished fragment', () => {
+    beforeEach(() => setNewFragment({ published: false }))
+
+    it('can not list unpublished fragments in a protected collection', () => {
+      return api.fragments.get({ collection }).expect(STATUS.OK).then(
+        res => expect(res.body).toEqual([])
+      )
+    })
+
+    it('can not find a fragment in a protected collection', () => {
+      return api.fragments.get({ collection, fragmentId: fragment.id })
+        .expect(STATUS.NOT_FOUND)
+    })
+  })
+
+  describe('collections filtering by object and properties', () => {
+    let publicCollection
+    let privateCollection
+
+    beforeEach(async () => {
+      publicCollection = new Collection({
+        title: 'Public collection',
+        published: true,
+        nonPublicProperty: 'example'
+      })
+      await publicCollection.save()
+
+      privateCollection = new Collection({
+        title: 'Private collection'
+      })
+      await privateCollection.save()
+    })
+
+    it('can only see the filtered list of collections and only filtered properties in each collection', () => {
+      return api.collections.list().expect(STATUS.OK).then(
+        res => {
+          const collections = res.body
+          expect(collections.length).toEqual(1)
+          expect(Object.keys(collections[0])).toEqual(['id', 'title', 'owners'])
+        }
+      )
+    })
+  })
+})
diff --git a/packages/pubsweet-server/test/api_users_test.js b/packages/pubsweet-server/test/api_users_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..435208f8532af6d57540fec92377dcd71e3c7c1d
--- /dev/null
+++ b/packages/pubsweet-server/test/api_users_test.js
@@ -0,0 +1,283 @@
+// jasmine.DEFAULT_TIMEOUT_INTERVAL = 100000
+
+const STATUS = require('http-status-codes')
+
+const cleanDB = require('./helpers/db_cleaner')
+const User = require('../src/models/User')
+const fixtures = require('./fixtures/fixtures')
+const api = require('./helpers/api')
+const setupBase = require('../src/setup-base')
+
+describe('users api', () => {
+  let userId
+
+  beforeEach(async () => {
+    await cleanDB()
+    const { user } = await setupBase.setup(fixtures.user, fixtures.collection)
+    userId = user.id
+    expect(userId).not.toBeNull()
+  })
+
+  afterEach(cleanDB)
+
+  describe('admin', () => {
+    let otherUser
+
+    beforeEach(async () => {
+      const user = new User(fixtures.otherUser)
+      otherUser = await user.save()
+    })
+
+    afterEach(() => {
+      return User.find(otherUser.id)
+        .then(user => user.delete())
+        .catch(() => {}) // we might have already deleted the user
+    })
+
+    it('can get a list of users', () => {
+      return api.users.authenticate.post(
+        fixtures.user
+      ).then(
+        token => api.users.get(null, token).expect(STATUS.OK)
+      ).then(
+        res => {
+          expect(res.body.users.length).toBe(2)
+          expect(res.body.users[0].username).not.toBe(undefined)
+        }
+      )
+    })
+
+    it('can get another user', () => {
+      return api.users.authenticate.post(
+        fixtures.user
+      ).then(
+        token => api.users.get(otherUser.id, token).expect(STATUS.OK)
+      ).then(
+        res => {
+          expect(res.body.username).toBe(otherUser.username)
+        }
+      )
+    })
+
+    it('can make another user an admin', () => {
+      const patchedUser = Object.assign(
+        otherUser, { admin: true }
+      )
+
+      return api.users.authenticate.post(
+        fixtures.user
+      ).then(
+        token => api.users.patch(
+          otherUser.id, patchedUser, token
+        ).expect(
+          STATUS.OK
+        )
+      )
+    })
+
+    it('deletes a user', () => {
+      return api.users.authenticate.post(
+        fixtures.user
+      ).then(
+        token => api.users.del(otherUser.id, token).expect(STATUS.OK)
+      )
+    })
+  })
+
+  describe('unauthenticated user', () => {
+    it(
+      'can not get a list of users',
+      () => api.users.get().expect(STATUS.UNAUTHORIZED)
+    )
+
+    it('cannot sign up as an admin directly', () => {
+      const fakeAdmin = Object.assign(
+        {}, fixtures.otherUser, { admin: true }
+      )
+      return api.users.post(fakeAdmin).expect(STATUS.BAD_REQUEST)
+    })
+
+    it('can sign up', () => {
+      return api.users.post(
+        fixtures.otherUser
+      ).expect(
+        STATUS.CREATED
+      ).then(
+        res => {
+          expect(res.body.username).toBe(fixtures.otherUser.username)
+        }
+      )
+    })
+  })
+
+  describe('new user', () => {
+    let otherUser
+
+    beforeEach(async () => {
+      const user = new User(fixtures.otherUser)
+      otherUser = await user.save()
+    })
+
+    afterEach(() => {
+      return User.find(otherUser.id)
+        .then(user => user.delete())
+        .catch(() => {}) // we might have already deleted the user
+    })
+
+    it('cant log in with the wrong username', () => {
+      return api.users.authenticate.post({
+        username: 'wrongusername',
+        password: 'wrongpassword'
+      }, {
+        expect: false,
+        token: false
+      }).then(res => {
+        expect(res.statusCode).toEqual(STATUS.UNAUTHORIZED)
+      }
+      )
+    })
+
+    it('cant log in with the wrong password', () => {
+      return api.users.authenticate.post({
+        username: otherUser.username,
+        password: 'wrongpassword'
+      }, {
+        expect: false,
+        token: false
+      }).then(res => {
+        expect(res.statusCode).toEqual(STATUS.UNAUTHORIZED)
+      }
+      )
+    })
+
+    it('can verify its token', async () => {
+      const token = await api.users.authenticate.post(fixtures.otherUser)
+      const res = await api.users.authenticate.get(token).expect(STATUS.OK)
+
+      expect(res.body.id).toBe(otherUser.id)
+      expect(res.body.token).toBe(token)
+    })
+
+    it('can not get a list of users', () => {
+      return api.users.authenticate.post(
+        fixtures.otherUser
+      ).then(
+        token => api.users.get(null, token).expect(STATUS.FORBIDDEN)
+      )
+    })
+
+    it('can not delete other users', () => {
+      return api.users.authenticate.post(
+        fixtures.otherUser
+      ).then(
+        token => api.users.del(userId, token).expect(STATUS.FORBIDDEN)
+      )
+    })
+
+    it('can not get other users', () => {
+      return api.users.authenticate.post(
+        fixtures.otherUser
+      ).then(
+        token => api.users.get(userId, token).expect(STATUS.FORBIDDEN)
+      )
+    })
+
+    it('can get itself', () => {
+      return api.users.authenticate.post(
+        fixtures.otherUser
+      ).then(
+        token => api.users.get(otherUser.id, token).expect(STATUS.OK)
+      ).then(
+        res => {
+          expect(res.body.id).toBe(otherUser.id)
+          expect(res.body.username).toBe(fixtures.otherUser.username)
+        }
+      )
+    })
+
+    it('can not make itself admin', () => {
+      const newself = Object.assign(
+        { id: otherUser.id, admin: true }, fixtures.otherUser
+      )
+
+      return api.users.authenticate.post(
+        fixtures.otherUser
+      ).then(
+        token => api.users.patch(
+          otherUser.id, newself, token
+        ).expect(
+          STATUS.FORBIDDEN
+        )
+      )
+    })
+
+    it('updates itself', () => {
+      const newSelf = Object.assign({}, otherUser, fixtures.updatedUser)
+
+      return api.users.authenticate.post(
+        fixtures.otherUser
+      ).then(
+        token => api.users.patch(
+          otherUser.id, newSelf, token
+        ).expect(
+          STATUS.OK
+        )
+      )
+    })
+
+    it('authenticates an updated user', async () => {
+
+      // authenticate
+      const token = await api.users.authenticate.post(fixtures.otherUser)
+
+      // change the username, email and password
+      const updatedUser = Object.assign({}, otherUser, fixtures.updatedUser)
+      await api.users.patch(otherUser.id, updatedUser, token).expect(STATUS.OK)
+
+      // authenticate with the updated details
+      await api.users.authenticate.post(fixtures.updatedUser)
+    })
+
+    it('persists an updated user', () => {
+      const newSelf = Object.assign({}, otherUser, fixtures.updatedUser)
+
+      return api.users.authenticate.post(
+        fixtures.otherUser
+      ).then(
+        token => api.users.patch(
+          otherUser.id, newSelf, token
+        ).expect(
+          STATUS.OK
+        ).then(
+          () => token
+        )
+      ).then(
+        token => api.users.get(otherUser.id, token).expect(STATUS.OK)
+      ).then(
+        res => {
+          expect(res.body.id).toBe(otherUser.id)
+          expect(res.body.username).toBe(fixtures.updatedUser.username)
+        }
+      )
+    })
+
+    it('user can delete itself', async () => {
+      // authenticate
+      const otherUserToken = await api.users.authenticate.post(fixtures.otherUser)
+
+      // change username, email and password
+      const updatedUser = Object.assign({}, otherUser, fixtures.updatedUser)
+      await api.users.patch(otherUser.id, updatedUser, otherUserToken).expect(STATUS.OK)
+
+      // authenticate with updated details
+      const updatedUserToken = await api.users.authenticate.post(fixtures.updatedUser)
+
+      // delete the updated user
+      await api.users.del(otherUser.id, updatedUserToken).expect(STATUS.OK)
+    })
+  })
+
+  it('cannot create a user if user exists', () => {
+    return api.users.post(fixtures.user).expect(STATUS.CONFLICT)
+  })
+})
diff --git a/packages/pubsweet-server/test/db_test.js b/packages/pubsweet-server/test/db_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..a91b0b9eba78ff2ef21a6b86dde83de46f27a763
--- /dev/null
+++ b/packages/pubsweet-server/test/db_test.js
@@ -0,0 +1,14 @@
+describe('db configuration', () => {
+  it('overrides default adapter with config', async () => {
+    const originalNodeEnv = process.env.NODE_ENV
+    process.env.NODE_ENV = 'test'
+    const config = require('config')
+    config['pubsweet-server'].adapter = 'leveldb'
+    config['pubsweet-server'].dbPath = __dirname
+    const createDb = require('../src/db')
+    const db = createDb()
+    expect(db.adapter).toBe('leveldb')
+    await db.destroy()
+    process.env.NODE_ENV = originalNodeEnv
+  })
+})
diff --git a/packages/pubsweet-server/test/fixtures/fixture.jpg b/packages/pubsweet-server/test/fixtures/fixture.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..100ff212c4f4033c4333ea69e4d78898d2e5b181
Binary files /dev/null and b/packages/pubsweet-server/test/fixtures/fixture.jpg differ
diff --git a/packages/pubsweet-server/test/fixtures/fixtures.js b/packages/pubsweet-server/test/fixtures/fixtures.js
new file mode 100644
index 0000000000000000000000000000000000000000..fc1959b68b19a4c889f3c82ba51a87e488b5cd1e
--- /dev/null
+++ b/packages/pubsweet-server/test/fixtures/fixtures.js
@@ -0,0 +1,101 @@
+const collection = {
+  'type': 'collection',
+  'title': 'Science Blogger posts',
+  'published': true
+}
+
+const collection2 = {
+  'type': 'collection',
+  'title': 'Second collection',
+  'published': true
+}
+
+const updatedCollection = {
+  'title': 'Update Blogger posts'
+}
+
+const fragment = {
+  'type': 'fragment',
+  'fragmentType': 'blogpost',
+  'title': 'Just your regular blogpost',
+  'source': '<blog></blog>',
+  'presentation': '<p></p>'
+}
+
+const updatedFragment = {
+  'fragmentType': 'blogpost',
+  'source': '<blog><title>Updated</title></blog>',
+  'presentation': '<p><h1>Updated</h1></p>'
+}
+
+const user = {
+  'type': 'user',
+  'username': 'testuser',
+  'email': 'test@example.com',
+  'password': 'test'
+}
+
+const updatedUser = {
+  'username': 'changeduser',
+  'email': 'changed@email.com',
+  'password': 'changed'
+}
+
+const otherUser = {
+  'type': 'user',
+  'username': 'anotheruser',
+  'email': 'another@com.nz',
+  'password': 'rubgy'
+}
+
+const adminUser = {
+  'type': 'user',
+  'username': 'admin',
+  'email': 'admin@admins.example.org',
+  'password': 'admin',
+  'admin': true
+}
+
+const contribTeamType = {
+  name: 'Contributors',
+  permissions: 'POST'
+}
+
+const readerTeamType = {
+  name: 'Readers',
+  permissions: 'GET'
+}
+
+const teams = {
+  contributors: contribTeamType,
+  readers: readerTeamType
+}
+
+const contributorTeam = {
+  type: 'team',
+  name: 'My contributors',
+  teamType: teams.contributors,
+  object: fragment
+}
+
+const readerTeam = {
+  type: 'team',
+  name: 'My readers',
+  teamType: teams.readers,
+  object: fragment
+}
+
+module.exports = {
+  collection: collection,
+  collection2,
+  updatedCollection: updatedCollection,
+  fragment: fragment,
+  updatedFragment: updatedFragment,
+  user: user,
+  updatedUser: updatedUser,
+  otherUser: otherUser,
+  adminUser: adminUser,
+  teams: teams,
+  contributorTeam: contributorTeam,
+  readerTeam: readerTeam
+}
diff --git a/packages/pubsweet-server/test/helpers/api.js b/packages/pubsweet-server/test/helpers/api.js
new file mode 100644
index 0000000000000000000000000000000000000000..ba4647d0da398cb20eefa35bedfd1f91fc3e3ed6
--- /dev/null
+++ b/packages/pubsweet-server/test/helpers/api.js
@@ -0,0 +1,360 @@
+const request = require('supertest')
+const api = require('../../src').configureApp(require('express')())
+const STATUS = require('http-status-codes')
+const isString = require('lodash/isString')
+const querystring = require('querystring')
+
+const COLLECTIONS_ROOT = '/api/collections/'
+
+const authorizedRequest = (req, token) => {
+  if (token) {
+    req.set('Authorization', 'Bearer ' + token)
+  }
+
+  return req
+}
+
+// TODO: standardise parameter order of the "fragments" methods below
+
+const fragments = {
+  post: (opts = {}) => {
+    const { fragment, collection, token } = opts
+
+    let url
+
+    if (collection) {
+      const collectionId = isString(collection) ? collection : collection.id
+      url = `/api/collections/${collectionId}/fragments`
+    } else {
+      url = '/api/fragments'
+    }
+
+    const req = request(
+      api
+    ).post(
+      url
+    ).send(
+      fragment
+    )
+
+    return authorizedRequest(req, token)
+  },
+  patch: (opts = {}) => {
+    const { fragmentId, update, token, collection } = opts
+
+    let url
+    if (collection) {
+      url = `/api/collections/${collection.id}/fragments/${fragmentId}`
+    } else {
+      url = `/api/fragments/${fragmentId}`
+    }
+
+    const req = request(api)
+      .patch(url)
+      .send(update)
+
+    return authorizedRequest(req, token)
+  },
+  get: (opts = {}) => {
+    const { token, collection, fragmentId } = opts
+
+    let url
+
+    if (collection) {
+      url = `/api/collections/${collection.id}/fragments`
+    } else {
+      url = '/api/fragments'
+    }
+
+    if (fragmentId) url += '/' + fragmentId
+
+    if (opts.fields) {
+      url += '?' + querystring.stringify({
+        fields: opts.fields.join(',')
+      })
+    }
+
+    const req = request(api).get(url)
+    return authorizedRequest(req, token)
+  },
+  delete: (opts = {}) => {
+    const { fragmentId, token } = opts
+    const req = request(api).delete(`/api/fragments/${fragmentId}`)
+    return authorizedRequest(req, token)
+  },
+  teams: (opts = {}) => {
+    const { fragmentId, token } = opts
+
+    const url = `/api/fragments/${fragmentId}/teams`
+
+    const req = request(api).get(url)
+    return authorizedRequest(req, token)
+  }
+}
+
+const users = {
+  authenticate: {
+    post: (user, opts = {}) => {
+      let expect = opts.expect === undefined ? true : opts.expect
+      let token = opts.token === undefined ? true : opts.token
+
+      let req = request(
+        api
+      ).post(
+        '/api/users/authenticate'
+      ).send(
+        {
+          username: user.username,
+          password: user.password
+        }
+      )
+
+      if (expect) {
+        req = req.expect(
+          STATUS.CREATED
+        )
+      }
+
+      if (token) {
+        return req.then(
+          res => res.body.token
+        )
+      } else {
+        return req
+      }
+    },
+    get: token => {
+      const req = request(
+        api
+      ).get(
+        '/api/users/authenticate'
+      ).set(
+        'Authorization', 'Bearer ' + token
+      )
+
+      return req
+    }
+  },
+  post: user => {
+    return request(
+      api
+    ).post(
+      '/api/users'
+    ).send(
+      user
+    )
+  },
+  // deprecated: use patch instead
+  put: (userId, user, token) => {
+    const req = request(
+      api
+    ).put(
+      `/api/users/${userId}`
+    ).send(
+      user
+    )
+
+    return token ? req.set(
+      'Authorization', 'Bearer ' + token
+    ) : req
+  },
+  patch: (userId, user, token) => {
+    const req = request(
+      api
+    ).patch(
+      `/api/users/${userId}`
+    ).send(
+      user
+    )
+
+    return token ? req.set(
+      'Authorization', 'Bearer ' + token
+    ) : req
+  },
+  get: (userId, token) => {
+    const url = `/api/users${userId ? `/${userId}` : ''}`
+
+    const req = request(
+      api
+    ).get(
+      url
+    )
+
+    return token ? req.set(
+      'Authorization', 'Bearer ' + token
+    ) : req
+  },
+  del: (userId, token) => {
+    const req = request(
+      api
+    ).delete(
+      `/api/users/${userId}`
+    )
+
+    return token ? req.set(
+      'Authorization', 'Bearer ' + token
+    ) : req
+  }
+}
+
+const collections = {
+  list: (token, query = {}) => {
+    const url = COLLECTIONS_ROOT
+
+    if (query.fields) {
+      query.fields = query.fields.join(',')
+    }
+
+    const req = request(api).get(url).query(query)
+    return authorizedRequest(req, token)
+  },
+  create: (collection, token) => {
+    const req = request(api).post(COLLECTIONS_ROOT)
+      .send(collection)
+    return authorizedRequest(req, token)
+  },
+  retrieve: (collectionId, token) => {
+    const req = request(api).get(COLLECTIONS_ROOT + collectionId)
+    return authorizedRequest(req, token)
+  },
+  update: (collectionId, patch, token) => {
+    const req = request(api).patch(COLLECTIONS_ROOT + collectionId)
+      .send(patch)
+    return authorizedRequest(req, token)
+  },
+  delete: (collectionId, token) => {
+    const req = request(api).delete(COLLECTIONS_ROOT + collectionId)
+    return authorizedRequest(req, token)
+  },
+  listTeams: (collectionId, token) => {
+    const req = request(api).get(COLLECTIONS_ROOT + collectionId + '/teams')
+    return authorizedRequest(req, token)
+  },
+  listFragments: (collectionId, token, options) => {
+    let url = COLLECTIONS_ROOT + collectionId + '/fragments'
+
+    if (options && options.fields) {
+      url += '?' + querystring.stringify({
+        fields: options.fields.join(',')
+      })
+    }
+
+    const req = request(api).get(url)
+    return authorizedRequest(req, token)
+  },
+  createFragment: (collectionId, fragment, token) => {
+    const req = request(api).post(COLLECTIONS_ROOT + collectionId + '/fragments')
+      .send(fragment)
+    return authorizedRequest(req, token)
+  },
+  retrieveFragment: (collectionId, fragmentId, token) => {
+    const req = request(api).get(COLLECTIONS_ROOT + collectionId + '/fragments/' + fragmentId)
+    return authorizedRequest(req, token)
+  },
+  updateFragment: (collectionId, fragmentId, patch, token) => {
+    const req = request(api).patch(COLLECTIONS_ROOT + collectionId + '/fragments/' + fragmentId)
+      .send(patch)
+    return authorizedRequest(req, token)
+  },
+  deleteFragment: (collectionId, fragmentId, token) => {
+    const req = request(api).delete(COLLECTIONS_ROOT + collectionId + '/fragments/' + fragmentId)
+    return authorizedRequest(req, token)
+  },
+  listFragmentTeams: (collectionId, fragmentId, token) => {
+    const req = request(api).get(`${COLLECTIONS_ROOT}${collectionId}/fragments/${fragmentId}/teams`)
+    return authorizedRequest(req, token)
+  }
+}
+
+const teams = {
+  get: (token, teamId) => {
+    const url = `/api/teams/${teamId}`
+
+    return request(
+      api
+    ).get(
+      url
+    ).set(
+      'Authorization', 'Bearer ' + token
+    )
+  },
+  list: (token, collection) => {
+    const collectionId = () => {
+      return isString(collection) ? collection : collection.id
+    }
+    const collectionpart = collection ? `/collections/${collectionId()}` : ''
+
+    const url = `/api${collectionpart}/teams`
+
+    return request(
+      api
+    ).get(
+      url
+    ).set(
+      'Authorization', 'Bearer ' + token
+    )
+  },
+  post: (team, token) => {
+    const url = `/api/teams`
+
+    return request(
+      api
+    ).post(
+      url
+    ).send(
+      team
+    ).set(
+      'Authorization', 'Bearer ' + token
+    )
+  },
+  patch: (team, teamId, token) => {
+    const teamPart = teamId ? `/${teamId}` : ''
+    const url = `/api/teams${teamPart}`
+
+    return request(
+      api
+    ).patch(
+      url
+    ).send(
+      team
+    ).set(
+      'Authorization', 'Bearer ' + token
+    )
+  }
+}
+
+const upload = {
+  post: (file, token) => {
+    const req = request(
+      api
+    ).post(
+      '/api/upload'
+    ).attach(
+      'file', file
+    )
+
+    return token ? req.set(
+      'Authorization', 'Bearer ' + token
+    ) : req
+  },
+  get: (path, token) => {
+    const req = request(
+      api
+    ).get(
+      path
+    )
+
+    return token ? req.set(
+      'Authorization', 'Bearer ' + token
+    ) : req
+  }
+}
+
+module.exports = {
+  fragments: fragments,
+  users: users,
+  collections: collections,
+  teams: teams,
+  upload: upload,
+  api: api
+}
diff --git a/packages/pubsweet-server/test/helpers/authsome_mode.js b/packages/pubsweet-server/test/helpers/authsome_mode.js
new file mode 100644
index 0000000000000000000000000000000000000000..8a3a3294f363a4caf9176aac1a42c2b87ff4790c
--- /dev/null
+++ b/packages/pubsweet-server/test/helpers/authsome_mode.js
@@ -0,0 +1,178 @@
+const get = require('lodash/get')
+const pickBy = require('lodash/pickBy')
+const omit = require('lodash/omit')
+
+async function teamPermissions (user, operation, object, context) {
+  const collection = get(object, 'collection')
+
+  if (collection) {
+    // Go through a user's teams, if they belong to a team that's based around
+    // this particular collection, check what membership in that team allows
+    // and return accordingly
+
+    for (const teamId of user.teams) {
+      const team = await context.models.Team.find(teamId)
+
+      if (team.teamType.permissions === 'POST' &&
+          team.object.id === collection.id &&
+          operation === 'POST') {
+        return true
+      } else if (team.teamType.permissions === 'PATCH' &&
+          team.object.id === object.id &&
+          operation === 'PATCH') {
+        return true
+      }
+    }
+  }
+}
+
+function unauthenticatedUser (operation, object) {
+  // Public/unauthenticated users can GET /collections, filtered by 'published'
+  if (operation === 'GET' && object && object.path === '/collections') {
+    return {
+      filter: (collections) => collections.filter(collection => collection.published)
+    }
+  }
+
+  // Public/unauthenticated users can GET /collections/:id/fragments, filtered by 'published'
+  if (operation === 'GET' && object && object.path === '/collections/:id/fragments') {
+    return {
+      filter: (fragments) => fragments.filter(fragment => fragment.published)
+    }
+  }
+
+  // and filtered individual collection's properties: id, title, source, content, owners
+  if (operation === 'GET' && object && object.type === 'collection') {
+    if (object.published) {
+      return {
+        filter: (collection) => pickBy(collection, (_, key) => {
+          return ['id', 'title', 'owners'].includes(key)
+        })
+      }
+    }
+  }
+
+  if (operation === 'GET' && object && object.type === 'fragment') {
+    if (object.published) {
+      return {
+        filter: (fragment) => pickBy(fragment, (_, key) => {
+          return ['id', 'title', 'source', 'presentation', 'owners'].includes(key)
+        })
+      }
+    }
+  }
+
+  return false
+}
+
+async function authenticatedUser (user, operation, object, context) {
+  // Allow the authenticated user to POST a collection (but not with a 'filtered' property)
+  if (operation === 'POST' && object.path === '/collections') {
+    return {
+      filter: (collection) => omit(collection, 'filtered')
+    }
+  }
+
+  // Allow the authenticated user to GET collections they own
+  if (operation === 'GET' && object === '/collections/') {
+    return {
+      filter: (collection) => collection.owners.includes(user.id)
+    }
+  }
+
+  // Allow owners of a collection to GET its teams, e.g.
+  // GET /api/collections/1/teams
+  if (operation === 'GET' && get(object, 'path') === '/teams') {
+    const collectionId = get(object, 'params.collectionId')
+    if (collectionId) {
+      const collection = await context.models.Collection.find(collectionId)
+      if (collection.owners.includes(user.id)) {
+        return true
+      }
+    }
+  }
+
+  if (operation === 'GET' && get(object, 'type') === 'team' && get(object, 'object.type') === 'collection') {
+    const collection = await context.models.Collection.find(get(object, 'object.id'))
+    if (collection.owners.includes(user.id)) {
+      return true
+    }
+  }
+
+  // Advanced example
+  // Allow authenticated users to create a team based around a collection
+  // if they are one of the owners of this collection
+  if (['POST', 'PATCH'].includes(operation) && get(object, 'type') === 'team') {
+    if (get(object, 'object.type') === 'collection') {
+      const collection = await context.models.Collection.find(get(object, 'object.id'))
+      if (collection.owners.includes(user.id)) {
+        return true
+      }
+    }
+  }
+
+  if (user.teams.length !== 0) {
+    const permissions = await teamPermissions(user, operation, object, context)
+
+    if (permissions) {
+      return permissions
+    }
+  }
+
+  if (get(object, 'type') === 'fragment') {
+    const fragment = object
+
+    if (fragment.owners.includes(user.id)) {
+      return true
+    }
+  }
+
+  if (get(object, 'type') === 'collection') {
+    const collection = object
+
+    // Owner user
+    if (collection.owners.includes(user.id)) {
+      if (['GET', 'DELETE'].includes(operation)) {
+        return true
+      }
+
+      // Only allow filtered updating (mirroring filtered creation) for non-admin users)
+      if (operation === 'PATCH') {
+        return {
+          filter: (collection) => omit(collection, 'filtered')
+        }
+      }
+    }
+  }
+
+  // A user can GET, DELETE and PATCH itself
+  if (get(object, 'type') === 'user' && get(object, 'id') === user.id) {
+    if (['GET', 'DELETE', 'PATCH'].includes(operation)) {
+      return true
+    }
+  }
+  // If no individual permissions exist (above), fallback to unauthenticated
+  // user's permission
+  return unauthenticatedUser(operation, object)
+}
+
+const authsomeMode = async function (userId, operation, object, context) {
+  if (!userId) {
+    return unauthenticatedUser(operation, object)
+  }
+
+  // It's up to us to retrieve the relevant models for our
+  // authorization/authsome mode, e.g.
+  const user = await context.models.User.find(userId)
+
+  // Admins can do anything
+  if (user && user.admin === true) return true
+
+  if (user) {
+    return authenticatedUser(user, operation, object, context)
+  }
+
+  return false
+}
+
+module.exports = authsomeMode
diff --git a/packages/pubsweet-server/test/helpers/basic_collection.js b/packages/pubsweet-server/test/helpers/basic_collection.js
new file mode 100644
index 0000000000000000000000000000000000000000..ab62a828e5172e60f41de1e565140b7ad15a2ad9
--- /dev/null
+++ b/packages/pubsweet-server/test/helpers/basic_collection.js
@@ -0,0 +1,9 @@
+const fixtures = require('../fixtures/fixtures')
+
+module.exports = () => {
+  return require(
+    '../../src/setup-base'
+  ).setup(
+    fixtures.user, fixtures.collection
+  )
+}
diff --git a/packages/pubsweet-server/test/helpers/db_cleaner.js b/packages/pubsweet-server/test/helpers/db_cleaner.js
new file mode 100644
index 0000000000000000000000000000000000000000..9b0747e24b77071075926020be517b9a76b44a2d
--- /dev/null
+++ b/packages/pubsweet-server/test/helpers/db_cleaner.js
@@ -0,0 +1,16 @@
+'use strict'
+
+const createDb = require('../../src/db')
+const logger = require('@pubsweet/logger')
+
+let dbCleaner = async () => {
+  await global.db.destroy()
+
+  global.db = createDb()
+
+  const info = await global.db.info()
+
+  logger.info('Created database', info)
+}
+
+module.exports = dbCleaner
diff --git a/packages/pubsweet-server/test/helpers/fragment.js b/packages/pubsweet-server/test/helpers/fragment.js
new file mode 100644
index 0000000000000000000000000000000000000000..8ef8aa7a8e71e0403b48565d12acaf7b80736e7e
--- /dev/null
+++ b/packages/pubsweet-server/test/helpers/fragment.js
@@ -0,0 +1,17 @@
+const fixtures = require('../fixtures/fixtures')
+const Fragment = require('../../src/models/Fragment')
+const assign = require('lodash/assign')
+
+module.exports = (opts, collection) => {
+  const fragment = new Fragment(fixtures.fragment)
+  assign(fragment, opts)
+
+  return fragment.save().then(
+    () => {
+      collection.addFragment(fragment)
+      return collection.save().then(
+        () => fragment
+      )
+    }
+  )
+}
diff --git a/packages/pubsweet-server/test/helpers/set_team.js b/packages/pubsweet-server/test/helpers/set_team.js
new file mode 100644
index 0000000000000000000000000000000000000000..60a49901ac70cea93f078ccbf143dfcd7ec5a0be
--- /dev/null
+++ b/packages/pubsweet-server/test/helpers/set_team.js
@@ -0,0 +1,25 @@
+const STATUS = require('http-status-codes')
+
+const api = require('./api')
+const fixtures = require('../fixtures/fixtures')
+
+module.exports = (members, collection, team) => {
+  return api.users.authenticate.post(
+    fixtures.user
+  ).then(
+    (token) => {
+      team.name = 'Test team'
+      team.members = members
+      team.object = {
+        id: collection.id,
+        type: 'collection'
+      }
+
+      return api.teams.post(
+        team, token
+      ).expect(
+        STATUS.CREATED
+      )
+    }
+  )
+}
diff --git a/packages/pubsweet-server/test/mocks/mock_component.js b/packages/pubsweet-server/test/mocks/mock_component.js
new file mode 100644
index 0000000000000000000000000000000000000000..6cda8b1980d428ca77c03366e1a613d65eccf4a0
--- /dev/null
+++ b/packages/pubsweet-server/test/mocks/mock_component.js
@@ -0,0 +1,9 @@
+const STATUS = require('http-status-codes')
+
+const mockComponent = { backend: () => app => {
+  app.use('/mock-component', (req, res, next) => {
+    return res.status(STATUS.OK).json({ok: '!'})
+  })
+} }
+
+module.exports = mockComponent
diff --git a/packages/pubsweet-server/test/model_test.js b/packages/pubsweet-server/test/model_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..0a9f4429b35335ee2ba7dd8a114f47ea67ae8d7b
--- /dev/null
+++ b/packages/pubsweet-server/test/model_test.js
@@ -0,0 +1,144 @@
+const createDb = require('../src/db')
+const STATUS = require('http-status-codes')
+
+const Model = require('../src/models/Model')
+const User = require('../src/models/User')
+const Fragment = require('../src/models/Fragment')
+const Collection = require('../src/models/Collection')
+const dbCleaner = require('./helpers/db_cleaner')
+const fixtures = require('./fixtures/fixtures')
+
+describe('Model', function () {
+  let user
+  let otherUser
+
+  beforeEach(async () => {
+    await dbCleaner()
+    user = await new User(fixtures.user).save()
+    otherUser = await new User(fixtures.updatedUser).save()
+  })
+
+  it('raises an error if trying to find on a destroyed database', () => {
+    expect.hasAssertions()
+    return global.db.destroy().then(
+      () => Model.findByField('field', 'value')
+    ).catch(err => {
+      expect(err.name).toEqual('Error')
+    }).then(() => {
+      global.db = createDb()
+    })
+  })
+
+  it('raises an error if trying to find all on a destroyed database', () => {
+    expect.hasAssertions()
+    return global.db.destroy().then(
+      () => User.all()
+    ).catch(err => {
+      expect(err.name).toEqual('Error')
+    }).then(() => {
+      global.db = createDb()
+    })
+  })
+
+  it('raises an error if trying to save on a destroyed database', () => {
+    expect.hasAssertions()
+    return global.db.destroy().then(
+      () => user.save()
+    ).catch(err => {
+      expect(err.name).toEqual('Error')
+    }).then(() => {
+      global.db = createDb()
+    })
+  })
+
+  it('initially has no owner', () => {
+    const collection = new Collection(fixtures.collection)
+
+    expect(collection.owners).toBeUndefined()
+    expect(collection.isOwner(user.id)).toBe(false)
+    expect(collection.isOwner(otherUser.id)).toBe(false)
+  })
+
+  it('can set the owners of a Collection', () => {
+    const collection = new Collection(fixtures.collection)
+
+    collection.setOwners([otherUser.id])
+    expect(collection.owners).toEqual([otherUser.id])
+    expect(collection.isOwner(user.id)).toBe(false)
+    expect(collection.isOwner(otherUser.id)).toBe(true)
+
+    collection.setOwners([user.id, otherUser.id])
+    expect(collection.owners.sort()).toEqual([user.id, otherUser.id].sort())
+    expect(collection.isOwner(user.id)).toBe(true)
+    expect(collection.isOwner(otherUser.id)).toBe(true)
+  })
+
+  it('cannot set owners to non-array', () => {
+    const collection = new Collection(fixtures.collection)
+
+    expect.hasAssertions()
+    try {
+      collection.setOwners('notAnArray')
+    } catch (err) {
+      expect(err.name).toEqual('ValidationError')
+      expect(err.message).toEqual('owners should be an array')
+    }
+  })
+
+  it('can validate an object', () => {
+    const user = new User(fixtures.user)
+    user.email = 'notanemail'
+
+    expect.hasAssertions()
+    return user.save().catch(err => {
+      expect(err.name).toEqual('ValidationError')
+      expect(err.message).toEqual('child "email" fails because ["email" must be a valid email]')
+    })
+  })
+
+  it('rejects a fragment with wrong fragmentType', () => {
+    const fragment = new Fragment(fixtures.fragment)
+    fragment.fragmentType = 'file'
+
+    expect.hasAssertions()
+    return fragment.save().catch(err => {
+      expect(err.name).toEqual('ValidationError')
+      expect(err.message).toEqual(
+        'child "fragmentType" fails because ["fragmentType" must be one of [blogpost]], child "path" fails because ["path" is required]'
+      )
+    })
+  })
+
+  it('accepts a fragment with alternative fragmentType', () => {
+    const fragment = new Fragment({fragmentType: 'file', path: '/one/two'})
+
+    return fragment.save()
+  })
+
+  it('saving the same object multiple times in parallel throws conflict error', async () => {
+    expect.hasAssertions()
+    try {
+      await Promise.all([user.save(), user.save()])
+    } catch (e) {
+      expect(e).toHaveProperty('status', STATUS.CONFLICT)
+    }
+  })
+
+  it('can find by multiple fields', async () => {
+    const users = await User.findByField({username: 'testuser', email: 'test@example.com'})
+    expect(users).toHaveLength(1)
+    expect(users[0]).toMatchObject({
+      username: 'testuser',
+      email: 'test@example.com'
+    })
+  })
+
+  it('can find with complex value', async () => {
+    const users = await User.findByField('username', {$ne: 'testuser'})
+    expect(users).toHaveLength(1)
+    expect(users[0]).toMatchObject({
+      username: 'changeduser',
+      email: 'changed@email.com'
+    })
+  })
+})
diff --git a/packages/pubsweet-server/test/register_components_test.js b/packages/pubsweet-server/test/register_components_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..45747a0160a9e196a169e40db6fbe3503f7922a8
--- /dev/null
+++ b/packages/pubsweet-server/test/register_components_test.js
@@ -0,0 +1,15 @@
+const STATUS = require('http-status-codes')
+const request = require('supertest')
+const path = require('path')
+const config = require('config')
+const mockComponentPath = path.join(process.cwd(), 'test', 'mocks', 'mock_component.js')
+config['pubsweet'] = { components: [mockComponentPath] }
+
+const api = require('./helpers/api')
+
+describe('App startup', async () => {
+  it('should register components on config.pubsweet.components', async () => {
+    const res = await request(api.api).get('/mock-component')
+    expect(res.status).toBe(STATUS.OK)
+  })
+})
diff --git a/packages/pubsweet-server/test/start_server_test.js b/packages/pubsweet-server/test/start_server_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..bfc53ef7b6988853a8e5c10235324402a2865108
--- /dev/null
+++ b/packages/pubsweet-server/test/start_server_test.js
@@ -0,0 +1,19 @@
+const start = require('../src/')
+
+describe('Function exported by src/index.js', () => {
+  it('starts the server and returns it with express app attached', async () => {
+    const server = await start()
+    expect(server.listening).toBe(true)
+    expect(server).toHaveProperty('app')
+    return server.close()
+  })
+
+  it('returns the server if it is already running', async () => {
+    const server = await start()
+    server.originalServer = true
+    const secondAccess = await start()
+    expect(secondAccess).toHaveProperty('originalServer')
+    return server.close()
+  })
+
+})
diff --git a/packages/pubsweet-server/test/teams_test.js b/packages/pubsweet-server/test/teams_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..4521f8781da437729d34a8726fecd84962aaa34d
--- /dev/null
+++ b/packages/pubsweet-server/test/teams_test.js
@@ -0,0 +1,164 @@
+const Collection = require('../src/models/Collection')
+const Fragment = require('../src/models/Fragment')
+const User = require('../src/models/User')
+const Team = require('../src/models/Team')
+
+const dbCleaner = require('./helpers/db_cleaner')
+const fixtures = require('./fixtures/fixtures')
+
+const userFixture = fixtures.user
+const adminFixture = fixtures.adminUser
+const collectionFixture = fixtures.collection
+const teamFixture = fixtures.contributorTeam
+const fragmentFixture = fixtures.fragment
+
+describe('Teams model', function () {
+  let adminId
+  let userId
+  let collectionId
+  let fragmentId
+
+  beforeEach(function () {
+    return dbCleaner().then(function () {
+      return new User(adminFixture).save()
+    }).then(function (admin) {
+      adminId = admin.id
+      return new User(userFixture).save()
+    }).then(function (user) {
+      userId = user.id
+      return new Collection(collectionFixture).save()
+    }).then(function (collection) {
+      collectionId = collection.id
+      return new Fragment(fragmentFixture).save()
+    }).then(function (fragment) {
+      fragmentId = fragment.id
+    })
+  })
+
+  it('can save a team without members', function () {
+    let team = teamFixture
+    team.name = 'Test team'
+    team.object = {
+      id: collectionId,
+      type: 'collection'
+    }
+    team = new Team(team)
+
+    return team.save().then(function (savedTeam) {
+      expect(savedTeam.members).toEqual([])
+      expect(savedTeam.object).toEqual(team.object)
+      expect(savedTeam.teamType).toEqual(team.teamType)
+      expect(savedTeam.name).toEqual(team.name)
+    })
+  })
+
+  it('can save a team with members', function () {
+    let team = teamFixture
+    team.name = 'Test team'
+    team.object = {
+      id: collectionId,
+      type: 'collection'
+    }
+    team.members = [userId]
+    team = new Team(team)
+
+    let teamId
+
+    return team.save().then(function (savedTeam) {
+      teamId = savedTeam.id
+      expect(savedTeam.members).toEqual([userId])
+      expect(savedTeam.object).toEqual(team.object)
+      expect(savedTeam.teamType).toEqual(team.teamType)
+      expect(savedTeam.name).toEqual(team.name)
+      return User.find(userId)
+    }).then(function (user) {
+      expect(user.teams).toEqual([teamId])
+    })
+  })
+
+  it('can save a team with members based around a fragment', function () {
+    let team = teamFixture
+    team.name = 'Test team'
+    team.object = {
+      id: fragmentId,
+      type: 'fragment'
+    }
+    team.members = [userId]
+    team = new Team(team)
+
+    let teamId
+
+    return team.save().then(function (savedTeam) {
+      teamId = savedTeam.id
+      expect(savedTeam.members).toEqual([userId])
+      expect(savedTeam.object).toEqual(team.object)
+      expect(savedTeam.teamType).toEqual(team.teamType)
+      expect(savedTeam.name).toEqual(team.name)
+      return User.find(userId)
+    }).then(function (user) {
+      expect(user.teams).toEqual([teamId])
+    })
+  })
+
+  it('can update a team with members', function () {
+    let team = teamFixture
+    team.name = 'Test team'
+    team.object = {
+      id: collectionId,
+      type: 'collection'
+    }
+    team.members = [userId]
+    team = new Team(team)
+
+    let teamId
+
+    return team.save().then(function (savedTeam) {
+      return Team.find(savedTeam.id)
+    }).then(function (team) {
+      return team.updateProperties({
+        members: [userId, adminId],
+        rev: team.rev
+      })
+    }).then(function (team) {
+      return team.save()
+    }).then(function (savedTeam) {
+      teamId = savedTeam.id
+      expect(savedTeam.members).toEqual([userId, adminId])
+      expect(savedTeam.object).toEqual(team.object)
+      expect(savedTeam.teamType).toEqual(team.teamType)
+      expect(savedTeam.name).toEqual(team.name)
+      return User.find(userId)
+    }).then(function (user) {
+      expect(user.teams).toEqual([teamId])
+      return User.find(adminId)
+    }).then(function (admin) {
+      expect(admin.teams).toEqual([teamId])
+    })
+  })
+
+  it('can delete a team with members', function () {
+    let team = teamFixture
+    team.name = 'Test team'
+    team.object = {
+      id: collectionId,
+      type: 'collection'
+    }
+    team.members = [userId]
+    team = new Team(team)
+
+    return team.save().then(
+      savedTeam => Team.find(savedTeam.id)
+    ).then(
+      team => team.delete()
+    ).then(
+      deletedTeam => Team.find(deletedTeam.id)
+    ).catch(err => {
+      expect(err.name).toEqual('NotFoundError')
+      if (err.name !== 'NotFoundError') throw err
+    }).then(
+      () => User.find(userId)
+    ).then(
+      user => expect(user.teams).toEqual([])
+    )
+  })
+})
diff --git a/packages/pubsweet-server/test/user_test.js b/packages/pubsweet-server/test/user_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..2359c76daff271c27ddba8dcda52bbea5ddc11cf
--- /dev/null
+++ b/packages/pubsweet-server/test/user_test.js
@@ -0,0 +1,127 @@
+const dbCleaner = require('./helpers/db_cleaner')
+const User = require('../src/models/User')
+
+const fixtures = require('./fixtures/fixtures')
+const userFixture = fixtures.user
+
+describe('User', function () {
+  beforeEach(dbCleaner)
+
+  it('validates passwords correctly after saving to db', async () => {
+    const user = new User(userFixture)
+    await user.save()
+
+    const savedUser = await User.findByUsername(user.username)
+    expect(typeof savedUser).toBe('object')
+
+    const shouldBeValid = await savedUser.validPassword(userFixture.password)
+    expect(shouldBeValid).toEqual(true)
+
+    const shouldBeInvalid = await savedUser.validPassword('wrongpassword')
+    expect(shouldBeInvalid).toEqual(false)
+  })
+
+  it('raises an error if trying to save a user with a non-unique username', async () => {
+    const user = new User(userFixture)
+    const otherUserFixture = fixtures.otherUser
+    otherUserFixture.username = userFixture.username
+    const duplicateUser = new User(otherUserFixture)
+
+    await user.save()
+
+    try {
+      await duplicateUser.save()
+    } catch (err) {
+      expect(err.name).toEqual('ConflictError')
+    }
+
+    expect.hasAssertions()
+  })
+
+  it('raises an error if trying to save a user with a non-unique email', async () => {
+    const user = new User(userFixture)
+    const otherUserFixture = fixtures.otherUser
+    otherUserFixture.email = userFixture.email
+    const duplicateUser = new User(otherUserFixture)
+
+    await user.save()
+
+    try {
+      await duplicateUser.save()
+    } catch (err) {
+      expect(err.name).toEqual('ConflictError')
+    }
+
+    expect.hasAssertions()
+  })
+
+  it('uses custom JSON serialization', async () => {
+    const user = new User(userFixture)
+    await user.save()
+
+    const savedUser = await User.findByUsername(user.username)
+    expect(savedUser).toHaveProperty('username', user.username)
+    expect(savedUser).toHaveProperty('passwordHash')
+
+    const stringifiedUser = JSON.parse(JSON.stringify(savedUser))
+    expect(stringifiedUser).toHaveProperty('username', user.username)
+    expect(stringifiedUser).not.toHaveProperty('passwordHash')
+  })
+
+  it('uses custom JSON serialization in an array', async () => {
+    const users = [
+      {username: 'user1', email: 'user-1@example.com', password: 'foo1'},
+      {username: 'user2', email: 'user-2@example.com', password: 'foo2'},
+      {username: 'user3', email: 'user-3@example.com', password: 'foo3'}
+    ]
+
+    await Promise.all(users.map(user => new User(user).save()))
+
+    const savedUsers = await User.all()
+
+    const savedUser = savedUsers[2]
+    expect(savedUser).toHaveProperty('username')
+    expect(savedUser).toHaveProperty('passwordHash')
+
+    const stringifiedUsers = JSON.parse(JSON.stringify(savedUsers))
+    const stringifiedUser = stringifiedUsers[2]
+
+    expect(stringifiedUser).toHaveProperty('username', savedUser.username)
+    expect(stringifiedUser).not.toHaveProperty('passwordHash')
+  })
+
+  it('finds a list of users', async () => {
+    const users = [
+      { username: 'user1', email: 'user-1@example.com', password: 'foo1', admin: true },
+      { username: 'user2', email: 'user-2@example.com', password: 'foo2' },
+      { username: 'user3', email: 'user-3@example.com', password: 'foo3' }
+    ]
+
+    await Promise.all(users.map(user => new User(user).save()))
+
+    const items = await User.findByField('admin', true)
+
+    expect(items).toHaveLength(1)
+    expect(items[0]).toBeInstanceOf(User)
+  })
+
+  it('finds a single user by field', async () => {
+    const users = [
+      { username: 'user1', email: 'user-1@example.com', password: 'foo1', admin: true },
+      { username: 'user2', email: 'user-2@example.com', password: 'foo2' },
+      { username: 'user3', email: 'user-3@example.com', password: 'foo3' }
+    ]
+
+    await Promise.all(users.map(user => new User(user).save()))
+
+    const item = await User.findOneByField('admin', true)
+
+    expect(item).toBeInstanceOf(User)
+
+    expect(item).toEqual(expect.objectContaining({
+      username: 'user1',
+      email: 'user-1@example.com',
+      admin: true
+    }))
+  })
+})
diff --git a/packages/pubsweet-server/test/validations_test.js b/packages/pubsweet-server/test/validations_test.js
new file mode 100644
index 0000000000000000000000000000000000000000..f9189d0206fcc910e47fc7afa81b8d1a8185927b
--- /dev/null
+++ b/packages/pubsweet-server/test/validations_test.js
@@ -0,0 +1,28 @@
+const Fragment = require('../src/models/Fragment')
+const config = require('config')
+const appValidations = require(config.validations)
+const validations = require('../src/models/validations')(appValidations)
+
+describe('Validations export', function () {
+  it('has validations for each type', () => {
+    expect(Object.keys(validations).sort()).toEqual(['collection', 'fragment', 'team', 'user'])
+  })
+
+  it('allows fragment with required fields', () => {
+    const fragment = new Fragment({
+      title: 'Testing',
+      fragmentType: 'blogpost'
+    })
+
+    expect(fragment.validate()).toBe(true)
+  })
+
+  it('rejects fragment with missing type', () => {
+    const fragment = new Fragment({
+      title: 'Testing'
+    })
+    fragment.type = undefined
+
+    expect(() => fragment.validate()).toThrow('"type" is required')
+  })
+})
diff --git a/packages/pubsweet-server/yarn.lock b/packages/pubsweet-server/yarn.lock
new file mode 100644
index 0000000000000000000000000000000000000000..da1f66200f366b389ecc87da362c70688ecf3216
--- /dev/null
+++ b/packages/pubsweet-server/yarn.lock
@@ -0,0 +1,5531 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@pubsweet/logger@^0.0.1":
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/@pubsweet/logger/-/logger-0.0.1.tgz#ec0c15f04e0c64232c29173848ffe6da8190c9c2"
+  dependencies:
+    config "^1.26.2"
+    joi "^10.6.0"
+
+abab@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
+
+abbrev@1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+
+abstract-leveldown@2.4.1:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-2.4.1.tgz#b3bfedb884eb693a12775f0c55e9f0a420ccee64"
+  dependencies:
+    xtend "~4.0.0"
+
+abstract-leveldown@~2.6.0, abstract-leveldown@~2.6.1:
+  version "2.6.3"
+  resolved "https://registry.yarnpkg.com/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz#1c5e8c6a5ef965ae8c35dfb3a8770c476b82c4b8"
+  dependencies:
+    xtend "~4.0.0"
+
+accepts@~1.3.4:
+  version "1.3.4"
+  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f"
+  dependencies:
+    mime-types "~2.1.16"
+    negotiator "0.6.1"
+
+acorn-globals@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf"
+  dependencies:
+    acorn "^4.0.4"
+
+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@^1.0.3:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014"
+
+acorn@^3.0.4:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
+
+acorn@^4.0.3, acorn@^4.0.4:
+  version "4.0.13"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
+
+acorn@^5.1.1:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.2.tgz#911cb53e036807cf0fa778dc5d370fbd864246d7"
+
+after@~0.8.1:
+  version "0.8.2"
+  resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
+
+agent-base@2:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-2.1.1.tgz#d6de10d5af6132d5bd692427d46fc538539094c7"
+  dependencies:
+    extend "~3.0.0"
+    semver "~5.0.1"
+
+ajv-keywords@^1.0.0:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
+
+ajv-keywords@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0"
+
+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"
+
+ajv@^5.1.0, ajv@^5.2.0, ajv@^5.2.3:
+  version "5.2.3"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.3.tgz#c06f598778c44c6b161abafe3466b81ad1814ed2"
+  dependencies:
+    co "^4.6.0"
+    fast-deep-equal "^1.0.0"
+    json-schema-traverse "^0.3.0"
+    json-stable-stringify "^1.0.1"
+
+align-text@^0.1.1, align-text@^0.1.3:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
+  dependencies:
+    kind-of "^3.0.2"
+    longest "^1.0.1"
+    repeat-string "^1.5.2"
+
+amdefine@>=0.0.4:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
+
+ansi-escapes@^1.1.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
+
+ansi-escapes@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92"
+
+ansi-regex@^2.0.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+
+ansi-regex@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+
+ansi-styles@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
+
+ansi-styles@^3.1.0, ansi-styles@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88"
+  dependencies:
+    color-convert "^1.9.0"
+
+ansi@^0.3.0, ansi@~0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/ansi/-/ansi-0.3.1.tgz#0c42d4fb17160d5a9af1e484bace1c66922c1b21"
+
+anymatch@^1.3.0:
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a"
+  dependencies:
+    micromatch "^2.1.5"
+    normalize-path "^2.0.0"
+
+append-field@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/append-field/-/append-field-0.1.0.tgz#6ddc58fa083c7bc545d3c5995b2830cc2366d44a"
+
+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.2.0"
+  resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
+
+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"
+
+argsarray@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/argsarray/-/argsarray-0.0.1.tgz#6e7207b4ecdb39b0af88303fa5ae22bda8df61cb"
+
+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-flatten@^1.0.1:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
+
+array-equal@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
+
+array-flatten@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+
+array-index@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/array-index/-/array-index-1.0.0.tgz#ec56a749ee103e4e08c790b9c353df16055b97f9"
+  dependencies:
+    debug "^2.2.0"
+    es6-symbol "^3.0.2"
+
+array-union@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
+  dependencies:
+    array-uniq "^1.0.1"
+
+array-uniq@^1.0.1:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
+
+array-unique@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
+
+array.prototype.find@^2.0.1:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.0.4.tgz#556a5c5362c08648323ddaeb9de9d14bc1864c90"
+  dependencies:
+    define-properties "^1.1.2"
+    es-abstract "^1.7.0"
+
+arrify@^1.0.0, arrify@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
+
+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"
+
+ast-types@0.8.15:
+  version "0.8.15"
+  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.8.15.tgz#8eef0827f04dff0ec8857ba925abe3fea6194e52"
+
+ast-types@0.9.6:
+  version "0.9.6"
+  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
+
+astral-regex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
+
+async@^1.4.0, async@^1.5.2:
+  version "1.5.2"
+  resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
+
+async@^2.1.4:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
+  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"
+
+attempt-x@^1.1.0, attempt-x@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/attempt-x/-/attempt-x-1.1.1.tgz#fba64e96ce03c3e0bd92c92622061c4df387cb76"
+
+authsome@0.0.9:
+  version "0.0.9"
+  resolved "https://registry.yarnpkg.com/authsome/-/authsome-0.0.9.tgz#08b34f1797b3539e6a362f0fb11a01ae0613f928"
+
+aws-sign2@~0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
+
+aws-sign2@~0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
+
+aws4@^1.2.1, aws4@^1.6.0:
+  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, babel-code-frame@^6.26.0:
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
+  dependencies:
+    chalk "^1.1.3"
+    esutils "^2.0.2"
+    js-tokens "^3.0.2"
+
+babel-core@^6.0.0, babel-core@^6.26.0:
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8"
+  dependencies:
+    babel-code-frame "^6.26.0"
+    babel-generator "^6.26.0"
+    babel-helpers "^6.24.1"
+    babel-messages "^6.23.0"
+    babel-register "^6.26.0"
+    babel-runtime "^6.26.0"
+    babel-template "^6.26.0"
+    babel-traverse "^6.26.0"
+    babel-types "^6.26.0"
+    babylon "^6.18.0"
+    convert-source-map "^1.5.0"
+    debug "^2.6.8"
+    json5 "^0.5.1"
+    lodash "^4.17.4"
+    minimatch "^3.0.4"
+    path-is-absolute "^1.0.1"
+    private "^0.1.7"
+    slash "^1.0.0"
+    source-map "^0.5.6"
+
+babel-generator@^6.18.0, babel-generator@^6.26.0:
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5"
+  dependencies:
+    babel-messages "^6.23.0"
+    babel-runtime "^6.26.0"
+    babel-types "^6.26.0"
+    detect-indent "^4.0.0"
+    jsesc "^1.3.0"
+    lodash "^4.17.4"
+    source-map "^0.5.6"
+    trim-right "^1.0.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-jest@^21.2.0:
+  version "21.2.0"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-21.2.0.tgz#2ce059519a9374a2c46f2455b6fbef5ad75d863e"
+  dependencies:
+    babel-plugin-istanbul "^4.0.0"
+    babel-preset-jest "^21.2.0"
+
+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-istanbul@^4.0.0:
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e"
+  dependencies:
+    find-up "^2.1.0"
+    istanbul-lib-instrument "^1.7.5"
+    test-exclude "^4.1.1"
+
+babel-plugin-jest-hoist@^21.2.0:
+  version "21.2.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-21.2.0.tgz#2cef637259bd4b628a6cace039de5fcd14dbb006"
+
+babel-plugin-syntax-object-rest-spread@^6.13.0:
+  version "6.13.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
+
+babel-preset-jest@^21.2.0:
+  version "21.2.0"
+  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-21.2.0.tgz#ff9d2bce08abd98e8a36d9a8a5189b9173b85638"
+  dependencies:
+    babel-plugin-jest-hoist "^21.2.0"
+    babel-plugin-syntax-object-rest-spread "^6.13.0"
+
+babel-register@^6.26.0:
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
+  dependencies:
+    babel-core "^6.26.0"
+    babel-runtime "^6.26.0"
+    core-js "^2.5.0"
+    home-or-tmp "^2.0.0"
+    lodash "^4.17.4"
+    mkdirp "^0.5.1"
+    source-map-support "^0.4.15"
+
+babel-runtime@^6.22.0, babel-runtime@^6.26.0:
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
+  dependencies:
+    core-js "^2.4.0"
+    regenerator-runtime "^0.11.0"
+
+babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0:
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
+  dependencies:
+    babel-runtime "^6.26.0"
+    babel-traverse "^6.26.0"
+    babel-types "^6.26.0"
+    babylon "^6.18.0"
+    lodash "^4.17.4"
+
+babel-traverse@^6.18.0, babel-traverse@^6.26.0:
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
+  dependencies:
+    babel-code-frame "^6.26.0"
+    babel-messages "^6.23.0"
+    babel-runtime "^6.26.0"
+    babel-types "^6.26.0"
+    babylon "^6.18.0"
+    debug "^2.6.8"
+    globals "^9.18.0"
+    invariant "^2.2.2"
+    lodash "^4.17.4"
+
+babel-types@^6.18.0, babel-types@^6.26.0:
+  version "6.26.0"
+  resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
+  dependencies:
+    babel-runtime "^6.26.0"
+    esutils "^2.0.2"
+    lodash "^4.17.4"
+    to-fast-properties "^1.0.3"
+
+babylon@^6.18.0:
+  version "6.18.0"
+  resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
+
+balanced-match@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+
+base62@^1.1.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.0.tgz#31e7e560dc846c9f44c1a531df6514da35474157"
+
+base64url@2.0.0, base64url@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/base64url/-/base64url-2.0.0.tgz#eac16e03ea1438eff9423d69baa36262ed1f70bb"
+
+basic-auth@~2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba"
+  dependencies:
+    safe-buffer "5.1.1"
+
+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"
+
+bcrypt@^1.0.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-1.0.3.tgz#b02ddc6c0b52ea16b8d3cf375d5a32e780dab548"
+  dependencies:
+    nan "2.6.2"
+    node-pre-gyp "0.6.36"
+
+bindings@~1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11"
+
+bl@^1.0.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e"
+  dependencies:
+    readable-stream "^2.0.5"
+
+bl@~1.0.0:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/bl/-/bl-1.0.3.tgz#fc5421a28fd4226036c3b3891a66a25bc64d226e"
+  dependencies:
+    readable-stream "~2.0.5"
+
+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.5.1:
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
+
+body-parser@1.18.2, body-parser@^1.15.2:
+  version "1.18.2"
+  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454"
+  dependencies:
+    bytes "3.0.0"
+    content-type "~1.0.4"
+    debug "2.6.9"
+    depd "~1.1.1"
+    http-errors "~1.6.2"
+    iconv-lite "0.4.19"
+    on-finished "~2.3.0"
+    qs "6.5.1"
+    raw-body "2.3.2"
+    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"
+
+boom@4.x.x:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
+  dependencies:
+    hoek "4.x.x"
+
+boom@5.x.x:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
+  dependencies:
+    hoek "4.x.x"
+
+brace-expansion@^1.1.7:
+  version "1.1.8"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
+  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"
+
+browser-resolve@^1.11.2:
+  version "1.11.2"
+  resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce"
+  dependencies:
+    resolve "1.1.7"
+
+bser@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719"
+  dependencies:
+    node-int64 "^0.4.0"
+
+buffer-equal-constant-time@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
+
+buffer-from@0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-0.1.1.tgz#57b18b1da0a19ec06f33837a5275a242351bd75e"
+  dependencies:
+    is-array-buffer-x "^1.0.13"
+
+builtin-modules@^1.0.0, builtin-modules@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
+
+busboy@^0.2.11:
+  version "0.2.14"
+  resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453"
+  dependencies:
+    dicer "0.2.5"
+    readable-stream "1.1.x"
+
+bytes@3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
+
+cached-constructors-x@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/cached-constructors-x/-/cached-constructors-x-1.0.0.tgz#c421e3892a4b6f7794434bdcffd1299b330c181b"
+
+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"
+
+callsites@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
+
+camelcase@^1.0.2:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
+
+camelcase@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
+
+camelize@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
+
+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"
+
+chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
+  dependencies:
+    ansi-styles "^2.2.1"
+    escape-string-regexp "^1.0.2"
+    has-ansi "^2.0.0"
+    strip-ansi "^3.0.0"
+    supports-color "^2.0.0"
+
+chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e"
+  dependencies:
+    ansi-styles "^3.1.0"
+    escape-string-regexp "^1.0.5"
+    supports-color "^4.0.0"
+
+chownr@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
+
+ci-info@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.1.tgz#47b44df118c48d2597b56d342e7e25791060171a"
+
+circular-json@^0.3.1:
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
+
+cli-cursor@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
+  dependencies:
+    restore-cursor "^1.0.1"
+
+cli-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-table@^0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
+  dependencies:
+    colors "1.0.3"
+
+cli-width@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
+
+cliclopts@^1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/cliclopts/-/cliclopts-1.1.1.tgz#69431c7cb5af723774b0d3911b4c37512431910f"
+
+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"
+
+clone-buffer@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
+
+co@^4.6.0:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+
+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.9.0:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a"
+  dependencies:
+    color-name "^1.1.1"
+
+color-name@^1.1.1:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+
+colors@1.0.3, 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.5.0:
+  version "2.11.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
+
+commoner@^0.10.1:
+  version "0.10.8"
+  resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5"
+  dependencies:
+    commander "^2.5.0"
+    detective "^4.3.1"
+    glob "^5.0.15"
+    graceful-fs "^4.1.2"
+    iconv-lite "^0.4.5"
+    mkdirp "^0.5.0"
+    private "^0.1.6"
+    q "^1.1.2"
+    recast "^0.11.17"
+
+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.5.0, concat-stream@^1.5.2, 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"
+
+config@^1.26.2:
+  version "1.27.0"
+  resolved "https://registry.yarnpkg.com/config/-/config-1.27.0.tgz#3ab30d0080ff76f407c2f47ac1326adfd908af5f"
+  dependencies:
+    json5 "0.4.0"
+    os-homedir "1.0.2"
+
+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"
+
+contains-path@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
+
+content-disposition@0.5.2:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+
+content-security-policy-builder@1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/content-security-policy-builder/-/content-security-policy-builder-1.1.0.tgz#d91f1b076236c119850c7dee9924bf55e05772b3"
+  dependencies:
+    dashify "^0.2.0"
+
+content-type-parser@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.1.tgz#c3e56988c53c65127fb46d4032a3a900246fdc94"
+
+content-type@~1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
+
+convert-source-map@^1.4.0, convert-source-map@^1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5"
+
+cookie-parser@^1.4.3:
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.3.tgz#0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5"
+  dependencies:
+    cookie "0.3.1"
+    cookie-signature "1.0.6"
+
+cookie-signature@1.0.6:
+  version "1.0.6"
+  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-js@^2.4.0, core-js@^2.5.0:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b"
+
+core-util-is@1.0.2, core-util-is@~1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+
+cross-spawn@^5.0.1, cross-spawn@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
+  dependencies:
+    lru-cache "^4.0.1"
+    shebang-command "^1.2.0"
+    which "^1.2.9"
+
+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"
+
+cryptiles@3.x.x:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
+  dependencies:
+    boom "5.x.x"
+
+cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b"
+
+"cssstyle@>= 0.2.37 < 0.3.0":
+  version "0.2.37"
+  resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54"
+  dependencies:
+    cssom "0.3.x"
+
+cvss@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/cvss/-/cvss-1.0.2.tgz#df67e92bf12a796f49e928799c8db3ba74b9fcd6"
+
+cycle@1.0.x:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
+
+d@1:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
+  dependencies:
+    es5-ext "^0.10.9"
+
+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"
+
+dasherize@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308"
+
+dashify@^0.2.0:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/dashify/-/dashify-0.2.2.tgz#6a07415a01c91faf4a32e38d9dfba71f61cb20fe"
+
+debug-log@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f"
+
+debug@2, debug@2.6.9, debug@^2.1.0, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.6.3, debug@^2.6.8:
+  version "2.6.9"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+  dependencies:
+    ms "2.0.0"
+
+debug@2.6.4:
+  version "2.6.4"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.4.tgz#7586a9b3c39741c0282ae33445c4e8ac74734fe0"
+  dependencies:
+    ms "0.7.3"
+
+debug@^3.0.1:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+  dependencies:
+    ms "2.0.0"
+
+decamelize@^1.0.0, decamelize@^1.1.1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+
+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"
+
+deferred-leveldown@~1.2.1:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz#3acd2e0b75d1669924bc0a4b642851131173e1eb"
+  dependencies:
+    abstract-leveldown "~2.6.0"
+
+define-properties@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
+  dependencies:
+    foreach "^2.0.5"
+    object-keys "^1.0.8"
+
+defined@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
+
+deglob@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/deglob/-/deglob-2.1.0.tgz#4d44abe16ef32c779b4972bd141a80325029a14a"
+  dependencies:
+    find-root "^1.0.0"
+    glob "^7.0.5"
+    ignore "^3.0.9"
+    pkg-config "^1.1.0"
+    run-parallel "^1.1.2"
+    uniq "^1.0.1"
+
+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.1, depd@~1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
+
+destroy@~1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
+
+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"
+
+detective@^4.3.1:
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/detective/-/detective-4.5.0.tgz#6e5a8c6b26e6c7a254b1c6b6d7490d98ec91edd1"
+  dependencies:
+    acorn "^4.0.3"
+    defined "^1.0.0"
+
+dicer@0.2.5:
+  version "0.2.5"
+  resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f"
+  dependencies:
+    readable-stream "1.1.x"
+    streamsearch "0.1.2"
+
+diff@^3.2.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c"
+
+dns-prefetch-control@0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2"
+
+doctrine@1.5.0, doctrine@^1.2.2:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
+  dependencies:
+    esutils "^2.0.2"
+    isarray "^1.0.0"
+
+doctrine@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
+  dependencies:
+    esutils "^2.0.2"
+    isarray "^1.0.0"
+
+dont-sniff-mimetype@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz#5932890dc9f4e2f19e5eb02a20026e5e5efc8f58"
+
+dotenv@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d"
+
+double-ended-queue@2.1.0-0:
+  version "2.1.0-0"
+  resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
+
+duplexer2@~0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db"
+  dependencies:
+    readable-stream "~1.1.9"
+
+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"
+
+ecdsa-sig-formatter@1.0.9:
+  version "1.0.9"
+  resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz#4bc926274ec3b5abb5016e7e1d60921ac262b2a1"
+  dependencies:
+    base64url "^2.0.0"
+    safe-buffer "^5.0.1"
+
+ee-first@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+
+encodeurl@~1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20"
+
+end-of-stream@^1.0.0, end-of-stream@^1.1.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206"
+  dependencies:
+    once "^1.4.0"
+
+end-stream@~0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/end-stream/-/end-stream-0.1.0.tgz#32003f3f438a2b0143168137f8fa6e9866c81ed5"
+  dependencies:
+    write-stream "~0.4.3"
+
+errno@^0.1.4, errno@~0.1.1:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d"
+  dependencies:
+    prr "~0.0.0"
+
+error-ex@^1.2.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
+  dependencies:
+    is-arrayish "^0.2.1"
+
+es-abstract@^1.7.0:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.9.0.tgz#690829a07cae36b222e7fd9b75c0d0573eb25227"
+  dependencies:
+    es-to-primitive "^1.1.1"
+    function-bind "^1.1.1"
+    has "^1.0.1"
+    is-callable "^1.1.3"
+    is-regex "^1.0.4"
+
+es-to-primitive@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
+  dependencies:
+    is-callable "^1.1.1"
+    is-date-object "^1.0.1"
+    is-symbol "^1.0.1"
+
+es3ify@^0.2.2:
+  version "0.2.2"
+  resolved "https://registry.yarnpkg.com/es3ify/-/es3ify-0.2.2.tgz#5dae3e650e5be3684b88066513d528d092629862"
+  dependencies:
+    esprima "^2.7.1"
+    jstransform "~11.0.0"
+    through "~2.3.4"
+
+es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14:
+  version "0.10.35"
+  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.35.tgz#18ee858ce6a3c45c7d79e91c15fcca9ec568494f"
+  dependencies:
+    es6-iterator "~2.0.1"
+    es6-symbol "~3.1.1"
+
+es6-iterator@^2.0.1, es6-iterator@~2.0.1:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
+  dependencies:
+    d "1"
+    es5-ext "^0.10.35"
+    es6-symbol "^3.1.1"
+
+es6-map@^0.1.3:
+  version "0.1.5"
+  resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0"
+  dependencies:
+    d "1"
+    es5-ext "~0.10.14"
+    es6-iterator "~2.0.1"
+    es6-set "~0.1.5"
+    es6-symbol "~3.1.1"
+    event-emitter "~0.3.5"
+
+es6-set@~0.1.5:
+  version "0.1.5"
+  resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1"
+  dependencies:
+    d "1"
+    es5-ext "~0.10.14"
+    es6-iterator "~2.0.1"
+    es6-symbol "3.1.1"
+    event-emitter "~0.3.5"
+
+es6-symbol@3.1.1, es6-symbol@^3.0.2, es6-symbol@^3.1.1, es6-symbol@~3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
+  dependencies:
+    d "1"
+    es5-ext "~0.10.14"
+
+es6-weak-map@^2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f"
+  dependencies:
+    d "1"
+    es5-ext "^0.10.14"
+    es6-iterator "^2.0.1"
+    es6-symbol "^3.1.1"
+
+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.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+
+escodegen@^1.6.1:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852"
+  dependencies:
+    esprima "^3.1.3"
+    estraverse "^4.2.0"
+    esutils "^2.0.2"
+    optionator "^0.8.1"
+  optionalDependencies:
+    source-map "~0.5.6"
+
+escope@^3.6.0:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3"
+  dependencies:
+    es6-map "^0.1.3"
+    es6-weak-map "^2.0.1"
+    esrecurse "^4.1.0"
+    estraverse "^4.1.1"
+
+eslint-config-standard-jsx@4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-4.0.2.tgz#009e53c4ddb1e9ee70b4650ffe63a7f39f8836e1"
+
+eslint-config-standard@10.2.1, eslint-config-standard@^10.2.1:
+  version "10.2.1"
+  resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz#c061e4d066f379dc17cd562c64e819b4dd454591"
+
+eslint-import-resolver-node@^0.2.0:
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz#5add8106e8c928db2cba232bcd9efa846e3da16c"
+  dependencies:
+    debug "^2.2.0"
+    object-assign "^4.0.1"
+    resolve "^1.1.6"
+
+eslint-import-resolver-node@^0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz#4422574cde66a9a7b099938ee4d508a199e0e3cc"
+  dependencies:
+    debug "^2.6.8"
+    resolve "^1.2.0"
+
+eslint-module-utils@^2.0.0, eslint-module-utils@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449"
+  dependencies:
+    debug "^2.6.8"
+    pkg-dir "^1.0.0"
+
+eslint-plugin-import@^2.7.0:
+  version "2.8.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz#fa1b6ef31fcb3c501c09859c1b86f1fc5b986894"
+  dependencies:
+    builtin-modules "^1.1.1"
+    contains-path "^0.1.0"
+    debug "^2.6.8"
+    doctrine "1.5.0"
+    eslint-import-resolver-node "^0.3.1"
+    eslint-module-utils "^2.1.1"
+    has "^1.0.1"
+    lodash.cond "^4.3.0"
+    minimatch "^3.0.3"
+    read-pkg-up "^2.0.0"
+
+eslint-plugin-import@~2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.2.0.tgz#72ba306fad305d67c4816348a4699a4229ac8b4e"
+  dependencies:
+    builtin-modules "^1.1.1"
+    contains-path "^0.1.0"
+    debug "^2.2.0"
+    doctrine "1.5.0"
+    eslint-import-resolver-node "^0.2.0"
+    eslint-module-utils "^2.0.0"
+    has "^1.0.1"
+    lodash.cond "^4.3.0"
+    minimatch "^3.0.3"
+    pkg-up "^1.0.0"
+
+eslint-plugin-node@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-5.2.0.tgz#e1efca04a385516cff3f2f04027ce8c5ae6db749"
+  dependencies:
+    ignore "^3.3.3"
+    minimatch "^3.0.4"
+    resolve "^1.3.3"
+    semver "5.3.0"
+
+eslint-plugin-node@~4.2.2:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-4.2.3.tgz#c04390ab8dbcbb6887174023d6f3a72769e63b97"
+  dependencies:
+    ignore "^3.0.11"
+    minimatch "^3.0.2"
+    object-assign "^4.0.1"
+    resolve "^1.1.7"
+    semver "5.3.0"
+
+eslint-plugin-promise@^3.5.0, eslint-plugin-promise@~3.5.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.5.0.tgz#78fbb6ffe047201627569e85a6c5373af2a68fca"
+
+eslint-plugin-react@~6.10.0:
+  version "6.10.3"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz#c5435beb06774e12c7db2f6abaddcbf900cd3f78"
+  dependencies:
+    array.prototype.find "^2.0.1"
+    doctrine "^1.2.2"
+    has "^1.0.1"
+    jsx-ast-utils "^1.3.4"
+    object.assign "^4.0.4"
+
+eslint-plugin-standard@^3.0.1, eslint-plugin-standard@~3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-3.0.1.tgz#34d0c915b45edc6f010393c7eef3823b08565cf2"
+
+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.9.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.9.0.tgz#76879d274068261b191fe0f2f56c74c2f4208e8b"
+  dependencies:
+    ajv "^5.2.0"
+    babel-code-frame "^6.22.0"
+    chalk "^2.1.0"
+    concat-stream "^1.6.0"
+    cross-spawn "^5.1.0"
+    debug "^3.0.1"
+    doctrine "^2.0.0"
+    eslint-scope "^3.7.1"
+    espree "^3.5.1"
+    esquery "^1.0.0"
+    estraverse "^4.2.0"
+    esutils "^2.0.2"
+    file-entry-cache "^2.0.0"
+    functional-red-black-tree "^1.0.1"
+    glob "^7.1.2"
+    globals "^9.17.0"
+    ignore "^3.3.3"
+    imurmurhash "^0.1.4"
+    inquirer "^3.0.6"
+    is-resolvable "^1.0.0"
+    js-yaml "^3.9.1"
+    json-stable-stringify "^1.0.1"
+    levn "^0.3.0"
+    lodash "^4.17.4"
+    minimatch "^3.0.2"
+    mkdirp "^0.5.1"
+    natural-compare "^1.4.0"
+    optionator "^0.8.2"
+    path-is-inside "^1.0.2"
+    pluralize "^7.0.0"
+    progress "^2.0.0"
+    require-uncached "^1.0.3"
+    semver "^5.3.0"
+    strip-ansi "^4.0.0"
+    strip-json-comments "~2.0.1"
+    table "^4.0.1"
+    text-table "~0.2.0"
+
+eslint@~3.19.0:
+  version "3.19.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc"
+  dependencies:
+    babel-code-frame "^6.16.0"
+    chalk "^1.1.3"
+    concat-stream "^1.5.2"
+    debug "^2.1.1"
+    doctrine "^2.0.0"
+    escope "^3.6.0"
+    espree "^3.4.0"
+    esquery "^1.0.0"
+    estraverse "^4.2.0"
+    esutils "^2.0.2"
+    file-entry-cache "^2.0.0"
+    glob "^7.0.3"
+    globals "^9.14.0"
+    ignore "^3.2.0"
+    imurmurhash "^0.1.4"
+    inquirer "^0.12.0"
+    is-my-json-valid "^2.10.0"
+    is-resolvable "^1.0.0"
+    js-yaml "^3.5.1"
+    json-stable-stringify "^1.0.0"
+    levn "^0.3.0"
+    lodash "^4.0.0"
+    mkdirp "^0.5.0"
+    natural-compare "^1.4.0"
+    optionator "^0.8.2"
+    path-is-inside "^1.0.1"
+    pluralize "^1.2.1"
+    progress "^1.1.8"
+    require-uncached "^1.0.2"
+    shelljs "^0.7.5"
+    strip-bom "^3.0.0"
+    strip-json-comments "~2.0.1"
+    table "^3.7.8"
+    text-table "~0.2.0"
+    user-home "^2.0.0"
+
+esmangle-evaluator@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/esmangle-evaluator/-/esmangle-evaluator-1.0.1.tgz#620d866ef4861b3311f75766d52a8572bb3c6336"
+
+espree@^3.4.0, espree@^3.5.1:
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.1.tgz#0c988b8ab46db53100a1954ae4ba995ddd27d87e"
+  dependencies:
+    acorn "^5.1.1"
+    acorn-jsx "^3.0.0"
+
+esprima-fb@^15001.1.0-dev-harmony-fb:
+  version "15001.1.0-dev-harmony-fb"
+  resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901"
+
+esprima-fb@~15001.1001.0-dev-harmony-fb:
+  version "15001.1001.0-dev-harmony-fb"
+  resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz#43beb57ec26e8cf237d3dd8b33e42533577f2659"
+
+esprima@^2.7.1:
+  version "2.7.3"
+  resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
+
+esprima@^3.1.3, esprima@~3.1.0:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
+
+esprima@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
+
+esquery@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa"
+  dependencies:
+    estraverse "^4.0.0"
+
+esrecurse@^4.1.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163"
+  dependencies:
+    estraverse "^4.1.0"
+    object-assign "^4.0.1"
+
+estraverse@^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.1:
+  version "1.8.1"
+  resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+
+event-emitter@~0.3.5:
+  version "0.3.5"
+  resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
+  dependencies:
+    d "1"
+    es5-ext "~0.10.14"
+
+eventsource@^1.0.4:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.5.tgz#1f012c9df0bd8832fd6b1744fea00ccdd479f046"
+  dependencies:
+    original "^1.0.0"
+
+exec-sh@^0.2.0:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38"
+  dependencies:
+    merge "^1.1.3"
+
+execa@^0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
+  dependencies:
+    cross-spawn "^5.0.1"
+    get-stream "^3.0.0"
+    is-stream "^1.1.0"
+    npm-run-path "^2.0.0"
+    p-finally "^1.0.0"
+    signal-exit "^3.0.0"
+    strip-eof "^1.0.0"
+
+execspawn@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/execspawn/-/execspawn-1.0.1.tgz#8286f9dde7cecde7905fbdc04e24f368f23f8da6"
+  dependencies:
+    util-extend "^1.0.1"
+
+exit-hook@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
+
+expand-brackets@^0.1.4:
+  version "0.1.5"
+  resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
+  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-template@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-1.1.0.tgz#e09efba977bf98f9ee0ed25abd0c692e02aec3fc"
+
+expect-ct@0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/expect-ct/-/expect-ct-0.1.0.tgz#52735678de18530890d8d7b95f0ac63640958094"
+
+expect@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/expect/-/expect-21.2.1.tgz#003ac2ac7005c3c29e73b38a272d4afadd6d1d7b"
+  dependencies:
+    ansi-styles "^3.2.0"
+    jest-diff "^21.2.1"
+    jest-get-type "^21.2.0"
+    jest-matcher-utils "^21.2.1"
+    jest-message-util "^21.2.1"
+    jest-regex-util "^21.2.0"
+
+express@^4.16.1:
+  version "4.16.2"
+  resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c"
+  dependencies:
+    accepts "~1.3.4"
+    array-flatten "1.1.1"
+    body-parser "1.18.2"
+    content-disposition "0.5.2"
+    content-type "~1.0.4"
+    cookie "0.3.1"
+    cookie-signature "1.0.6"
+    debug "2.6.9"
+    depd "~1.1.1"
+    encodeurl "~1.0.1"
+    escape-html "~1.0.3"
+    etag "~1.8.1"
+    finalhandler "1.1.0"
+    fresh "0.5.2"
+    merge-descriptors "1.0.1"
+    methods "~1.1.2"
+    on-finished "~2.3.0"
+    parseurl "~1.3.2"
+    path-to-regexp "0.1.7"
+    proxy-addr "~2.0.2"
+    qs "6.5.1"
+    range-parser "~1.2.0"
+    safe-buffer "5.1.1"
+    send "0.16.1"
+    serve-static "1.13.1"
+    setprototypeof "1.1.0"
+    statuses "~1.3.1"
+    type-is "~1.6.15"
+    utils-merge "1.0.1"
+    vary "~1.1.2"
+
+extend@3, extend@^3.0.0, extend@~3.0.0, extend@~3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
+
+external-editor@^2.0.4:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.5.tgz#52c249a3981b9ba187c7cacf5beb50bf1d91a6bc"
+  dependencies:
+    iconv-lite "^0.4.17"
+    jschardet "^1.4.2"
+    tmp "^0.0.33"
+
+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.3.0, extsprintf@^1.2.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
+
+eyes@0.1.x:
+  version "0.1.8"
+  resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
+
+falafel@^1.0.1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/falafel/-/falafel-1.2.0.tgz#c18d24ef5091174a497f318cd24b026a25cddab4"
+  dependencies:
+    acorn "^1.0.3"
+    foreach "^2.0.5"
+    isarray "0.0.1"
+    object-keys "^1.0.6"
+
+fast-deep-equal@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
+
+fast-future@~1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/fast-future/-/fast-future-1.0.2.tgz#8435a9aaa02d79248d17d704e76259301d99280a"
+
+fast-levenshtein@~2.0.4:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+
+fb-watchman@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
+  dependencies:
+    bser "^2.0.0"
+
+figures@^1.3.5:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
+  dependencies:
+    escape-string-regexp "^1.0.5"
+    object-assign "^4.1.0"
+
+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"
+
+fileset@^2.0.2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0"
+  dependencies:
+    glob "^7.0.3"
+    minimatch "^3.0.3"
+
+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"
+
+finalhandler@1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5"
+  dependencies:
+    debug "2.6.9"
+    encodeurl "~1.0.1"
+    escape-html "~1.0.3"
+    on-finished "~2.3.0"
+    parseurl "~1.3.2"
+    statuses "~1.3.1"
+    unpipe "~1.0.0"
+
+find-root@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
+
+find-up@^1.0.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
+  dependencies:
+    path-exists "^2.0.0"
+    pinkie-promise "^2.0.0"
+
+find-up@^2.0.0, 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"
+
+flat-cache@^1.2.1:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481"
+  dependencies:
+    circular-json "^0.3.1"
+    del "^2.0.2"
+    graceful-fs "^4.1.2"
+    write "^0.2.1"
+
+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"
+
+foreach@^2.0.5:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
+
+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@1.0.0-rc4:
+  version "1.0.0-rc4"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.0-rc4.tgz#05ac6bc22227b43e4461f488161554699d4f8b5e"
+  dependencies:
+    async "^1.5.2"
+    combined-stream "^1.0.5"
+    mime-types "^2.1.10"
+
+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"
+
+form-data@~2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf"
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.5"
+    mime-types "^2.1.12"
+
+formidable@^1.0.17:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9"
+
+forwarded@~0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
+
+frameguard@3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/frameguard/-/frameguard-3.0.0.tgz#7bcad469ee7b96e91d12ceb3959c78235a9272e9"
+
+fresh@0.5.2:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+
+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.1.1:
+  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"
+
+function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+
+functional-red-black-tree@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
+
+gauge@~1.2.5:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/gauge/-/gauge-1.2.7.tgz#e9cec5483d3d4ee0ef44b60a7d99e4935e136d93"
+  dependencies:
+    ansi "^0.3.0"
+    has-unicode "^2.0.0"
+    lodash.pad "^4.1.0"
+    lodash.padend "^4.1.0"
+    lodash.padstart "^4.1.0"
+
+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"
+
+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-stdin@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
+
+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"
+
+ghreleases@^1.0.2:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/ghreleases/-/ghreleases-1.0.6.tgz#b1b9b773a9d4090a524497806be0dedd7882219d"
+  dependencies:
+    after "~0.8.1"
+    ghrepos "~2.0.0"
+    ghutils "~3.2.0"
+    simple-mime "~0.1.0"
+    url-template "~2.0.6"
+    xtend "~4.0.0"
+
+ghrepos@~2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ghrepos/-/ghrepos-2.0.0.tgz#d66eae9d98a3b5398e460d6db7e10a742692e81b"
+  dependencies:
+    ghutils "~3.2.0"
+
+ghutils@~3.2.0:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/ghutils/-/ghutils-3.2.1.tgz#4fcedffac935fcace06e12a17c6174e2c29ffe4f"
+  dependencies:
+    jsonist "~1.3.0"
+    xtend "~4.0.1"
+
+github-from-package@0.0.0:
+  version "0.0.0"
+  resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
+
+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@3 || 4 || 5 || 6 || 7", glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, 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"
+
+glob@^5.0.15:
+  version "5.0.15"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
+  dependencies:
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "2 || 3"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+globals@^9.14.0, globals@^9.17.0, globals@^9.18.0:
+  version "9.18.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
+
+globby@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d"
+  dependencies:
+    array-union "^1.0.1"
+    arrify "^1.0.0"
+    glob "^7.0.3"
+    object-assign "^4.0.1"
+    pify "^2.0.0"
+    pinkie-promise "^2.0.0"
+
+graceful-fs@^4.1.11, graceful-fs@^4.1.2:
+  version "4.1.11"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
+
+growly@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
+
+handlebars@^4.0.3:
+  version "4.0.11"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc"
+  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-schema@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
+
+har-validator@~4.2.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"
+
+har-validator@~5.0.3:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
+  dependencies:
+    ajv "^5.1.0"
+    har-schema "^2.0.0"
+
+has-ansi@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
+  dependencies:
+    ansi-regex "^2.0.0"
+
+has-flag@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
+
+has-flag@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
+
+has-own-property-x@^3.1.1:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/has-own-property-x/-/has-own-property-x-3.2.0.tgz#1c4b112a577c8cb5805469556e54b6e959e4ded9"
+  dependencies:
+    cached-constructors-x "^1.0.0"
+    to-object-x "^1.5.0"
+    to-property-key-x "^2.0.2"
+
+has-symbol-support-x@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz#66ec2e377e0c7d7ccedb07a3a84d77510ff1bc4c"
+
+has-to-string-tag-x@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d"
+  dependencies:
+    has-symbol-support-x "^1.4.1"
+
+has-unicode@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+
+has@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
+  dependencies:
+    function-bind "^1.0.2"
+
+hawk@3.1.3, 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"
+
+hawk@~6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
+  dependencies:
+    boom "4.x.x"
+    cryptiles "3.x.x"
+    hoek "4.x.x"
+    sntp "2.x.x"
+
+helmet-csp@2.6.0:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.6.0.tgz#c1f5595afbc5f83e5f1e6c15f842f07a10f6ea04"
+  dependencies:
+    camelize "1.0.0"
+    content-security-policy-builder "1.1.0"
+    dasherize "2.0.0"
+    lodash.reduce "4.6.0"
+    platform "1.3.4"
+
+helmet@^3.8.1:
+  version "3.9.0"
+  resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.9.0.tgz#7b2cf015a2d109bca83ede7924420799c0e67dee"
+  dependencies:
+    dns-prefetch-control "0.1.0"
+    dont-sniff-mimetype "1.0.0"
+    expect-ct "0.1.0"
+    frameguard "3.0.0"
+    helmet-csp "2.6.0"
+    hide-powered-by "1.0.0"
+    hpkp "2.0.0"
+    hsts "2.1.0"
+    ienoopen "1.0.0"
+    nocache "2.0.0"
+    referrer-policy "1.1.0"
+    x-xss-protection "1.0.0"
+
+hide-powered-by@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/hide-powered-by/-/hide-powered-by-1.0.0.tgz#4a85ad65881f62857fc70af7174a1184dccce32b"
+
+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.2.0"
+  resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
+
+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"
+
+hosted-git-info@^2.1.4:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
+
+hpkp@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/hpkp/-/hpkp-2.0.0.tgz#10e142264e76215a5d30c44ec43de64dee6d1672"
+
+hsts@2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/hsts/-/hsts-2.1.0.tgz#cbd6c918a2385fee1dd5680bfb2b3a194c0121cc"
+
+html-encoding-sniffer@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz#79bf7a785ea495fe66165e734153f363ff5437da"
+  dependencies:
+    whatwg-encoding "^1.0.1"
+
+http-errors@1.6.2, http-errors@~1.6.2:
+  version "1.6.2"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
+  dependencies:
+    depd "1.1.1"
+    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"
+
+http-signature@~1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
+  dependencies:
+    assert-plus "^1.0.0"
+    jsprim "^1.2.2"
+    sshpk "^1.7.0"
+
+http-status-codes@^1.0.6:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.3.0.tgz#9cd0e71391773d0671b489d41cbc5094aa4163b6"
+
+https-proxy-agent@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz#35f7da6c48ce4ddbfa264891ac593ee5ff8671e6"
+  dependencies:
+    agent-base "2"
+    debug "2"
+    extend "3"
+
+hyperquest@~1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/hyperquest/-/hyperquest-1.2.0.tgz#39e1fef66888dc7ce0dec6c0dd814f6fc8944ad5"
+  dependencies:
+    duplexer2 "~0.0.2"
+    through2 "~0.6.3"
+
+i@0.3.x:
+  version "0.3.6"
+  resolved "https://registry.yarnpkg.com/i/-/i-0.3.6.tgz#d96c92732076f072711b6b10fd7d4f65ad8ee23d"
+
+iconv-lite@0.4.13:
+  version "0.4.13"
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
+
+iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@^0.4.5:
+  version "0.4.19"
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
+
+ienoopen@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/ienoopen/-/ienoopen-1.0.0.tgz#346a428f474aac8f50cf3784ea2d0f16f62bda6b"
+
+ignore@^3.0.11, ignore@^3.0.9, ignore@^3.2.0, ignore@^3.3.3:
+  version "3.3.5"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.5.tgz#c4e715455f6073a8d7e5dae72d2fc9d71663dba6"
+
+immediate@3.0.6, immediate@~3.0.5:
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
+
+immediate@^3.2.3:
+  version "3.2.3"
+  resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c"
+
+imurmurhash@^0.1.4:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+
+infinity-x@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/infinity-x/-/infinity-x-1.0.0.tgz#cea2d75181d820961b0f72d78e7c4e06fdd55a07"
+
+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"
+
+ini@~1.3.0:
+  version "1.3.4"
+  resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
+
+inline-process-browser@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/inline-process-browser/-/inline-process-browser-1.0.0.tgz#46a61b153dd3c9b1624b1a00626edb4f7f414f22"
+  dependencies:
+    falafel "^1.0.1"
+    through2 "^0.6.5"
+
+inquirer@^0.12.0:
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
+  dependencies:
+    ansi-escapes "^1.1.0"
+    ansi-regex "^2.0.0"
+    chalk "^1.0.0"
+    cli-cursor "^1.0.1"
+    cli-width "^2.0.0"
+    figures "^1.3.5"
+    lodash "^4.3.0"
+    readline2 "^1.0.1"
+    run-async "^0.1.0"
+    rx-lite "^3.1.2"
+    string-width "^1.0.1"
+    strip-ansi "^3.0.0"
+    through "^2.3.6"
+
+inquirer@^3.0.6:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9"
+  dependencies:
+    ansi-escapes "^3.0.0"
+    chalk "^2.0.0"
+    cli-cursor "^2.1.0"
+    cli-width "^2.0.0"
+    external-editor "^2.0.4"
+    figures "^2.0.0"
+    lodash "^4.3.0"
+    mute-stream "0.0.7"
+    run-async "^2.2.0"
+    rx-lite "^4.0.8"
+    rx-lite-aggregates "^4.0.8"
+    string-width "^2.1.0"
+    strip-ansi "^4.0.0"
+    through "^2.3.6"
+
+interpret@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0"
+
+invariant@^2.2.2:
+  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.5.2:
+  version "1.5.2"
+  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0"
+
+is-array-buffer-x@^1.0.13:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/is-array-buffer-x/-/is-array-buffer-x-1.7.0.tgz#4b0b10427b64aa3437767adf4fc07702c59b2371"
+  dependencies:
+    attempt-x "^1.1.0"
+    has-to-string-tag-x "^1.4.1"
+    is-object-like-x "^1.5.1"
+    object-get-own-property-descriptor-x "^3.2.0"
+    to-string-tag-x "^1.4.1"
+
+is-array@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-array/-/is-array-1.0.1.tgz#e9850cc2cc860c3bc0977e84ccf0dd464584279a"
+
+is-arrayish@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+
+is-buffer@^1.1.5:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
+
+is-builtin-module@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
+  dependencies:
+    builtin-modules "^1.0.0"
+
+is-callable@^1.1.1, is-callable@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2"
+
+is-ci@^1.0.10:
+  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-date-object@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
+
+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-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-falsey-x@^1.0.0, is-falsey-x@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-falsey-x/-/is-falsey-x-1.0.1.tgz#c469951adc95b8b3fdbf90929b335a7de937d17f"
+  dependencies:
+    to-boolean-x "^1.0.1"
+
+is-finite-x@^3.0.1:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/is-finite-x/-/is-finite-x-3.0.2.tgz#a6ec683cfb2bc1a918a1ff59d178edbcea54f7a6"
+  dependencies:
+    infinity-x "^1.0.0"
+    is-nan-x "^1.0.1"
+
+is-finite@^1.0.0:
+  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-function-x@^3.2.0, is-function-x@^3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/is-function-x/-/is-function-x-3.3.0.tgz#7d16bc113853db206d5e40a8b32caf99bd4ff7c0"
+  dependencies:
+    attempt-x "^1.1.1"
+    has-to-string-tag-x "^1.4.1"
+    is-falsey-x "^1.0.1"
+    is-primitive "^2.0.0"
+    normalize-space-x "^3.0.0"
+    replace-comments-x "^2.0.0"
+    to-boolean-x "^1.0.1"
+    to-string-tag-x "^1.4.2"
+
+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-index-x@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-index-x/-/is-index-x-1.0.0.tgz#55f022f861dbc44a862bc8ea03f6c3666fa168fc"
+  dependencies:
+    math-clamp-x "^1.0.0"
+    max-safe-integer "^1.0.1"
+    safe-to-string-x "^2.0.1"
+    to-integer-x "^2.0.1"
+    to-number-x "^1.0.1"
+
+is-my-json-valid@^2.10.0:
+  version "2.16.1"
+  resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11"
+  dependencies:
+    generate-function "^2.0.0"
+    generate-object-property "^1.1.0"
+    jsonpointer "^4.0.0"
+    xtend "^4.0.0"
+
+is-nan-x@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-nan-x/-/is-nan-x-1.0.1.tgz#de747ebcc8bddeb66f367c17caca7eba843855c0"
+
+is-nil-x@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/is-nil-x/-/is-nil-x-1.4.1.tgz#bd9e7b08b4cd732f9dcbde13d93291bb2ec2e248"
+  dependencies:
+    lodash.isnull "^3.0.0"
+    validate.io-undefined "^1.0.3"
+
+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-object-like-x@^1.5.1:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/is-object-like-x/-/is-object-like-x-1.6.0.tgz#a8c4a95bd6b95db174e0e4730171a160ec73be82"
+  dependencies:
+    is-function-x "^3.3.0"
+    is-primitive "^2.0.0"
+
+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-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-regex@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
+  dependencies:
+    has "^1.0.1"
+
+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-stream@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+
+is-string@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64"
+
+is-symbol@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
+
+is-typedarray@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+
+is-utf8@^0.2.0:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
+
+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@1.x.x:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a"
+
+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"
+
+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-api@^1.1.1:
+  version "1.1.14"
+  resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.14.tgz#25bc5701f7c680c0ffff913de46e3619a3a6e680"
+  dependencies:
+    async "^2.1.4"
+    fileset "^2.0.2"
+    istanbul-lib-coverage "^1.1.1"
+    istanbul-lib-hook "^1.0.7"
+    istanbul-lib-instrument "^1.8.0"
+    istanbul-lib-report "^1.1.1"
+    istanbul-lib-source-maps "^1.2.1"
+    istanbul-reports "^1.1.2"
+    js-yaml "^3.7.0"
+    mkdirp "^0.5.1"
+    once "^1.4.0"
+
+istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da"
+
+istanbul-lib-hook@^1.0.7:
+  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.4.2, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.8.0:
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.8.0.tgz#66f6c9421cc9ec4704f76f2db084ba9078a2b532"
+  dependencies:
+    babel-generator "^6.18.0"
+    babel-template "^6.16.0"
+    babel-traverse "^6.18.0"
+    babel-types "^6.18.0"
+    babylon "^6.18.0"
+    istanbul-lib-coverage "^1.1.1"
+    semver "^5.3.0"
+
+istanbul-lib-report@^1.1.1:
+  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.1.0, istanbul-lib-source-maps@^1.2.1:
+  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.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.2.tgz#0fb2e3f6aa9922bd3ce45d05d8ab4d5e8e07bd4f"
+  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-changed-files@^21.2.0:
+  version "21.2.0"
+  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-21.2.0.tgz#5dbeecad42f5d88b482334902ce1cba6d9798d29"
+  dependencies:
+    throat "^4.0.0"
+
+jest-cli@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-21.2.1.tgz#9c528b6629d651911138d228bdb033c157ec8c00"
+  dependencies:
+    ansi-escapes "^3.0.0"
+    chalk "^2.0.1"
+    glob "^7.1.2"
+    graceful-fs "^4.1.11"
+    is-ci "^1.0.10"
+    istanbul-api "^1.1.1"
+    istanbul-lib-coverage "^1.0.1"
+    istanbul-lib-instrument "^1.4.2"
+    istanbul-lib-source-maps "^1.1.0"
+    jest-changed-files "^21.2.0"
+    jest-config "^21.2.1"
+    jest-environment-jsdom "^21.2.1"
+    jest-haste-map "^21.2.0"
+    jest-message-util "^21.2.1"
+    jest-regex-util "^21.2.0"
+    jest-resolve-dependencies "^21.2.0"
+    jest-runner "^21.2.1"
+    jest-runtime "^21.2.1"
+    jest-snapshot "^21.2.1"
+    jest-util "^21.2.1"
+    micromatch "^2.3.11"
+    node-notifier "^5.0.2"
+    pify "^3.0.0"
+    slash "^1.0.0"
+    string-length "^2.0.0"
+    strip-ansi "^4.0.0"
+    which "^1.2.12"
+    worker-farm "^1.3.1"
+    yargs "^9.0.0"
+
+jest-config@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-21.2.1.tgz#c7586c79ead0bcc1f38c401e55f964f13bf2a480"
+  dependencies:
+    chalk "^2.0.1"
+    glob "^7.1.1"
+    jest-environment-jsdom "^21.2.1"
+    jest-environment-node "^21.2.1"
+    jest-get-type "^21.2.0"
+    jest-jasmine2 "^21.2.1"
+    jest-regex-util "^21.2.0"
+    jest-resolve "^21.2.0"
+    jest-util "^21.2.1"
+    jest-validate "^21.2.1"
+    pretty-format "^21.2.1"
+
+jest-diff@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-21.2.1.tgz#46cccb6cab2d02ce98bc314011764bb95b065b4f"
+  dependencies:
+    chalk "^2.0.1"
+    diff "^3.2.0"
+    jest-get-type "^21.2.0"
+    pretty-format "^21.2.1"
+
+jest-docblock@^21.2.0:
+  version "21.2.0"
+  resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414"
+
+jest-environment-jsdom@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-21.2.1.tgz#38d9980c8259b2a608ec232deee6289a60d9d5b4"
+  dependencies:
+    jest-mock "^21.2.0"
+    jest-util "^21.2.1"
+    jsdom "^9.12.0"
+
+jest-environment-node@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-21.2.1.tgz#98c67df5663c7fbe20f6e792ac2272c740d3b8c8"
+  dependencies:
+    jest-mock "^21.2.0"
+    jest-util "^21.2.1"
+
+jest-get-type@^21.2.0:
+  version "21.2.0"
+  resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.2.0.tgz#f6376ab9db4b60d81e39f30749c6c466f40d4a23"
+
+jest-haste-map@^21.2.0:
+  version "21.2.0"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-21.2.0.tgz#1363f0a8bb4338f24f001806571eff7a4b2ff3d8"
+  dependencies:
+    fb-watchman "^2.0.0"
+    graceful-fs "^4.1.11"
+    jest-docblock "^21.2.0"
+    micromatch "^2.3.11"
+    sane "^2.0.0"
+    worker-farm "^1.3.1"
+
+jest-jasmine2@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-21.2.1.tgz#9cc6fc108accfa97efebce10c4308548a4ea7592"
+  dependencies:
+    chalk "^2.0.1"
+    expect "^21.2.1"
+    graceful-fs "^4.1.11"
+    jest-diff "^21.2.1"
+    jest-matcher-utils "^21.2.1"
+    jest-message-util "^21.2.1"
+    jest-snapshot "^21.2.1"
+    p-cancelable "^0.3.0"
+
+jest-matcher-utils@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-21.2.1.tgz#72c826eaba41a093ac2b4565f865eb8475de0f64"
+  dependencies:
+    chalk "^2.0.1"
+    jest-get-type "^21.2.0"
+    pretty-format "^21.2.1"
+
+jest-message-util@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-21.2.1.tgz#bfe5d4692c84c827d1dcf41823795558f0a1acbe"
+  dependencies:
+    chalk "^2.0.1"
+    micromatch "^2.3.11"
+    slash "^1.0.0"
+
+jest-mock@^21.2.0:
+  version "21.2.0"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-21.2.0.tgz#7eb0770e7317968165f61ea2a7281131534b3c0f"
+
+jest-regex-util@^21.2.0:
+  version "21.2.0"
+  resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-21.2.0.tgz#1b1e33e63143babc3e0f2e6c9b5ba1eb34b2d530"
+
+jest-resolve-dependencies@^21.2.0:
+  version "21.2.0"
+  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-21.2.0.tgz#9e231e371e1a736a1ad4e4b9a843bc72bfe03d09"
+  dependencies:
+    jest-regex-util "^21.2.0"
+
+jest-resolve@^21.2.0:
+  version "21.2.0"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-21.2.0.tgz#068913ad2ba6a20218e5fd32471f3874005de3a6"
+  dependencies:
+    browser-resolve "^1.11.2"
+    chalk "^2.0.1"
+    is-builtin-module "^1.0.0"
+
+jest-runner@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-21.2.1.tgz#194732e3e518bfb3d7cbfc0fd5871246c7e1a467"
+  dependencies:
+    jest-config "^21.2.1"
+    jest-docblock "^21.2.0"
+    jest-haste-map "^21.2.0"
+    jest-jasmine2 "^21.2.1"
+    jest-message-util "^21.2.1"
+    jest-runtime "^21.2.1"
+    jest-util "^21.2.1"
+    pify "^3.0.0"
+    throat "^4.0.0"
+    worker-farm "^1.3.1"
+
+jest-runtime@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-21.2.1.tgz#99dce15309c670442eee2ebe1ff53a3cbdbbb73e"
+  dependencies:
+    babel-core "^6.0.0"
+    babel-jest "^21.2.0"
+    babel-plugin-istanbul "^4.0.0"
+    chalk "^2.0.1"
+    convert-source-map "^1.4.0"
+    graceful-fs "^4.1.11"
+    jest-config "^21.2.1"
+    jest-haste-map "^21.2.0"
+    jest-regex-util "^21.2.0"
+    jest-resolve "^21.2.0"
+    jest-util "^21.2.1"
+    json-stable-stringify "^1.0.1"
+    micromatch "^2.3.11"
+    slash "^1.0.0"
+    strip-bom "3.0.0"
+    write-file-atomic "^2.1.0"
+    yargs "^9.0.0"
+
+jest-snapshot@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-21.2.1.tgz#29e49f16202416e47343e757e5eff948c07fd7b0"
+  dependencies:
+    chalk "^2.0.1"
+    jest-diff "^21.2.1"
+    jest-matcher-utils "^21.2.1"
+    mkdirp "^0.5.1"
+    natural-compare "^1.4.0"
+    pretty-format "^21.2.1"
+
+jest-util@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-21.2.1.tgz#a274b2f726b0897494d694a6c3d6a61ab819bb78"
+  dependencies:
+    callsites "^2.0.0"
+    chalk "^2.0.1"
+    graceful-fs "^4.1.11"
+    jest-message-util "^21.2.1"
+    jest-mock "^21.2.0"
+    jest-validate "^21.2.1"
+    mkdirp "^0.5.1"
+
+jest-validate@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-21.2.1.tgz#cc0cbca653cd54937ba4f2a111796774530dd3c7"
+  dependencies:
+    chalk "^2.0.1"
+    jest-get-type "^21.2.0"
+    leven "^2.1.0"
+    pretty-format "^21.2.1"
+
+joi@^10.0.6, joi@^10.6.0:
+  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"
+
+joi@^6.10.1, joi@^6.9.1:
+  version "6.10.1"
+  resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06"
+  dependencies:
+    hoek "2.x.x"
+    isemail "1.x.x"
+    moment "2.x.x"
+    topo "1.x.x"
+
+js-tokens@^3.0.0, js-tokens@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
+
+js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@^3.9.1:
+  version "3.10.0"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
+  dependencies:
+    argparse "^1.0.7"
+    esprima "^4.0.0"
+
+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.5.1"
+  resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.1.tgz#c519f629f86b3a5bedba58a88d311309eec097f9"
+
+jsdom@^9.12.0:
+  version "9.12.0"
+  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4"
+  dependencies:
+    abab "^1.0.3"
+    acorn "^4.0.4"
+    acorn-globals "^3.1.0"
+    array-equal "^1.0.0"
+    content-type-parser "^1.0.1"
+    cssom ">= 0.3.2 < 0.4.0"
+    cssstyle ">= 0.2.37 < 0.3.0"
+    escodegen "^1.6.1"
+    html-encoding-sniffer "^1.0.1"
+    nwmatcher ">= 1.3.9 < 2.0.0"
+    parse5 "^1.5.1"
+    request "^2.79.0"
+    sax "^1.2.1"
+    symbol-tree "^3.2.1"
+    tough-cookie "^2.3.2"
+    webidl-conversions "^4.0.0"
+    whatwg-encoding "^1.0.1"
+    whatwg-url "^4.3.0"
+    xml-name-validator "^2.0.1"
+
+jsesc@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
+
+json-schema-traverse@^0.3.0:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
+
+json-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.0, 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.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.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-0.4.0.tgz#054352e4c4c80c86c0923877d449de176a732c8d"
+
+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"
+
+jsonist@~1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/jsonist/-/jsonist-1.3.0.tgz#c0c74b95ef1c952038619b29efa520b1cc987556"
+  dependencies:
+    bl "~1.0.0"
+    hyperquest "~1.2.0"
+    json-stringify-safe "~5.0.0"
+    xtend "~4.0.0"
+
+jsonpointer@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
+
+jsonwebtoken@^7.1.7:
+  version "7.4.3"
+  resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.4.3.tgz#77f5021de058b605a1783fa1283e99812e645638"
+  dependencies:
+    joi "^6.10.1"
+    jws "^3.1.4"
+    lodash.once "^4.0.0"
+    ms "^2.0.0"
+    xtend "^4.0.1"
+
+jsprim@^1.2.2:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
+  dependencies:
+    assert-plus "1.0.0"
+    extsprintf "1.3.0"
+    json-schema "0.2.3"
+    verror "1.10.0"
+
+jstransform@~11.0.0:
+  version "11.0.3"
+  resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223"
+  dependencies:
+    base62 "^1.1.0"
+    commoner "^0.10.1"
+    esprima-fb "^15001.1.0-dev-harmony-fb"
+    object-assign "^2.0.0"
+    source-map "^0.4.2"
+
+jsx-ast-utils@^1.3.4:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz#3867213e8dd79bf1e8f2300c0cfc1efb182c0df1"
+
+jwa@^1.1.4:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.5.tgz#a0552ce0220742cd52e153774a32905c30e756e5"
+  dependencies:
+    base64url "2.0.0"
+    buffer-equal-constant-time "1.0.1"
+    ecdsa-sig-formatter "1.0.9"
+    safe-buffer "^5.0.1"
+
+jws@^3.1.4:
+  version "3.1.4"
+  resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.4.tgz#f9e8b9338e8a847277d6444b1464f61880e050a2"
+  dependencies:
+    base64url "^2.0.0"
+    jwa "^1.1.4"
+    safe-buffer "^5.0.1"
+
+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"
+
+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"
+
+level-codec@7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-7.0.0.tgz#c755b68d0d44ffa0b1cba044b8f81a55a14ad39b"
+
+level-codec@~6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-6.1.0.tgz#f5df0a99582f76dac43855151ab6f4e4d0d60045"
+
+level-errors@^1.0.3:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/level-errors/-/level-errors-1.1.1.tgz#52fdc2dbbaf395cf767db843929a38b7015678d2"
+  dependencies:
+    errno "~0.1.1"
+
+level-errors@~1.0.3:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/level-errors/-/level-errors-1.0.5.tgz#83dbfb12f0b8a2516bdc9a31c4876038e227b859"
+  dependencies:
+    errno "~0.1.1"
+
+level-iterator-stream@~1.3.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz#e43b78b1a8143e6fa97a4f485eb8ea530352f2ed"
+  dependencies:
+    inherits "^2.0.1"
+    level-errors "^1.0.3"
+    readable-stream "^1.0.33"
+    xtend "^4.0.0"
+
+level-write-stream@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/level-write-stream/-/level-write-stream-1.0.0.tgz#3f7fbb679a55137c0feb303dee766e12ee13c1dc"
+  dependencies:
+    end-stream "~0.1.0"
+
+leveldown@1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/leveldown/-/leveldown-1.5.0.tgz#6b8d3cbea7a4a89aa47444607d7358213e6fcb81"
+  dependencies:
+    abstract-leveldown "~2.6.1"
+    bindings "~1.2.1"
+    fast-future "~1.0.0"
+    nan "~2.4.0"
+    prebuild "^4.1.1"
+
+levelup@1.3.8:
+  version "1.3.8"
+  resolved "https://registry.yarnpkg.com/levelup/-/levelup-1.3.8.tgz#fb442c488efbea1043f7eb9929a792a74fbd1da6"
+  dependencies:
+    deferred-leveldown "~1.2.1"
+    level-codec "~6.1.0"
+    level-errors "~1.0.3"
+    level-iterator-stream "~1.3.0"
+    prr "~1.0.1"
+    semver "~5.1.0"
+    xtend "~4.0.0"
+
+leven@^2.1.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"
+
+lie@3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/lie/-/lie-3.0.4.tgz#bc7ae1ebe7f1c8de39afdcd4f789076b47b0f634"
+  dependencies:
+    es3ify "^0.2.2"
+    immediate "~3.0.5"
+    inline-process-browser "^1.0.0"
+    unreachable-branch-transform "^0.3.0"
+
+lie@3.1.1, lie@^3.1.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
+  dependencies:
+    immediate "~3.0.5"
+
+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.cond@^4.3.0:
+  version "4.5.2"
+  resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
+
+lodash.isnull@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/lodash.isnull/-/lodash.isnull-3.0.0.tgz#fafbe59ea1dca27eed786534039dd84c2e07c56e"
+
+lodash.once@^4.0.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
+
+lodash.pad@^4.1.0:
+  version "4.5.1"
+  resolved "https://registry.yarnpkg.com/lodash.pad/-/lodash.pad-4.5.1.tgz#4330949a833a7c8da22cc20f6a26c4d59debba70"
+
+lodash.padend@^4.1.0:
+  version "4.6.1"
+  resolved "https://registry.yarnpkg.com/lodash.padend/-/lodash.padend-4.6.1.tgz#53ccba047d06e158d311f45da625f4e49e6f166e"
+
+lodash.padstart@^4.1.0:
+  version "4.6.1"
+  resolved "https://registry.yarnpkg.com/lodash.padstart/-/lodash.padstart-4.6.1.tgz#d2e3eebff0d9d39ad50f5cbd1b52a7bce6bb611b"
+
+lodash.reduce@4.6.0:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b"
+
+lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.3.0:
+  version "4.17.4"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
+
+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:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
+  dependencies:
+    js-tokens "^3.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"
+
+ltgt@2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.0.tgz#b65ba5fcb349a29924c8e333f7c6a5562f2e4842"
+
+ltgt@~2.1.3:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.1.3.tgz#10851a06d9964b971178441c23c9e52698eece34"
+
+makeerror@1.0.x:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
+  dependencies:
+    tmpl "1.0.x"
+
+math-clamp-x@^1.0.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/math-clamp-x/-/math-clamp-x-1.2.0.tgz#8b537be0645bbba7ee73ee16091e7d6018c5edcf"
+  dependencies:
+    to-number-x "^2.0.0"
+
+math-sign-x@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/math-sign-x/-/math-sign-x-2.1.0.tgz#1be7ae9ed89b976e007d0ef36b28c5db6fef430c"
+  dependencies:
+    is-nan-x "^1.0.1"
+    to-number-x "^1.1.0"
+
+max-safe-integer@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/max-safe-integer/-/max-safe-integer-1.0.1.tgz#f38060be2c563d8c02e6d48af39122fd83b6f410"
+
+media-typer@0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+
+mem@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
+  dependencies:
+    mimic-fn "^1.0.0"
+
+memdown@1.2.4:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/memdown/-/memdown-1.2.4.tgz#cd9a34aaf074d53445a271108eb4b8dd4ec0f27f"
+  dependencies:
+    abstract-leveldown "2.4.1"
+    functional-red-black-tree "^1.0.1"
+    immediate "^3.2.3"
+    inherits "~2.0.1"
+    ltgt "~2.1.3"
+
+merge-descriptors@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+
+merge@^1.1.3:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
+
+methods@1.x, 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:
+  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.30.0:
+  version "1.30.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
+
+mime-types@^2.1.10, mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7:
+  version "2.1.17"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
+  dependencies:
+    mime-db "~1.30.0"
+
+mime@1.4.1, mime@^1.3.4:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
+
+mimic-fn@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
+
+"minimatch@2 || 3", minimatch@3, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+  dependencies:
+    brace-expansion "^1.1.7"
+
+minimist@0.0.8:
+  version "0.0.8"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+
+minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.2, minimist@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
+
+minimist@~0.0.1:
+  version "0.0.10"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
+
+mkdirp@0.x.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1:
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+  dependencies:
+    minimist "0.0.8"
+
+moment@2.x.x:
+  version "2.19.1"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.1.tgz#56da1a2d1cbf01d38b7e1afc31c10bcfa1929167"
+
+morgan@^1.8.2:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.0.tgz#d01fa6c65859b76fcf31b3cb53a3821a311d8051"
+  dependencies:
+    basic-auth "~2.0.0"
+    debug "2.6.9"
+    depd "~1.1.1"
+    on-finished "~2.3.0"
+    on-headers "~1.0.1"
+
+ms@0.7.3:
+  version "0.7.3"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.3.tgz#708155a5e44e33f5fd0fc53e81d0d40a91be1fff"
+
+ms@2.0.0, ms@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+
+multer@^1.1.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/multer/-/multer-1.3.0.tgz#092b2670f6846fa4914965efc8cf94c20fec6cd2"
+  dependencies:
+    append-field "^0.1.0"
+    busboy "^0.2.11"
+    concat-stream "^1.5.0"
+    mkdirp "^0.5.1"
+    object-assign "^3.0.0"
+    on-finished "^2.3.0"
+    type-is "^1.6.4"
+    xtend "^4.0.0"
+
+mute-stream@0.0.5:
+  version "0.0.5"
+  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
+
+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-x@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/nan-x/-/nan-x-1.0.0.tgz#0ee78e8d1cd0592d5b4260a5940154545c61c121"
+
+nan@2.6.2:
+  version "2.6.2"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
+
+nan@^2.3.0:
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46"
+
+nan@~2.4.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.4.0.tgz#fb3c59d45fe4effe215f0b890f8adf6eb32d2232"
+
+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"
+
+nocache@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.0.0.tgz#202b48021a0c4cbde2df80de15a17443c8b43980"
+
+node-gyp@^3.0.3:
+  version "3.6.2"
+  resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60"
+  dependencies:
+    fstream "^1.0.0"
+    glob "^7.0.3"
+    graceful-fs "^4.1.2"
+    minimatch "^3.0.2"
+    mkdirp "^0.5.0"
+    nopt "2 || 3"
+    npmlog "0 || 1 || 2 || 3 || 4"
+    osenv "0"
+    request "2"
+    rimraf "2"
+    semver "~5.3.0"
+    tar "^2.0.0"
+    which "1"
+
+node-int64@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
+
+node-ninja@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/node-ninja/-/node-ninja-1.0.2.tgz#20a09e57b92e2df591993d4bf098ac3e727062b6"
+  dependencies:
+    fstream "^1.0.0"
+    glob "3 || 4 || 5 || 6 || 7"
+    graceful-fs "^4.1.2"
+    minimatch "3"
+    mkdirp "^0.5.0"
+    nopt "2 || 3"
+    npmlog "0 || 1 || 2"
+    osenv "0"
+    path-array "^1.0.0"
+    request "2"
+    rimraf "2"
+    semver "2.x || 3.x || 4 || 5"
+    tar "^2.0.0"
+    which "1"
+
+node-notifier@^5.0.2:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff"
+  dependencies:
+    growly "^1.3.0"
+    semver "^5.3.0"
+    shellwords "^0.1.0"
+    which "^1.2.12"
+
+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"
+
+node-pre-gyp@^0.6.36:
+  version "0.6.38"
+  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz#e92a20f83416415bb4086f6d1fb78b3da73d113d"
+  dependencies:
+    hawk "3.1.3"
+    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"
+
+nodesecurity-npm-utils@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/nodesecurity-npm-utils/-/nodesecurity-npm-utils-5.0.0.tgz#05aa30de30ca8c845c4048e94fd78e5e08b55ed9"
+
+noop-logger@^0.1.0:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2"
+
+"nopt@2 || 3":
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
+  dependencies:
+    abbrev "1"
+
+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:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
+  dependencies:
+    hosted-git-info "^2.1.4"
+    is-builtin-module "^1.0.0"
+    semver "2 || 3 || 4 || 5"
+    validate-npm-package-license "^3.0.1"
+
+normalize-path@^2.0.0, normalize-path@^2.0.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
+  dependencies:
+    remove-trailing-separator "^1.0.1"
+
+normalize-space-x@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/normalize-space-x/-/normalize-space-x-3.0.0.tgz#17907d6c7c724a4f9567471cbb319553bc0f8882"
+  dependencies:
+    cached-constructors-x "^1.0.0"
+    trim-x "^3.0.0"
+    white-space-x "^3.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@0 || 1 || 2", npmlog@^2.0.0:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-2.0.4.tgz#98b52530f2514ca90d09ec5b22c8846722375692"
+  dependencies:
+    ansi "~0.3.1"
+    are-we-there-yet "~1.1.2"
+    gauge "~1.2.5"
+
+"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.2:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
+  dependencies:
+    are-we-there-yet "~1.1.2"
+    console-control-strings "~1.1.0"
+    gauge "~2.7.3"
+    set-blocking "~2.0.0"
+
+nsp@^2.7.0:
+  version "2.8.1"
+  resolved "https://registry.yarnpkg.com/nsp/-/nsp-2.8.1.tgz#436e3f13869e0610d3a38f59d55f9b353cc32817"
+  dependencies:
+    chalk "^1.1.1"
+    cli-table "^0.3.1"
+    cvss "^1.0.0"
+    https-proxy-agent "^1.0.0"
+    joi "^6.9.1"
+    nodesecurity-npm-utils "^5.0.0"
+    path-is-absolute "^1.0.0"
+    rc "^1.1.2"
+    semver "^5.0.3"
+    subcommand "^2.0.3"
+    wreck "^6.3.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"
+
+"nwmatcher@>= 1.3.9 < 2.0.0":
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c"
+
+oauth-sign@~0.8.1, oauth-sign@~0.8.2:
+  version "0.8.2"
+  resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
+
+object-assign@^2.0.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa"
+
+object-assign@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
+
+object-assign@^4.0.1, object-assign@^4.1.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+
+object-get-own-property-descriptor-x@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/object-get-own-property-descriptor-x/-/object-get-own-property-descriptor-x-3.2.0.tgz#464585ad03e66108ed166c99325b8d2c5ba93712"
+  dependencies:
+    attempt-x "^1.1.0"
+    has-own-property-x "^3.1.1"
+    has-symbol-support-x "^1.4.1"
+    is-falsey-x "^1.0.0"
+    is-index-x "^1.0.0"
+    is-primitive "^2.0.0"
+    is-string "^1.0.4"
+    property-is-enumerable-x "^1.1.0"
+    to-object-x "^1.4.1"
+    to-property-key-x "^2.0.1"
+
+object-keys@^1.0.10, object-keys@^1.0.6, object-keys@^1.0.8:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
+
+object.assign@^4.0.4:
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.0.4.tgz#b1c9cc044ef1b9fe63606fc141abbb32e14730cc"
+  dependencies:
+    define-properties "^1.1.2"
+    function-bind "^1.1.0"
+    object-keys "^1.0.10"
+
+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"
+
+on-finished@^2.3.0, 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.1, once@^1.3.3, once@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+  dependencies:
+    wrappy "1"
+
+onetime@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
+
+onetime@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
+  dependencies:
+    mimic-fn "^1.0.0"
+
+optimist@^0.6.1:
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
+  dependencies:
+    minimist "~0.0.1"
+    wordwrap "~0.0.2"
+
+optionator@^0.8.1, optionator@^0.8.2:
+  version "0.8.2"
+  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
+  dependencies:
+    deep-is "~0.1.3"
+    fast-levenshtein "~2.0.4"
+    levn "~0.3.0"
+    prelude-ls "~1.1.2"
+    type-check "~0.3.2"
+    wordwrap "~1.0.0"
+
+original@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/original/-/original-1.0.0.tgz#9147f93fa1696d04be61e01bd50baeaca656bd3b"
+  dependencies:
+    url-parse "1.0.x"
+
+os-homedir@1.0.2, 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@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
+  dependencies:
+    execa "^0.7.0"
+    lcid "^1.0.0"
+    mem "^1.1.0"
+
+os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+
+osenv@0, 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-cancelable@^0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa"
+
+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"
+
+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-int-x@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/parse-int-x/-/parse-int-x-1.1.0.tgz#ae1e1a412cb2b257ce47639d0714bea80565f6d5"
+  dependencies:
+    cached-constructors-x "^1.0.0"
+    to-string-x "^1.4.2"
+    trim-x "^2.0.2"
+    white-space-x "^2.0.3"
+
+parse-int-x@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/parse-int-x/-/parse-int-x-2.0.0.tgz#9f979d4115930df2f4706a41810b9c712405552f"
+  dependencies:
+    cached-constructors-x "^1.0.0"
+    nan-x "^1.0.0"
+    to-string-x "^1.4.2"
+    trim-left-x "^3.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"
+
+parse5@^1.5.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94"
+
+parseurl@~1.3.2:
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
+
+passport-anonymous@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/passport-anonymous/-/passport-anonymous-1.0.1.tgz#241e37274ec44dfb7f6cad234b41c438386bc117"
+  dependencies:
+    passport-strategy "1.x.x"
+
+passport-http-bearer@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/passport-http-bearer/-/passport-http-bearer-1.0.1.tgz#147469ea3669e2a84c6167ef99dbb77e1f0098a8"
+  dependencies:
+    passport-strategy "1.x.x"
+
+passport-local@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/passport-local/-/passport-local-1.0.0.tgz#1fe63268c92e75606626437e3b906662c15ba6ee"
+  dependencies:
+    passport-strategy "1.x.x"
+
+passport-strategy@1.x.x:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
+
+passport@^0.3.2:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/passport/-/passport-0.3.2.tgz#9dd009f915e8fe095b0124a01b8f82da07510102"
+  dependencies:
+    passport-strategy "1.x.x"
+    pause "0.0.1"
+
+path-array@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-array/-/path-array-1.0.1.tgz#7e2f0f35f07a2015122b868b7eac0eb2c4fec271"
+  dependencies:
+    array-index "^1.0.0"
+
+path-exists@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
+  dependencies:
+    pinkie-promise "^2.0.0"
+
+path-exists@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
+
+path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+
+path-is-inside@^1.0.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@^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"
+
+pause@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d"
+
+performance-now@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
+
+performance-now@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
+
+pify@^2.0.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
+
+pify@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
+
+pinkie-promise@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
+  dependencies:
+    pinkie "^2.0.0"
+
+pinkie@^2.0.0:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
+
+pkg-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-config@^1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/pkg-config/-/pkg-config-1.1.1.tgz#557ef22d73da3c8837107766c52eadabde298fe4"
+  dependencies:
+    debug-log "^1.0.0"
+    find-root "^1.0.0"
+    xtend "^4.0.1"
+
+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-up@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-1.0.0.tgz#3e08fb461525c4421624a33b9f7e6d0af5b05a26"
+  dependencies:
+    find-up "^1.0.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:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff"
+
+platform@1.3.4:
+  version "1.3.4"
+  resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.4.tgz#6f0fb17edaaa48f21442b3a975c063130f1c3ebd"
+
+pluralize@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
+
+pluralize@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
+
+pouchdb-abstract-mapreduce@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-abstract-mapreduce/-/pouchdb-abstract-mapreduce-6.3.4.tgz#0d8a11ec300e3affcacbf6e9618d3eccd3f2e9ce"
+  dependencies:
+    pouchdb-binary-utils "6.3.4"
+    pouchdb-collate "6.3.4"
+    pouchdb-collections "6.3.4"
+    pouchdb-mapreduce-utils "6.3.4"
+    pouchdb-md5 "6.3.4"
+    pouchdb-promise "6.3.4"
+    pouchdb-utils "6.3.4"
+
+pouchdb-adapter-http@^6.2.0:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-adapter-http/-/pouchdb-adapter-http-6.3.4.tgz#9cad63e41e309dd9172aaf9dd0c832a17f5e4379"
+  dependencies:
+    argsarray "0.0.1"
+    pouchdb-ajax "6.3.4"
+    pouchdb-binary-utils "6.3.4"
+    pouchdb-errors "6.3.4"
+    pouchdb-promise "6.3.4"
+    pouchdb-utils "6.3.4"
+
+pouchdb-adapter-leveldb-core@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-adapter-leveldb-core/-/pouchdb-adapter-leveldb-core-6.3.4.tgz#5a2f1758ee5005a48b8d0049ffc698a468d8dbf3"
+  dependencies:
+    argsarray "0.0.1"
+    buffer-from "0.1.1"
+    double-ended-queue "2.1.0-0"
+    levelup "1.3.8"
+    pouchdb-adapter-utils "6.3.4"
+    pouchdb-binary-utils "6.3.4"
+    pouchdb-collections "6.3.4"
+    pouchdb-errors "6.3.4"
+    pouchdb-json "6.3.4"
+    pouchdb-md5 "6.3.4"
+    pouchdb-merge "6.3.4"
+    pouchdb-promise "6.3.4"
+    pouchdb-utils "6.3.4"
+    sublevel-pouchdb "6.3.4"
+    through2 "2.0.3"
+
+pouchdb-adapter-leveldb@^6.1.1:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-adapter-leveldb/-/pouchdb-adapter-leveldb-6.3.4.tgz#3e618d27842f98ba5065d7079f64089f0eeac2b9"
+  dependencies:
+    level-write-stream "1.0.0"
+    leveldown "1.5.0"
+    levelup "1.3.8"
+    pouchdb-adapter-leveldb-core "6.3.4"
+    pouchdb-merge "6.3.4"
+    pouchdb-utils "6.3.4"
+    through2 "2.0.3"
+
+pouchdb-adapter-memory@^6.1.1:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-adapter-memory/-/pouchdb-adapter-memory-6.3.4.tgz#eb12da10a378172d0995b3b18aa7bced2b3ba0e3"
+  dependencies:
+    memdown "1.2.4"
+    pouchdb-adapter-leveldb-core "6.3.4"
+    pouchdb-utils "6.3.4"
+
+pouchdb-adapter-utils@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-adapter-utils/-/pouchdb-adapter-utils-6.3.4.tgz#fc1f4a9a2356087bcded7cb630e5ef69ef692d25"
+  dependencies:
+    pouchdb-binary-utils "6.3.4"
+    pouchdb-collections "6.3.4"
+    pouchdb-errors "6.3.4"
+    pouchdb-md5 "6.3.4"
+    pouchdb-merge "6.3.4"
+    pouchdb-utils "6.3.4"
+
+pouchdb-ajax@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-ajax/-/pouchdb-ajax-6.3.4.tgz#a673f82a423a9e59ca7e4e4c5e3fc3fa9b1ffd2f"
+  dependencies:
+    buffer-from "0.1.1"
+    pouchdb-binary-utils "6.3.4"
+    pouchdb-errors "6.3.4"
+    pouchdb-promise "6.3.4"
+    pouchdb-utils "6.3.4"
+    request "2.80.0"
+
+pouchdb-binary-utils@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-binary-utils/-/pouchdb-binary-utils-6.3.4.tgz#9bfd1b4b3d8caccfd2b6046b1316b4f70afea9f6"
+  dependencies:
+    buffer-from "0.1.1"
+
+pouchdb-changes-filter@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-changes-filter/-/pouchdb-changes-filter-6.3.4.tgz#00947871a18a365a7a730a92ed29471ee5b44442"
+  dependencies:
+    pouchdb-errors "6.3.4"
+    pouchdb-selector-core "6.3.4"
+    pouchdb-utils "6.3.4"
+
+pouchdb-collate@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-collate/-/pouchdb-collate-6.3.4.tgz#4f03245b3309fdd5f937caffc9efa0e8ee591b4d"
+
+pouchdb-collate@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/pouchdb-collate/-/pouchdb-collate-1.2.0.tgz#cae3b830fca124b7f97d23046e4faa311ec3828c"
+
+pouchdb-collections@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-collections/-/pouchdb-collections-6.3.4.tgz#24f4dd3d7aabd630b2f5f26353fe09ddd4ef7afa"
+
+pouchdb-core@^6.1.1:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-core/-/pouchdb-core-6.3.4.tgz#843f1c63b4da3be7dffb2d0bdf1663a250ab092c"
+  dependencies:
+    argsarray "0.0.1"
+    inherits "2.0.3"
+    pouchdb-changes-filter "6.3.4"
+    pouchdb-collections "6.3.4"
+    pouchdb-debug "6.3.4"
+    pouchdb-errors "6.3.4"
+    pouchdb-merge "6.3.4"
+    pouchdb-promise "6.3.4"
+    pouchdb-utils "6.3.4"
+
+pouchdb-debug@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-debug/-/pouchdb-debug-6.3.4.tgz#cea3e707307ec95911441f31f086a07a95aea806"
+  dependencies:
+    debug "2.6.4"
+
+pouchdb-errors@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-errors/-/pouchdb-errors-6.3.4.tgz#abc1a84242e9276a1102e5cba1802aa08e5be45a"
+  dependencies:
+    inherits "2.0.3"
+
+pouchdb-extend@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/pouchdb-extend/-/pouchdb-extend-0.1.2.tgz#d1ce511bf704ed2e29f7bf428a416acfffa124b8"
+
+pouchdb-find@^0.10.3:
+  version "0.10.5"
+  resolved "https://registry.yarnpkg.com/pouchdb-find/-/pouchdb-find-0.10.5.tgz#34f285b8a56496a98f9cef2e1a1f6a0aac4eae8b"
+  dependencies:
+    argsarray "0.0.1"
+    debug "^2.1.0"
+    inherits "^2.0.1"
+    is-array "^1.0.1"
+    pouchdb-collate "^1.2.0"
+    pouchdb-extend "^0.1.2"
+    pouchdb-promise "5.4.0"
+    pouchdb-upsert "~2.0.1"
+    spark-md5 "2.0.2"
+
+pouchdb-find@^6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-find/-/pouchdb-find-6.3.4.tgz#27163e3d9a82bab2cc6db1deb73447762e8bd705"
+  dependencies:
+    pouchdb-abstract-mapreduce "6.3.4"
+    pouchdb-collate "6.3.4"
+    pouchdb-md5 "6.3.4"
+    pouchdb-promise "6.3.4"
+    pouchdb-selector-core "6.3.4"
+    pouchdb-utils "6.3.4"
+
+pouchdb-json@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-json/-/pouchdb-json-6.3.4.tgz#8fcc5f8d77776e9ffb029b10a3a50c60ceca6151"
+  dependencies:
+    vuvuzela "1.0.3"
+
+pouchdb-mapreduce-utils@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-mapreduce-utils/-/pouchdb-mapreduce-utils-6.3.4.tgz#0d85fb7cc1faf078afc638fdf07d9db3ce8fdaa6"
+  dependencies:
+    argsarray "0.0.1"
+    inherits "2.0.3"
+    pouchdb-collections "6.3.4"
+    pouchdb-utils "6.3.4"
+
+pouchdb-md5@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-md5/-/pouchdb-md5-6.3.4.tgz#364987fa184a33a48cfc7eb175a279fca113a4e8"
+  dependencies:
+    pouchdb-binary-utils "6.3.4"
+    spark-md5 "3.0.0"
+
+pouchdb-merge@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-merge/-/pouchdb-merge-6.3.4.tgz#6c843304affafe2f66225586ee5d44f1079a7678"
+
+pouchdb-promise@5.4.0:
+  version "5.4.0"
+  resolved "https://registry.yarnpkg.com/pouchdb-promise/-/pouchdb-promise-5.4.0.tgz#e277ac6bda1ac8504597abb5c43a7c3a9e56866f"
+  dependencies:
+    lie "3.0.4"
+
+pouchdb-promise@6.3.4, pouchdb-promise@^6.1.2, pouchdb-promise@^6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-promise/-/pouchdb-promise-6.3.4.tgz#817052fa2320293dd2dbc4be2f1cc81b02be30c2"
+  dependencies:
+    lie "3.1.1"
+
+pouchdb-promise@^5.4.3:
+  version "5.4.5"
+  resolved "https://registry.yarnpkg.com/pouchdb-promise/-/pouchdb-promise-5.4.5.tgz#5c2a69759141eb73be1e172e522fd84da2e0752e"
+  dependencies:
+    lie "3.0.4"
+
+pouchdb-selector-core@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-selector-core/-/pouchdb-selector-core-6.3.4.tgz#116958b1af9d7147143da5a5e6ee74de128a9fbc"
+  dependencies:
+    pouchdb-collate "6.3.4"
+    pouchdb-utils "6.3.4"
+
+pouchdb-upsert@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/pouchdb-upsert/-/pouchdb-upsert-2.2.0.tgz#42b15e420848f3b294c35060589fdb51cf7f7f5f"
+  dependencies:
+    pouchdb-promise "^6.1.2"
+
+pouchdb-upsert@~2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/pouchdb-upsert/-/pouchdb-upsert-2.0.2.tgz#c746cc9945e52d8c78e42f63ade0666777996712"
+  dependencies:
+    pouchdb-promise "^5.4.3"
+
+pouchdb-utils@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/pouchdb-utils/-/pouchdb-utils-6.3.4.tgz#3926541ae439c019d9738d5e5c3aa9bb110e6a25"
+  dependencies:
+    argsarray "0.0.1"
+    clone-buffer "1.0.0"
+    immediate "3.0.6"
+    inherits "2.0.3"
+    pouchdb-collections "6.3.4"
+    pouchdb-errors "6.3.4"
+    pouchdb-promise "6.3.4"
+    uuid "^3.1.0"
+
+prebuild@^4.1.1:
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/prebuild/-/prebuild-4.5.0.tgz#2aaa0df2063bff814a803bd4dc94ff9b64e5df00"
+  dependencies:
+    async "^1.4.0"
+    execspawn "^1.0.1"
+    expand-template "^1.0.0"
+    ghreleases "^1.0.2"
+    github-from-package "0.0.0"
+    minimist "^1.1.2"
+    mkdirp "^0.5.1"
+    node-gyp "^3.0.3"
+    node-ninja "^1.0.1"
+    noop-logger "^0.1.0"
+    npmlog "^2.0.0"
+    os-homedir "^1.0.1"
+    pump "^1.0.0"
+    rc "^1.0.3"
+    simple-get "^1.4.2"
+    tar-fs "^1.7.0"
+    tar-stream "^1.2.1"
+    xtend "^4.0.1"
+
+prelude-ls@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
+
+preserve@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
+
+pretty-format@^21.2.1:
+  version "21.2.1"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-21.2.1.tgz#ae5407f3cf21066cd011aa1ba5fce7b6a2eddb36"
+  dependencies:
+    ansi-regex "^3.0.0"
+    ansi-styles "^3.2.0"
+
+private@^0.1.6, private@^0.1.7, private@~0.1.5:
+  version "0.1.8"
+  resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
+
+process-nextick-args@~1.0.6:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
+
+progress@^1.1.8:
+  version "1.1.8"
+  resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
+
+progress@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
+
+promise-queue@^2.2.3:
+  version "2.2.3"
+  resolved "https://registry.yarnpkg.com/promise-queue/-/promise-queue-2.2.3.tgz#8534d76bf4673c3baa3a82bba01bd295cc30f14f"
+
+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"
+
+property-is-enumerable-x@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/property-is-enumerable-x/-/property-is-enumerable-x-1.1.0.tgz#7ca48917476cd0914b37809bfd05776a0d942f6f"
+  dependencies:
+    to-object-x "^1.4.1"
+    to-property-key-x "^2.0.1"
+
+proxy-addr@~2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
+  dependencies:
+    forwarded "~0.1.2"
+    ipaddr.js "1.5.2"
+
+prr@~0.0.0:
+  version "0.0.0"
+  resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
+
+prr@~1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
+
+pseudomap@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+
+pubsweet-sse@^0.1.1:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/pubsweet-sse/-/pubsweet-sse-0.1.3.tgz#a185e81e270ef3b1132cae12959c1ac430a6ae03"
+
+pump@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.2.tgz#3b3ee6512f94f0e575538c17995f9f16990a5d51"
+  dependencies:
+    end-of-stream "^1.1.0"
+    once "^1.3.1"
+
+punycode@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+
+q@^1.1.2:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
+
+qs@6.5.1, qs@^6.1.0, qs@~6.5.1:
+  version "6.5.1"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
+
+qs@~6.3.0:
+  version "6.3.2"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
+
+qs@~6.4.0:
+  version "6.4.0"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
+
+querystringify@0.0.x:
+  version "0.0.4"
+  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c"
+
+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.3.2:
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
+  dependencies:
+    bytes "3.0.0"
+    http-errors "1.6.2"
+    iconv-lite "0.4.19"
+    unpipe "1.0.0"
+
+rc@^1.0.3, rc@^1.1.2, rc@^1.1.7:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077"
+  dependencies:
+    deep-extend "~0.4.0"
+    ini "~1.3.0"
+    minimist "^1.2.0"
+    strip-json-comments "~2.0.1"
+
+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.0.33:
+  version "1.0.33"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.33.tgz#3a360dd66c1b1d7fd4705389860eda1d0f61126c"
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.1"
+    isarray "0.0.1"
+    string_decoder "~0.10.x"
+
+readable-stream@1.1.x, readable-stream@^1.0.33, readable-stream@~1.1.9:
+  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@>=1.0.33-1 <1.1.0-0":
+  version "1.0.34"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.1"
+    isarray "0.0.1"
+    string_decoder "~0.10.x"
+
+readable-stream@^2.0.0, 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.3"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.3"
+    isarray "~1.0.0"
+    process-nextick-args "~1.0.6"
+    safe-buffer "~5.1.1"
+    string_decoder "~1.0.3"
+    util-deprecate "~1.0.1"
+
+readable-stream@~0.0.2:
+  version "0.0.4"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-0.0.4.tgz#f32d76e3fb863344a548d79923007173665b3b8d"
+
+readable-stream@~2.0.5:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.1"
+    isarray "~1.0.0"
+    process-nextick-args "~1.0.6"
+    string_decoder "~0.10.x"
+    util-deprecate "~1.0.1"
+
+readline2@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35"
+  dependencies:
+    code-point-at "^1.0.0"
+    is-fullwidth-code-point "^1.0.0"
+    mute-stream "0.0.5"
+
+recast@^0.10.1:
+  version "0.10.43"
+  resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.43.tgz#b95d50f6d60761a5f6252e15d80678168491ce7f"
+  dependencies:
+    ast-types "0.8.15"
+    esprima-fb "~15001.1001.0-dev-harmony-fb"
+    private "~0.1.5"
+    source-map "~0.5.0"
+
+recast@^0.11.17:
+  version "0.11.23"
+  resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3"
+  dependencies:
+    ast-types "0.9.6"
+    esprima "~3.1.0"
+    private "~0.1.5"
+    source-map "~0.5.0"
+
+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"
+
+referrer-policy@1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.1.0.tgz#35774eb735bf50fb6c078e83334b472350207d79"
+
+regenerator-runtime@^0.11.0:
+  version "0.11.0"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1"
+
+regex-cache@^0.4.2:
+  version "0.4.4"
+  resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
+  dependencies:
+    is-equal-shallow "^0.1.3"
+
+relational-pouch@^1.4.5:
+  version "1.4.6"
+  resolved "https://registry.yarnpkg.com/relational-pouch/-/relational-pouch-1.4.6.tgz#d2772ee1a36992bd8b28d4aaff2ef1a4a0410857"
+  dependencies:
+    argsarray "0.0.1"
+    es3ify "^0.2.2"
+    inherits "~2.0.1"
+    lie "^3.1.0"
+    pouchdb-extend "^0.1.2"
+    pouchdb-find "^6.3.4"
+    pouchdb-promise "^6.3.4"
+    uniq "^1.0.1"
+
+remove-trailing-separator@^1.0.1:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
+
+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"
+
+replace-comments-x@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/replace-comments-x/-/replace-comments-x-2.0.0.tgz#a5cec18efd912aad78a7c3c4b69d01768556d140"
+  dependencies:
+    require-coercible-to-string-x "^1.0.0"
+    to-string-x "^1.4.2"
+
+request@2, request@^2.79.0, request@^2.81.0:
+  version "2.83.0"
+  resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
+  dependencies:
+    aws-sign2 "~0.7.0"
+    aws4 "^1.6.0"
+    caseless "~0.12.0"
+    combined-stream "~1.0.5"
+    extend "~3.0.1"
+    forever-agent "~0.6.1"
+    form-data "~2.3.1"
+    har-validator "~5.0.3"
+    hawk "~6.0.2"
+    http-signature "~1.2.0"
+    is-typedarray "~1.0.0"
+    isstream "~0.1.2"
+    json-stringify-safe "~5.0.1"
+    mime-types "~2.1.17"
+    oauth-sign "~0.8.2"
+    performance-now "^2.1.0"
+    qs "~6.5.1"
+    safe-buffer "^5.1.1"
+    stringstream "~0.0.5"
+    tough-cookie "~2.3.3"
+    tunnel-agent "^0.6.0"
+    uuid "^3.1.0"
+
+request@2.80.0:
+  version "2.80.0"
+  resolved "https://registry.yarnpkg.com/request/-/request-2.80.0.tgz#8cc162d76d79381cdefdd3505d76b80b60589bd0"
+  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.0"
+    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.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-coercible-to-string-x@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/require-coercible-to-string-x/-/require-coercible-to-string-x-1.0.0.tgz#367b3e9ca67e00324c411b0b498453a74cd5569e"
+  dependencies:
+    require-object-coercible-x "^1.4.1"
+    to-string-x "^1.4.2"
+
+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-object-coercible-x@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/require-object-coercible-x/-/require-object-coercible-x-1.4.1.tgz#75b9fb5bda2d15cf705a5714f108e8b40ca3eb2e"
+  dependencies:
+    is-nil-x "^1.4.1"
+
+require-uncached@^1.0.2, 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"
+
+requires-port@1.0.x:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+
+resolve-from@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
+
+resolve@1.1.7:
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
+
+resolve@^1.1.6, resolve@^1.1.7, resolve@^1.2.0, resolve@^1.3.3:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86"
+  dependencies:
+    path-parse "^1.0.5"
+
+restore-cursor@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
+  dependencies:
+    exit-hook "^1.0.0"
+    onetime "^1.0.0"
+
+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.5.1, rimraf@^2.6.1:
+  version "2.6.2"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
+  dependencies:
+    glob "^7.0.5"
+
+run-async@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389"
+  dependencies:
+    once "^1.3.0"
+
+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"
+
+run-parallel@^1.1.2:
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.6.tgz#29003c9a2163e01e2d2dfc90575f2c6c1d61a039"
+
+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"
+
+rx-lite@^3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
+
+safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
+
+safe-to-string-x@^2.0.1:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/safe-to-string-x/-/safe-to-string-x-2.0.3.tgz#c4c5242c854a6727e3f09df7a62b5b2522545b86"
+  dependencies:
+    to-string-symbols-supported-x "^1.0.0"
+
+sane@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/sane/-/sane-2.2.0.tgz#d6d2e2fcab00e3d283c93b912b7c3a20846f1d56"
+  dependencies:
+    anymatch "^1.3.0"
+    exec-sh "^0.2.0"
+    fb-watchman "^2.0.0"
+    minimatch "^3.0.2"
+    minimist "^1.1.1"
+    walker "~1.0.5"
+    watch "~0.18.0"
+  optionalDependencies:
+    fsevents "^1.1.1"
+
+sax@^1.2.1:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
+
+"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.3, semver@^5.3.0:
+  version "5.4.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
+
+semver@5.3.0, semver@~5.3.0:
+  version "5.3.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
+
+semver@~5.0.1:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
+
+semver@~5.1.0:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.1.1.tgz#a3292a373e6f3e0798da0b20641b9a9c5bc47e19"
+
+send@0.16.1:
+  version "0.16.1"
+  resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3"
+  dependencies:
+    debug "2.6.9"
+    depd "~1.1.1"
+    destroy "~1.0.4"
+    encodeurl "~1.0.1"
+    escape-html "~1.0.3"
+    etag "~1.8.1"
+    fresh "0.5.2"
+    http-errors "~1.6.2"
+    mime "1.4.1"
+    ms "2.0.0"
+    on-finished "~2.3.0"
+    range-parser "~1.2.0"
+    statuses "~1.3.1"
+
+serve-static@1.13.1:
+  version "1.13.1"
+  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719"
+  dependencies:
+    encodeurl "~1.0.1"
+    escape-html "~1.0.3"
+    parseurl "~1.3.2"
+    send "0.16.1"
+
+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"
+
+setprototypeof@1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
+
+setprototypeof@1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
+
+shebang-command@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+  dependencies:
+    shebang-regex "^1.0.0"
+
+shebang-regex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+
+shelljs@^0.7.5:
+  version "0.7.8"
+  resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3"
+  dependencies:
+    glob "^7.0.0"
+    interpret "^1.0.0"
+    rechoir "^0.6.2"
+
+shellwords@^0.1.0:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
+
+signal-exit@^3.0.0, signal-exit@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
+
+simple-get@^1.4.2:
+  version "1.4.3"
+  resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-1.4.3.tgz#e9755eda407e96da40c5e5158c9ea37b33becbeb"
+  dependencies:
+    once "^1.3.1"
+    unzip-response "^1.0.0"
+    xtend "^4.0.0"
+
+simple-mime@~0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/simple-mime/-/simple-mime-0.1.0.tgz#95f517c4f466d7cff561a71fc9dab2596ea9ef2e"
+
+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"
+
+slice-ansi@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d"
+  dependencies:
+    is-fullwidth-code-point "^2.0.0"
+
+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"
+
+sntp@2.x.x:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.0.2.tgz#5064110f0af85f7cfdb7d6b67a40028ce52b4b2b"
+  dependencies:
+    hoek "4.x.x"
+
+source-map-support@^0.4.15:
+  version "0.4.18"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
+  dependencies:
+    source-map "^0.5.6"
+
+source-map@^0.4.2, 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.3, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.6:
+  version "0.5.7"
+  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+
+spark-md5@2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-2.0.2.tgz#37b763847763ae7e7acef2ca5233d01e649a78b7"
+
+spark-md5@3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.0.tgz#3722227c54e2faf24b1dc6d933cc144e6f71bfef"
+
+spdx-correct@~1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
+  dependencies:
+    spdx-license-ids "^1.0.2"
+
+spdx-expression-parse@~1.0.0:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c"
+
+spdx-license-ids@^1.0.2:
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
+
+sprintf-js@~1.0.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+
+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"
+
+standard-engine@~7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/standard-engine/-/standard-engine-7.0.0.tgz#ebb77b9c8fc2c8165ffa353bd91ba0dff41af690"
+  dependencies:
+    deglob "^2.1.0"
+    get-stdin "^5.0.1"
+    minimist "^1.1.0"
+    pkg-conf "^2.0.0"
+
+standard@^10.0.2:
+  version "10.0.3"
+  resolved "https://registry.yarnpkg.com/standard/-/standard-10.0.3.tgz#7869bcbf422bdeeaab689a1ffb1fea9677dd50ea"
+  dependencies:
+    eslint "~3.19.0"
+    eslint-config-standard "10.2.1"
+    eslint-config-standard-jsx "4.0.2"
+    eslint-plugin-import "~2.2.0"
+    eslint-plugin-node "~4.2.2"
+    eslint-plugin-promise "~3.5.0"
+    eslint-plugin-react "~6.10.0"
+    eslint-plugin-standard "~3.0.1"
+    standard-engine "~7.0.0"
+
+"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"
+
+streamsearch@0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
+
+string-length@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
+  dependencies:
+    astral-regex "^1.0.0"
+    strip-ansi "^4.0.0"
+
+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, string-width@^2.1.0, string-width@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
+  dependencies:
+    is-fullwidth-code-point "^2.0.0"
+    strip-ansi "^4.0.0"
+
+string_decoder@~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.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
+  dependencies:
+    safe-buffer "~5.1.0"
+
+stringstream@~0.0.4, stringstream@~0.0.5:
+  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@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+  dependencies:
+    ansi-regex "^3.0.0"
+
+strip-bom@3.0.0, strip-bom@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+
+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-eof@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
+
+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"
+
+subcommand@^2.0.3:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/subcommand/-/subcommand-2.1.0.tgz#5e4ceca5a3779e3365b1511e05f866877302f760"
+  dependencies:
+    cliclopts "^1.1.0"
+    debug "^2.1.3"
+    minimist "^1.2.0"
+    xtend "^4.0.0"
+
+sublevel-pouchdb@6.3.4:
+  version "6.3.4"
+  resolved "https://registry.yarnpkg.com/sublevel-pouchdb/-/sublevel-pouchdb-6.3.4.tgz#5f9591aeee978790465bf5b88efc173aa08cf183"
+  dependencies:
+    inherits "2.0.3"
+    level-codec "7.0.0"
+    ltgt "2.2.0"
+    readable-stream "1.0.33"
+
+superagent@^2.0.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/superagent/-/superagent-2.3.0.tgz#703529a0714e57e123959ddefbce193b2e50d115"
+  dependencies:
+    component-emitter "^1.2.0"
+    cookiejar "^2.0.6"
+    debug "^2.2.0"
+    extend "^3.0.0"
+    form-data "1.0.0-rc4"
+    formidable "^1.0.17"
+    methods "^1.1.1"
+    mime "^1.3.4"
+    qs "^6.1.0"
+    readable-stream "^2.0.5"
+
+supertest@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/supertest/-/supertest-2.0.1.tgz#a058081d788f1515d4700d7502881e6b759e44cd"
+  dependencies:
+    methods "1.x"
+    superagent "^2.0.0"
+
+supports-color@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
+
+supports-color@^3.1.2:
+  version "3.2.3"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
+  dependencies:
+    has-flag "^1.0.0"
+
+supports-color@^4.0.0:
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b"
+  dependencies:
+    has-flag "^2.0.0"
+
+symbol-tree@^3.2.1:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
+
+table@^3.7.8:
+  version "3.8.3"
+  resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
+  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"
+
+table@^4.0.1:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36"
+  dependencies:
+    ajv "^5.2.3"
+    ajv-keywords "^2.1.0"
+    chalk "^2.1.0"
+    lodash "^4.17.4"
+    slice-ansi "1.0.0"
+    string-width "^2.1.1"
+
+tar-fs@^1.7.0:
+  version "1.16.0"
+  resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.0.tgz#e877a25acbcc51d8c790da1c57c9cf439817b896"
+  dependencies:
+    chownr "^1.0.1"
+    mkdirp "^0.5.1"
+    pump "^1.0.0"
+    tar-stream "^1.1.2"
+
+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-stream@^1.1.2, tar-stream@^1.2.1:
+  version "1.5.4"
+  resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.4.tgz#36549cf04ed1aee9b2a30c0143252238daf94016"
+  dependencies:
+    bl "^1.0.0"
+    end-of-stream "^1.0.0"
+    readable-stream "^2.0.0"
+    xtend "^4.0.0"
+
+tar@^2.0.0, 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"
+
+test-exclude@^4.1.1:
+  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:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+
+throat@^4.0.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a"
+
+through2@2.0.3:
+  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"
+
+through2@^0.6.2, through2@^0.6.5, through2@~0.6.3:
+  version "0.6.5"
+  resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48"
+  dependencies:
+    readable-stream ">=1.0.33-1 <1.1.0-0"
+    xtend ">=4.0.0 <4.1.0-0"
+
+through@^2.3.6, through@~2.3.4:
+  version "2.3.8"
+  resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+
+tmp@^0.0.33:
+  version "0.0.33"
+  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
+  dependencies:
+    os-tmpdir "~1.0.2"
+
+tmpl@1.0.x:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
+
+to-boolean-x@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/to-boolean-x/-/to-boolean-x-1.0.1.tgz#724128dacc5bea75a93ad471be7ee9277561b2c1"
+
+to-fast-properties@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
+
+to-integer-x@^2.0.1:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/to-integer-x/-/to-integer-x-2.1.0.tgz#59ea231e23e0e934e2a8944d4555e8616e87d8c5"
+  dependencies:
+    is-finite-x "^3.0.1"
+    is-nan-x "^1.0.1"
+    math-sign-x "^2.1.0"
+    to-number-x "^1.1.0"
+
+to-number-x@^1.0.1, to-number-x@^1.1.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/to-number-x/-/to-number-x-1.2.0.tgz#c96cae954d0e92f01455b444dd7989e0211ec679"
+  dependencies:
+    cached-constructors-x "^1.0.0"
+    nan-x "^1.0.0"
+    parse-int-x "^1.1.0"
+    to-primitive-x "^1.1.0"
+    trim-x "^2.0.2"
+
+to-number-x@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/to-number-x/-/to-number-x-2.0.0.tgz#c9099d7ded8fd327132a2987df2dcc8baf36df4d"
+  dependencies:
+    cached-constructors-x "^1.0.0"
+    nan-x "^1.0.0"
+    parse-int-x "^2.0.0"
+    to-primitive-x "^1.1.0"
+    trim-x "^3.0.0"
+
+to-object-x@^1.4.1, to-object-x@^1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/to-object-x/-/to-object-x-1.5.0.tgz#bd69dd4e104d77acc0cc0d84f5ac48f630aebe3c"
+  dependencies:
+    cached-constructors-x "^1.0.0"
+    require-object-coercible-x "^1.4.1"
+
+to-primitive-x@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/to-primitive-x/-/to-primitive-x-1.1.0.tgz#41ce2c13e3e246e0e5d0a8829a0567c6015833f8"
+  dependencies:
+    has-symbol-support-x "^1.4.1"
+    is-date-object "^1.0.1"
+    is-function-x "^3.2.0"
+    is-nil-x "^1.4.1"
+    is-primitive "^2.0.0"
+    is-symbol "^1.0.1"
+    require-object-coercible-x "^1.4.1"
+    validate.io-undefined "^1.0.3"
+
+to-property-key-x@^2.0.1, to-property-key-x@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/to-property-key-x/-/to-property-key-x-2.0.2.tgz#b19aa8e22faa0ff7d1c102cfbc657af73413cfa1"
+  dependencies:
+    has-symbol-support-x "^1.4.1"
+    to-primitive-x "^1.1.0"
+    to-string-x "^1.4.2"
+
+to-string-symbols-supported-x@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/to-string-symbols-supported-x/-/to-string-symbols-supported-x-1.0.0.tgz#d435eb72312fe885b18047a96d59c75641476872"
+  dependencies:
+    cached-constructors-x "^1.0.0"
+    has-symbol-support-x "^1.4.1"
+    is-symbol "^1.0.1"
+
+to-string-tag-x@^1.4.1, to-string-tag-x@^1.4.2:
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/to-string-tag-x/-/to-string-tag-x-1.4.2.tgz#916a0c72d2f93dc27fccfe0ea0ce26cd78be21de"
+  dependencies:
+    lodash.isnull "^3.0.0"
+    validate.io-undefined "^1.0.3"
+
+to-string-x@^1.4.2:
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/to-string-x/-/to-string-x-1.4.2.tgz#7d9a2528e159a9214e668137c1e10a045abe6279"
+  dependencies:
+    is-symbol "^1.0.1"
+
+topo@1.x.x:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/topo/-/topo-1.1.0.tgz#e9d751615d1bb87dc865db182fa1ca0a5ef536d5"
+  dependencies:
+    hoek "2.x.x"
+
+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.2, tough-cookie@~2.3.0, tough-cookie@~2.3.3:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
+  dependencies:
+    punycode "^1.4.1"
+
+tr46@~0.0.3:
+  version "0.0.3"
+  resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+
+trim-left-x@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/trim-left-x/-/trim-left-x-2.0.1.tgz#92b071ff6ae9aa8026c21aefca2a9f0a7f8d48c6"
+  dependencies:
+    cached-constructors-x "^1.0.0"
+    require-coercible-to-string-x "^1.0.0"
+    white-space-x "^2.0.3"
+
+trim-left-x@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/trim-left-x/-/trim-left-x-3.0.0.tgz#356cf055896726b9754425e841398842e90b4cdf"
+  dependencies:
+    cached-constructors-x "^1.0.0"
+    require-coercible-to-string-x "^1.0.0"
+    white-space-x "^3.0.0"
+
+trim-right-x@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/trim-right-x/-/trim-right-x-2.0.1.tgz#17539a4725b5e6aedad43d72ebb5110925e7b5e0"
+  dependencies:
+    cached-constructors-x "^1.0.0"
+    require-coercible-to-string-x "^1.0.0"
+    white-space-x "^2.0.3"
+
+trim-right-x@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/trim-right-x/-/trim-right-x-3.0.0.tgz#28c4cd37d5981f50ace9b52e3ce9106f4d2d22c0"
+  dependencies:
+    cached-constructors-x "^1.0.0"
+    require-coercible-to-string-x "^1.0.0"
+    white-space-x "^3.0.0"
+
+trim-right@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
+
+trim-x@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/trim-x/-/trim-x-2.0.2.tgz#d48cb65904c3aa94fc2ddf64e307693e4186eaf3"
+  dependencies:
+    trim-left-x "^2.0.1"
+    trim-right-x "^2.0.1"
+
+trim-x@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/trim-x/-/trim-x-3.0.0.tgz#24efdcd027b748bbfc246a0139ad1749befef024"
+  dependencies:
+    trim-left-x "^3.0.0"
+    trim-right-x "^3.0.0"
+
+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"
+
+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.4, 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"
+
+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"
+
+uniq@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
+
+unpipe@1.0.0, unpipe@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+
+unreachable-branch-transform@^0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz#d99cc4c6e746d264928845b611db54b0f3474caa"
+  dependencies:
+    esmangle-evaluator "^1.0.0"
+    recast "^0.10.1"
+    through2 "^0.6.2"
+
+unzip-response@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe"
+
+url-parse@1.0.x:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"
+  dependencies:
+    querystringify "0.0.x"
+    requires-port "1.0.x"
+
+url-template@~2.0.6:
+  version "2.0.8"
+  resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21"
+
+user-home@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f"
+  dependencies:
+    os-homedir "^1.0.0"
+
+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"
+
+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.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+
+uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
+
+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"
+
+validate.io-undefined@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/validate.io-undefined/-/validate.io-undefined-1.0.3.tgz#7e27fcbb315b841e78243431897671929e20b7f4"
+
+vary@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+
+verror@1.10.0:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
+  dependencies:
+    assert-plus "^1.0.0"
+    core-util-is "1.0.2"
+    extsprintf "^1.2.0"
+
+vuvuzela@1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/vuvuzela/-/vuvuzela-1.0.3.tgz#3be145e58271c73ca55279dd851f12a682114b0b"
+
+walker@~1.0.5:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
+  dependencies:
+    makeerror "1.0.x"
+
+watch@~0.18.0:
+  version "0.18.0"
+  resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986"
+  dependencies:
+    exec-sh "^0.2.0"
+    minimist "^1.2.0"
+
+webidl-conversions@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+
+webidl-conversions@^4.0.0:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
+
+whatwg-encoding@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.1.tgz#3c6c451a198ee7aec55b1ec61d0920c67801a5f4"
+  dependencies:
+    iconv-lite "0.4.13"
+
+whatwg-url@^4.3.0:
+  version "4.8.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0"
+  dependencies:
+    tr46 "~0.0.3"
+    webidl-conversions "^3.0.0"
+
+which-module@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
+
+which@1, which@^1.2.12, which@^1.2.9:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
+  dependencies:
+    isexe "^2.0.0"
+
+white-space-x@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/white-space-x/-/white-space-x-2.0.3.tgz#d8af2a54e2e16f78abe5ff1811d5e9d4190c60d6"
+
+white-space-x@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/white-space-x/-/white-space-x-3.0.0.tgz#c8e31ed4fecf4f3feebe6532e6046008a666a3e1"
+
+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"
+
+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"
+
+winston@^2.2.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.0.tgz#808050b93d52661ed9fb6c26b3f0c826708b0aee"
+  dependencies:
+    async "~1.0.0"
+    colors "1.0.x"
+    cycle "1.0.x"
+    eyes "0.1.x"
+    isstream "0.1.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"
+
+worker-farm@^1.3.1:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.0.tgz#adfdf0cd40581465ed0a1f648f9735722afd5c8d"
+  dependencies:
+    errno "^0.1.4"
+    xtend "^4.0.1"
+
+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"
+
+wreck@^6.3.0:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/wreck/-/wreck-6.3.0.tgz#a1369769f07bbb62d6a378336a7871fc773c740b"
+  dependencies:
+    boom "2.x.x"
+    hoek "2.x.x"
+
+write-file-atomic@^2.1.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab"
+  dependencies:
+    graceful-fs "^4.1.11"
+    imurmurhash "^0.1.4"
+    signal-exit "^3.0.2"
+
+write-stream@~0.4.3:
+  version "0.4.3"
+  resolved "https://registry.yarnpkg.com/write-stream/-/write-stream-0.4.3.tgz#83cc8c0347d0af6057a93862b4e3ae01de5c81c1"
+  dependencies:
+    readable-stream "~0.0.2"
+
+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"
+
+x-xss-protection@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/x-xss-protection/-/x-xss-protection-1.0.0.tgz#898afb93869b24661cf9c52f9ee8db8ed0764dd9"
+
+xml-name-validator@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
+
+"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, 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.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
+
+yargs-parser@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9"
+  dependencies:
+    camelcase "^4.1.0"
+
+yargs@^9.0.0:
+  version "9.0.1"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c"
+  dependencies:
+    camelcase "^4.1.0"
+    cliui "^3.2.0"
+    decamelize "^1.1.1"
+    get-caller-file "^1.0.1"
+    os-locale "^2.0.0"
+    read-pkg-up "^2.0.0"
+    require-directory "^2.1.1"
+    require-main-filename "^1.0.1"
+    set-blocking "^2.0.0"
+    string-width "^2.0.0"
+    which-module "^2.0.0"
+    y18n "^3.2.1"
+    yargs-parser "^7.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"