Răsfoiți Sursa

Merge branch 'experimental' into snyk-fix-c5bac133267f0a74303e606a89374a13

Jonathan Graham 5 ani în urmă
părinte
comite
06ab2dd1c6
96 a modificat fișierele cu 39397 adăugiri și 19464 ștergeri
  1. 3 0
      .gitattributes
  2. 0 2
      .gitignore
  3. 1 1
      .travis.yml
  4. 92 94
      README.md
  5. 48 0
      backend/classes/Timer.class.js
  6. 102 70
      backend/core.js
  7. 298 159
      backend/index.js
  8. 99 0
      backend/logic/actions/activities.js
  9. 188 134
      backend/logic/actions/apis.js
  10. 54 36
      backend/logic/actions/hooks/adminRequired.js
  11. 46 29
      backend/logic/actions/hooks/loginRequired.js
  12. 66 40
      backend/logic/actions/hooks/ownerRequired.js
  13. 1 0
      backend/logic/actions/index.js
  14. 226 139
      backend/logic/actions/news.js
  15. 1149 523
      backend/logic/actions/playlists.js
  16. 154 108
      backend/logic/actions/punishments.js
  17. 374 229
      backend/logic/actions/queueSongs.js
  18. 342 233
      backend/logic/actions/reports.js
  19. 1022 469
      backend/logic/actions/songs.js
  20. 2349 1185
      backend/logic/actions/stations.js
  21. 2072 1139
      backend/logic/actions/users.js
  22. 71 0
      backend/logic/activities.js
  23. 44 39
      backend/logic/api.js
  24. 511 246
      backend/logic/app.js
  25. 261 206
      backend/logic/cache/index.js
  26. 324 190
      backend/logic/db/index.js
  27. 16 0
      backend/logic/db/schemas/activity.js
  28. 1 1
      backend/logic/db/schemas/news.js
  29. 1 1
      backend/logic/db/schemas/playlist.js
  30. 1 1
      backend/logic/db/schemas/punishment.js
  31. 1 1
      backend/logic/db/schemas/queueSong.js
  32. 1 1
      backend/logic/db/schemas/report.js
  33. 1 1
      backend/logic/db/schemas/song.js
  34. 8 1
      backend/logic/db/schemas/user.js
  35. 99 77
      backend/logic/discord.js
  36. 376 188
      backend/logic/io.js
  37. 0 177
      backend/logic/logger.js
  38. 45 35
      backend/logic/mail/index.js
  39. 17 12
      backend/logic/mail/schemas/passwordRequest.js
  40. 17 12
      backend/logic/mail/schemas/resetPasswordRequest.js
  41. 22 13
      backend/logic/mail/schemas/verifyEmail.js
  42. 238 154
      backend/logic/notifications.js
  43. 280 166
      backend/logic/playlists.js
  44. 313 241
      backend/logic/punishments.js
  45. 257 175
      backend/logic/songs.js
  46. 103 82
      backend/logic/spotify.js
  47. 1149 533
      backend/logic/stations.js
  48. 318 167
      backend/logic/tasks.js
  49. 756 559
      backend/logic/utils.js
  50. 2291 0
      backend/package-lock.json
  51. 5 5
      backend/package.json
  52. 1 2
      frontend/.prettierignore
  53. 90 21
      frontend/App.vue
  54. 5 7
      frontend/Dockerfile
  55. 7 0
      frontend/api/admin/index.js
  56. 17 0
      frontend/api/admin/reports.js
  57. 8 5
      frontend/api/auth.js
  58. 2 2
      frontend/bootstrap.sh
  59. 12 9
      frontend/components/Admin/Reports.vue
  60. 13 7
      frontend/components/Admin/Stations.vue
  61. 9 1
      frontend/components/Admin/Users.vue
  62. 2 5
      frontend/components/MainFooter.vue
  63. 1 0
      frontend/components/MainHeader.vue
  64. 1 0
      frontend/components/Modals/AddSongToPlaylist.vue
  65. 9 13
      frontend/components/Modals/AddSongToQueue.vue
  66. 12 1
      frontend/components/Modals/CreateCommunityStation.vue
  67. 2 4
      frontend/components/Modals/EditSong.vue
  68. 31 24
      frontend/components/Modals/EditStation.vue
  69. 16 5
      frontend/components/Modals/IssuesModal.vue
  70. 75 53
      frontend/components/Modals/Playlists/Edit.vue
  71. 116 10
      frontend/components/Modals/Register.vue
  72. 9 10
      frontend/components/Modals/Report.vue
  73. 7 9
      frontend/components/Sidebars/SongsList.vue
  74. 177 94
      frontend/components/Station/Station.vue
  75. 17 4
      frontend/components/Station/StationHeader.vue
  76. 473 148
      frontend/components/User/Settings.vue
  77. 700 101
      frontend/components/User/Show.vue
  78. 6 3
      frontend/components/pages/Home.vue
  79. 4 0
      frontend/components/pages/Team.vue
  80. 8 0
      frontend/dist/index.css
  81. 2 2
      frontend/dist/index.tpl.html
  82. 10 0
      frontend/io.js
  83. 71 2
      frontend/js/utils.js
  84. 32 0
      frontend/keyboardShortcuts.js
  85. 13261 0
      frontend/package-lock.json
  86. 2 0
      frontend/store/index.js
  87. 35 2
      frontend/store/modules/admin.js
  88. 65 0
      frontend/store/modules/sidebars.js
  89. 7 0
      frontend/store/modules/station.js
  90. 18 5
      frontend/store/modules/user.js
  91. 1 0
      frontend/validation.js
  92. 2 2
      lerna.json
  93. 7844 0
      package-lock.json
  94. 3 3
      package.json
  95. 1 1
      windows-start.cmd
  96. 0 11035
      yarn.lock

+ 3 - 0
.gitattributes

@@ -0,0 +1,3 @@
+* text=auto
+
+*.sh text eol=lf

+ 0 - 2
.gitignore

@@ -14,7 +14,6 @@ startMongo.cmd
 *.rdb
 
 npm-debug.log
-yarn-error.log
 lerna-debug.log
 
 # Backend
@@ -22,7 +21,6 @@ backend/node_modules/
 backend/config/default.json
 
 # Frontend
-frontend/yarn-error.log
 frontend/bundle-stats.json
 frontend/bundle-report.html
 frontend/node_modules/

+ 1 - 1
.travis.yml

@@ -31,7 +31,7 @@ jobs:
       script:
         - docker-compose build frontend # build frontend
         - docker-compose up -d frontend # start frontend
-        - docker-compose exec frontend /bin/bash -c "cd app && yarn lint" # using eslint to check for formatting/linting issues
+        - docker-compose exec frontend /bin/bash -c "cd app && npm run lint" # using eslint to check for formatting/linting issues
     - stage: backend
       script:
         - docker-compose up -d mongo # start mongo (users automatically setup)

+ 92 - 94
README.md

@@ -1,4 +1,3 @@
-
 # MusareNode
 
 Based off of the original [Musare](https://github.com/Musare/MusareMeteor), which utilized Meteor.
@@ -16,11 +15,11 @@ You can also find us on [Facebook](https://www.facebook.com/MusareMusic) and [Tw
 
 ### Our Stack
 
-- NodeJS
-- MongoDB
-- Redis
-- Nginx (not required)
-- VueJS
+-   NodeJS
+-   MongoDB
+-   Redis
+-   Nginx (not required)
+-   VueJS
 
 ### Frontend
 
@@ -36,16 +35,15 @@ We currently only utilize 1 backend, 1 MongoDB server and 1 Redis server running
 
 Installing with Docker: (not recommended for Windows users)
 
-- [Docker](https://www.docker.com/)
+-   [Docker](https://www.docker.com/)
 
 Standard Installation:
 
-- [NodeJS](https://nodejs.org/en/download/)
-  _ nodemon: `yarn global add nodemon`
-  _ [node-gyp](https://github.com/nodejs/node-gyp#installation): `yarn global add node-gyp`
-- [Yarn (Windows)](https://yarnpkg.com/lang/en/docs/install/#windows-stable) [Yarn (Unix)](https://yarnpkg.com/lang/en/docs/install/#debian-stable) ([npm](https://www.npmjs.com/) can also be used)
-- [MongoDB](https://www.mongodb.com/download-center) Currently version 4.0
-- [Redis (Windows)](https://github.com/MSOpenTech/redis/releases/tag/win-3.2.100) [Redis (Unix)](https://redis.io/download)
+-   [NodeJS](https://nodejs.org/en/download/)
+    _ nodemon: `npm install -g nodemon`
+    _ [node-gyp](https://github.com/nodejs/node-gyp#installation): `npm install -g node-gyp`
+-   [MongoDB](https://www.mongodb.com/download-center) Currently version 4.0
+-   [Redis (Windows)](https://github.com/MSOpenTech/redis/releases/tag/win-3.2.100) [Redis (Unix)](https://redis.io/download)
 
 ## Getting Started
 
@@ -57,51 +55,51 @@ Once you've installed the required tools:
 
 3. `cp backend/config/template.json backend/config/default.json`
 
-   |Property|Description|
-   |--|--|
-   |`mode`|Should be either `development` or `production`. No more explanation needed.|
-   |`secret`|Whatever you want - used by express's session module.|
-   |`domain`|Should be the url where the site will be accessible from,usually `http://localhost` for non-Docker.|
-   |`serverDomain`|Should be the url where the backend will be accessible from, usually `http://localhost:8080` for non-Docker.|
-   |`serverPort`|Should be the port where the backend will listen on, should always be `8080` for Docker, and is recommended for non-Docker.|
-   |`isDocker`|Self-explanatory. Are you using Docker?|
-   |`serverPort`|Should be the port where the backend will listen on, should always be `8080` for Docker, and is recommended for non-Docker.|
-   |`apis.youtube.key`|Can be obtained by setting up a [YouTube API Key](https://developers.google.com/youtube/v3/getting-started). You need to use the YouTube Data API v3, and create an API key.|
-   |`apis.recaptcha.secret`|Can be obtained by setting up a [ReCaptcha Site (v3)](https://www.google.com/recaptcha/admin).|
-   |`apis.github`|Can be obtained by setting up a [GitHub OAuth Application](https://github.com/settings/developers). You need to fill in some values to create the OAuth application. The homepage is the homepage of frontend. The authorization callback url is the backend url with `/auth/github/authorize/callback` added at the end. For example `http://localhost:8080/auth/github/authorize/callback`.|
-   |`apis.discord.token`|Token for the Discord bot.|
-   |`apis.discord.loggingServer`|Server ID of the Discord logging server.|
-   |`apis.discord.loggingChannel`|ID of the channel to be used in the Discord logging server.|
-   |`apis.mailgun`|Can be obtained by setting up a [Mailgun account](http://www.mailgun.com/), or you can disable it.|
-   |`apis.spotify`|Can be obtained by setting up a [Spotify client id](https://developer.spotify.com/dashboard/applications), or you can disable it.|
-   |`apis.discogs`|Can be obtained by setting up a [Discogs application](https://www.discogs.com/settings/developers), or you can disable it.|
-   |`redis.url`|Should be left alone for Docker, and changed to `redis://localhost:6379/0` for non-Docker.|
-   |`redis.password`|Should be the Redis password you either put in your `startRedis.cmd` file for Windows, or `.env` for docker.|
-   |`mongo.url`|Needs to have the proper password for the MongoDB musare user, and for non-Docker you need to replace `@musare:27017` with `@localhost:27017`.|
-   |`cookie.domain`|Should be the ip or address you use to access the site, without protocols (http/https), so for example `localhost`.|
-   |`cookie.secure`|Should be `true` for SSL connections, and `false` for normal http connections.|
+    | Property                      | Description                                                                                                                                                                                                                                                                                                                                                                                   |
+    | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+    | `mode`                        | Should be either `development` or `production`. No more explanation needed.                                                                                                                                                                                                                                                                                                                   |
+    | `secret`                      | Whatever you want - used by express's session module.                                                                                                                                                                                                                                                                                                                                         |
+    | `domain`                      | Should be the url where the site will be accessible from,usually `http://localhost` for non-Docker.                                                                                                                                                                                                                                                                                           |
+    | `serverDomain`                | Should be the url where the backend will be accessible from, usually `http://localhost:8080` for non-Docker.                                                                                                                                                                                                                                                                                  |
+    | `serverPort`                  | Should be the port where the backend will listen on, should always be `8080` for Docker, and is recommended for non-Docker.                                                                                                                                                                                                                                                                   |
+    | `isDocker`                    | Self-explanatory. Are you using Docker?                                                                                                                                                                                                                                                                                                                                                       |
+    | `serverPort`                  | Should be the port where the backend will listen on, should always be `8080` for Docker, and is recommended for non-Docker.                                                                                                                                                                                                                                                                   |
+    | `apis.youtube.key`            | Can be obtained by setting up a [YouTube API Key](https://developers.google.com/youtube/v3/getting-started). You need to use the YouTube Data API v3, and create an API key.                                                                                                                                                                                                                  |
+    | `apis.recaptcha.secret`       | Can be obtained by setting up a [ReCaptcha Site (v3)](https://www.google.com/recaptcha/admin).                                                                                                                                                                                                                                                                                                |
+    | `apis.github`                 | Can be obtained by setting up a [GitHub OAuth Application](https://github.com/settings/developers). You need to fill in some values to create the OAuth application. The homepage is the homepage of frontend. The authorization callback url is the backend url with `/auth/github/authorize/callback` added at the end. For example `http://localhost:8080/auth/github/authorize/callback`. |
+    | `apis.discord.token`          | Token for the Discord bot.                                                                                                                                                                                                                                                                                                                                                                    |
+    | `apis.discord.loggingServer`  | Server ID of the Discord logging server.                                                                                                                                                                                                                                                                                                                                                      |
+    | `apis.discord.loggingChannel` | ID of the channel to be used in the Discord logging server.                                                                                                                                                                                                                                                                                                                                   |
+    | `apis.mailgun`                | Can be obtained by setting up a [Mailgun account](http://www.mailgun.com/), or you can disable it.                                                                                                                                                                                                                                                                                            |
+    | `apis.spotify`                | Can be obtained by setting up a [Spotify client id](https://developer.spotify.com/dashboard/applications), or you can disable it.                                                                                                                                                                                                                                                             |
+    | `apis.discogs`                | Can be obtained by setting up a [Discogs application](https://www.discogs.com/settings/developers), or you can disable it.                                                                                                                                                                                                                                                                    |
+    | `redis.url`                   | Should be left alone for Docker, and changed to `redis://localhost:6379/0` for non-Docker.                                                                                                                                                                                                                                                                                                    |
+    | `redis.password`              | Should be the Redis password you either put in your `startRedis.cmd` file for Windows, or `.env` for docker.                                                                                                                                                                                                                                                                                  |
+    | `mongo.url`                   | Needs to have the proper password for the MongoDB musare user, and for non-Docker you need to replace `@musare:27017` with `@localhost:27017`.                                                                                                                                                                                                                                                |
+    | `cookie.domain`               | Should be the ip or address you use to access the site, without protocols (http/https), so for example `localhost`.                                                                                                                                                                                                                                                                           |
+    | `cookie.secure`               | Should be `true` for SSL connections, and `false` for normal http connections.                                                                                                                                                                                                                                                                                                                |
 
 4. `cp frontend/build/config/template.json frontend/build/config/default.json`
 
-   |Property|Description|
-   |--|--|
-   |`serverDomain`|Should be the url where the backend will be accessible from, usually `http://localhost:8080` for non-Docker.|
-   |`frontendDomain`|Should be the url where the frontend will be accessible from, usually `http://localhost` for docker or `http://localhost:80` for non-Docker.|
-   |`frontendPort`|Should be the port where the frontend will be accessible from, should always be port `81` for Docker, and is recommended to be port `80` for non-Docker.|
-   |`recaptcha.key`|Can be obtained by setting up a [ReCaptcha Site (v3)](https://www.google.com/recaptcha/admin).|
-   |`cookie.domain`|Should be the ip or address you use to access the site, without protocols (http/https), so for example `localhost`.|
-   |`cookie.secure`|Should be `true` for SSL connections, and `false` for normal http connections.|
-   |`siteSettings.logo`|Path to the logo image, by default it is `/assets/wordmark.png`.|
-   |`siteSettings.siteName`|Should be the name of the site.|
-   |`siteSettings.socialLinks`|`github`, `twitter` and `facebook` are set to the official Musare accounts by default, but can be changed.|
+    | Property                   | Description                                                                                                                                              |
+    | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
+    | `serverDomain`             | Should be the url where the backend will be accessible from, usually `http://localhost:8080` for non-Docker.                                             |
+    | `frontendDomain`           | Should be the url where the frontend will be accessible from, usually `http://localhost` for docker or `http://localhost:80` for non-Docker.             |
+    | `frontendPort`             | Should be the port where the frontend will be accessible from, should always be port `81` for Docker, and is recommended to be port `80` for non-Docker. |
+    | `recaptcha.key`            | Can be obtained by setting up a [ReCaptcha Site (v3)](https://www.google.com/recaptcha/admin).                                                           |
+    | `cookie.domain`            | Should be the ip or address you use to access the site, without protocols (http/https), so for example `localhost`.                                      |
+    | `cookie.secure`            | Should be `true` for SSL connections, and `false` for normal http connections.                                                                           |
+    | `siteSettings.logo`        | Path to the logo image, by default it is `/assets/wordmark.png`.                                                                                         |
+    | `siteSettings.siteName`    | Should be the name of the site.                                                                                                                          |
+    | `siteSettings.socialLinks` | `github`, `twitter` and `facebook` are set to the official Musare accounts by default, but can be changed.                                               |
 
 5. Simply `cp .env.example .env` to setup your environment variables.
 
 6. To setup [snyk](https://snyk.io/) (which is what we use for our precommit git-hooks), you will need to:
 
-   - Setup an account
-   - Go to [settings](https://app.snyk.io/account)
-   - Copy the API token and set it as your `SNYK_TOKEN` environment variable.
+    - Setup an account
+    - Go to [settings](https://app.snyk.io/account)
+    - Copy the API token and set it as your `SNYK_TOKEN` environment variable.
 
 We use snyk to test our dependencies / dev-dependencies for vulnerabilities.
 
@@ -110,51 +108,51 @@ We use snyk to test our dependencies / dev-dependencies for vulnerabilities.
 #### Configuration
 
 To configure docker configure the `.env` file to match your settings in `backend/config/default.json`.  
-The configurable ports will be how you access the services on your machine, or what ports you will need to specify in your nginx files when using proxy_pass. 
+The configurable ports will be how you access the services on your machine, or what ports you will need to specify in your nginx files when using proxy_pass.
 `COMPOSE_PROJECT_NAME` should be a unique name for this installation, especially if you have multiple instances of Musare on the same machine.
 `FRONTEND_MODE` should be either `dev` or `prod` (self-explanatory).
 
 1. Build the backend and frontend Docker images (from the main folder)
 
-   `docker-compose build`
+    `docker-compose build`
 
 2. Set up the MongoDB database
 
-   1. Set the password for the admin/root user.
+    1. Set the password for the admin/root user.
 
-      In `.env` set the environment variable of `MONGO_ROOT_PASSWORD`.
+        In `.env` set the environment variable of `MONGO_ROOT_PASSWORD`.
 
-   2. Set the password for the musare user (the one the backend will use).
+    2. Set the password for the musare user (the one the backend will use).
 
-      In `.env` set the environment variable of `MONGO_USER_USERNAME` and `MONGO_USER_PASSWORD`.
+        In `.env` set the environment variable of `MONGO_USER_USERNAME` and `MONGO_USER_PASSWORD`.
 
-   3. Start the database (in detached mode), which will generate the correct MongoDB users.
+    3. Start the database (in detached mode), which will generate the correct MongoDB users.
 
-      `docker-compose up -d mongo`
+        `docker-compose up -d mongo`
 
 3. Start redis and the mongo client in the background, as we usually don't need to monitor these for errors
 
-   `docker-compose up -d mongoclient redis`
+    `docker-compose up -d mongoclient redis`
 
 4. Start the backend and frontend in the foreground, so we can watch for errors during development
 
-   `docker-compose up backend frontend`
+    `docker-compose up backend frontend`
 
 5. You should now be able to begin development! The backend is auto reloaded when
    you make changes and the frontend is auto compiled and live reloaded by webpack
    when you make changes. You should be able to access Musare in your local browser
    at `http://<docker-machine-ip>:8080/` where `<docker-machine-ip>` can be found below:
 
-   - Docker for Windows / Mac: This is just `localhost`
+    - Docker for Windows / Mac: This is just `localhost`
 
-   - Docker ToolBox: The output of `docker-machine ip default`
+    - Docker ToolBox: The output of `docker-machine ip default`
 
-If you are using linting extensions in IDEs/want to run `yarn lint`, you need to install the following locally (outside of Docker):
+If you are using linting extensions in IDEs/want to run `npm run lint`, you need to install the following locally (outside of Docker):
 
-   ```bash
-   yarn global add eslint
-   yarn add eslint-config-airbnb-base
-   ```
+```bash
+npm install -g eslint
+npm install -g eslint-config-airbnb-base
+```
 
 ### Standard Installation
 
@@ -164,9 +162,9 @@ Steps 1-4 are things you only have to do once. The steps to start servers follow
 
 2. Create a file called `startMongo.cmd` in the main folder with the contents:
 
-   "C:\Program Files\MongoDB\Server\3.2\bin\mongod.exe" --dbpath "D:\Programming\HTML\MusareNode\.database"
+    "C:\Program Files\MongoDB\Server\3.2\bin\mongod.exe" --dbpath "D:\Programming\HTML\MusareNode\.database"
 
-   Make sure to adjust your paths accordingly.
+    Make sure to adjust your paths accordingly.
 
 3. Set up the MongoDB database
 
@@ -174,35 +172,35 @@ Steps 1-4 are things you only have to do once. The steps to start servers follow
 
     2. Connect to Mongo from a command prompt
 
-       `mongo admin`
+        `mongo admin`
 
     3. Create an admin user
 
-       `db.createUser({user: 'admin', pwd: 'PASSWORD_HERE', roles: [{role: 'userAdminAnyDatabase', db: 'admin'}]})`
+        `db.createUser({user: 'admin', pwd: 'PASSWORD_HERE', roles: [{role: 'userAdminAnyDatabase', db: 'admin'}]})`
 
     4. Connect to the Musare database
 
-       `use musare`
+        `use musare`
 
     5. Create the musare user
 
-       `db.createUser({user: 'musare', pwd: 'OTHER_PASSWORD_HERE', roles: [{role: 'readWrite', db: 'musare'}]})`
+        `db.createUser({user: 'musare', pwd: 'OTHER_PASSWORD_HERE', roles: [{role: 'readWrite', db: 'musare'}]})`
 
     6. Exit
 
-       `exit`
+        `exit`
 
     7. Add the authentication
 
-       In `startMongo.cmd` add `--auth` at the end of the first line
+        In `startMongo.cmd` add `--auth` at the end of the first line
 
 4. In the folder where you installed Redis, edit the `redis.windows.conf` file. In there, look for the property `notify-keyspace-events`. Make sure that property is uncommented and has the value `Ex`. It should look like `notify-keyspace-events Ex` when done.
 
 5. Create a file called `startRedis.cmd` in the main folder with the contents:
 
-   "D:\Redis\redis-server.exe" "D:\Redis\redis.windows.conf" "--requirepass" "PASSWORD"
+    "D:\Redis\redis-server.exe" "D:\Redis\redis.windows.conf" "--requirepass" "PASSWORD"
 
-   And again, make sure that the paths lead to the proper config and executable. Replace `PASSWORD` with your Redis password.
+    And again, make sure that the paths lead to the proper config and executable. Replace `PASSWORD` with your Redis password.
 
 ### Non-docker start servers
 
@@ -214,7 +212,7 @@ Steps 1-4 are things you only have to do once. The steps to start servers follow
 
 1. Run `startRedis.cmd` and `startMongo.cmd` to start Redis and Mongo.
 
-2. In a command prompt with the pwd of frontend, run `yarn run dev`
+2. In a command prompt with the pwd of frontend, run `npm run dev`
 
 3. In a command prompt with the pwd of backend, run `nodemon`
 
@@ -231,29 +229,29 @@ of the following commands to give Docker Toolbox access to those files.
 
 1. First lets ensure the machine isn't running
 
-   `docker-machine stop default`
+    `docker-machine stop default`
 
 1. Next we'll want to tell the machine about the folder we want to share.
 
-   `"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" sharedfolder add default --name "d/Projects/MusareNode" --hostpath "D:\Projects\MusareNode" --automount`
+    `"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" sharedfolder add default --name "d/Projects/MusareNode" --hostpath "D:\Projects\MusareNode" --automount`
 
 1. Now start the machine back up and ssh into it
 
-   `docker-machine start default && docker-machine ssh default`
+    `docker-machine start default && docker-machine ssh default`
 
 1. Tell boot2docker to mount our volume at startup, by appending to its startup script
 
-   ```bash
-   sudo tee -a /mnt/sda1/var/lib/boot2docker/profile >/dev/null <<EOF
+    ```bash
+    sudo tee -a /mnt/sda1/var/lib/boot2docker/profile >/dev/null <<EOF
 
-   mkdir -p /d/Projects/MusareNode
-   mount -t vboxsf -o uid=1000,gid=50 d/Projects/MusareNode /d/Projects/MusareNode
-   EOF
-   ```
+    mkdir -p /d/Projects/MusareNode
+    mount -t vboxsf -o uid=1000,gid=50 d/Projects/MusareNode /d/Projects/MusareNode
+    EOF
+    ```
 
 1. Restart the docker machine so that it uses the new shared folder
 
-   `docker-machine restart default`
+    `docker-machine restart default`
 
 1. You now should be good to go!
 
@@ -271,19 +269,19 @@ Run this command in your shell. You will have to do this command for every shell
 
 2. Install nodemon globally
 
-   `yarn global add nodemon`
+    `npm install -g nodemon`
 
 3. Install webpack globally
 
-   `yarn global add webpack`
+    `npm install -g webpack`
 
 4. Install node-gyp globally (first check out https://github.com/nodejs/node-gyp#installation)
 
-   `yarn global add node-gyp`.
+    `npm install -g node-gyp`.
 
-5. Run `yarn run bootstrap` to install dependencies and dev-dependencies for both the frontend and backend.
+5. Run `npm run bootstrap` to install dependencies and dev-dependencies for both the frontend and backend.
 
-6. Either execute `yarn run dev:frontend` and `yarn run dev:backend` separately, or in parallel with `yarn dev`.
+6. Either execute `npm run dev:frontend` and `npm run dev:backend` separately, or in parallel with `npm run dev`.
 
 ### Calling Toasts
 
@@ -314,4 +312,4 @@ For example, this is how we would to add the `webpack-bundle-analyser` package a
 
 ```bash
 npx lerna add webpack-bundle-analyser --scope=musare-frontend --dev
-```
+```

+ 48 - 0
backend/classes/Timer.class.js

@@ -0,0 +1,48 @@
+module.exports = class Timer {
+    constructor(callback, delay, paused) {
+        this.callback = callback;
+        this.timerId = undefined;
+        this.start = undefined;
+        this.paused = paused;
+        this.remaining = delay;
+        this.timeWhenPaused = 0;
+        this.timePaused = Date.now();
+
+        if (!paused) {
+            this.resume();
+        }
+    }
+
+    pause() {
+        clearTimeout(this.timerId);
+        this.remaining -= Date.now() - this.start;
+        this.timePaused = Date.now();
+        this.paused = true;
+    }
+
+    ifNotPaused() {
+        if (!this.paused) {
+            this.resume();
+        }
+    }
+
+    resume() {
+        this.start = Date.now();
+        clearTimeout(this.timerId);
+        this.timerId = setTimeout(this.callback, this.remaining);
+        this.timeWhenPaused = Date.now() - this.timePaused;
+        this.paused = false;
+    }
+
+    resetTimeWhenPaused() {
+        this.timeWhenPaused = 0;
+    }
+
+    getTimePaused() {
+        if (!this.paused) {
+            return this.timeWhenPaused;
+        } else {
+            return Date.now() - this.timePaused;
+        }
+    }
+};

+ 102 - 70
backend/core.js

@@ -1,85 +1,117 @@
-const EventEmitter = require('events');
+const async = require("async");
 
-const bus = new EventEmitter();
+class DeferredPromise {
+    constructor() {
+        this.promise = new Promise((resolve, reject) => {
+            this.reject = reject;
+            this.resolve = resolve;
+        });
+    }
+}
 
-bus.setMaxListeners(1000);
+class CoreClass {
+    constructor(name) {
+        this.name = name;
+        this.status = "UNINITIALIZED";
+        // this.log("Core constructor");
+        this.jobQueue = async.priorityQueue(
+            (job, callback) => this._runJob(job, callback),
+            10 // How many jobs can run concurrently
+        );
+        this.jobQueue.pause();
+        this.runningJobs = [];
+        this.priorities = {};
+        this.stage = 0;
+    }
 
-module.exports = class {
-	constructor(name, moduleManager) {
-		this.name = name;
-		this.moduleManager = moduleManager;
-		this.lockdown = false;
-		this.dependsOn = [];
-		this.eventHandlers = [];
-		this.state = "NOT_INITIALIZED";
-		this.stage = 0;
-		this.lastTime = 0;
-		this.totalTimeInitialize = 0;
-		this.timeDifferences = [];
-		this.failed = false;
-	}
+    setStatus(status) {
+        this.status = status;
+        this.log("INFO", `Status changed to: ${status}`);
+        if (this.status === "READY") this.jobQueue.resume();
+        else if (this.status === "FAIL" || this.status === "LOCKDOWN")
+            this.jobQueue.pause();
+    }
 
-	_initialize() {
-		this.logger = this.moduleManager.modules["logger"];
-		this.setState("INITIALIZING");
+    getStatus() {
+        return this.status;
+    }
 
-		this.initialize().then(() => {
-			this.setState("INITIALIZED");
-			this.setStage(0);
-			this.moduleManager.printStatus();
-		}).catch(async (err) => {			
-			this.failed = true;
+    setStage(stage) {
+        this.stage = stage;
+    }
 
-			this.logger.error(err.stack);
+    getStage() {
+        return this.stage;
+    }
 
-			this.moduleManager.aModuleFailed(this);
-		});
-	}
+    _initialize() {
+        this.setStatus("INITIALIZING");
+        this.initialize()
+            .then(() => {
+                this.setStatus("READY");
+                this.moduleManager.onInitialize(this);
+            })
+            .catch((err) => {
+                console.error(err);
+                this.setStatus("FAILED");
+                this.moduleManager.onFail(this);
+            });
+    }
 
-	_onInitialize() {
-		return new Promise(resolve => bus.once(`stateChange:${this.name}:INITIALIZED`, resolve));
-	}
+    log() {
+        let _arguments = Array.from(arguments);
+        const type = _arguments[0];
+        _arguments.splice(0, 1);
+        const start = `|${this.name.toUpperCase()}|`;
+        const numberOfTabsNeeded = 4 - Math.ceil(start.length / 8);
+        _arguments.unshift(`${start}${Array(numberOfTabsNeeded).join("\t")}`);
 
-	_isInitialized() {
-		return new Promise(resolve => {
-			if (this.state === "INITIALIZED") resolve();
-		});
-	}
+        if (type === "INFO") {
+            _arguments[0] = _arguments[0] + "\x1b[36m";
+            _arguments.push("\x1b[0m");
+            console.log.apply(null, _arguments);
+        } else if (type === "ERROR") {
+            _arguments[0] = _arguments[0] + "\x1b[31m";
+            _arguments.push("\x1b[0m");
+            console.error.apply(null, _arguments);
+        }
+    }
 
-	_isNotLocked() {
-		return new Promise((resolve, reject) => {
-			if (this.state === "LOCKDOWN") reject();
-			else resolve();
-		});
-	}
+    runJob(name, payload, options = {}) {
+        let deferredPromise = new DeferredPromise();
+        const job = { name, payload, onFinish: deferredPromise };
 
-	setState(state) {
-		this.state = state;
-		bus.emit(`stateChange:${this.name}:${state}`);
-		this.logger.info(`MODULE_STATE`, `${state}: ${this.name}`);
-	}
+        if (options.bypassQueue) {
+            this._runJob(job, () => {});
+        } else {
+            const priority = this.priorities[name] ? this.priorities[name] : 10;
+            this.jobQueue.push(job, priority);
+        }
 
-	setStage(stage) {
-		if (stage !== 1)
-			this.totalTimeInitialize += (Date.now() - this.lastTime);
-		//this.timeDifferences.push(this.stage + ": " + (Date.now() - this.lastTime) + "ms");
-		this.timeDifferences.push(Date.now() - this.lastTime);
+        return deferredPromise.promise;
+    }
 
-		this.lastTime = Date.now();
-		this.stage = stage;
-		this.moduleManager.printStatus();
-	}
+    setModuleManager(moduleManager) {
+        this.moduleManager = moduleManager;
+    }
 
-	_validateHook() {
-		return Promise.race([this._onInitialize(), this._isInitialized()]).then(
-			() => this._isNotLocked()
-		);
-	}
+    _runJob(job, cb) {
+        this.log("INFO", `Running job ${job.name}`);
+        this.runningJobs.push(job);
+        this[job.name](job.payload)
+            .then((response) => {
+                this.log("INFO", `Ran job ${job.name} successfully`);
+                job.onFinish.resolve(response);
+            })
+            .catch((error) => {
+                this.log("INFO", `Running job ${job.name} failed`);
+                job.onFinish.reject(error);
+            })
+            .finally(() => {
+                this.runningJobs.splice(this.runningJobs.indexOf(job), 1);
+                cb();
+            });
+    }
+}
 
-	_lockdown() {
-		if (this.lockdown) return;
-		this.lockdown = true;
-		this.setState("LOCKDOWN");
-		this.moduleManager.printStatus();
-	}
-}
+module.exports = CoreClass;

+ 298 - 159
backend/index.js

@@ -1,4 +1,4 @@
-'use strict';
+"use strict";
 
 const util = require("util");
 
@@ -6,168 +6,292 @@ process.env.NODE_CONFIG_DIR = `${__dirname}/config`;
 
 const config = require("config");
 
-process.on('uncaughtException', err => {
-	if (err.code === 'ECONNREFUSED' || err.code === 'UNCERTAIN_STATE') return;
-	console.log(`UNCAUGHT EXCEPTION: ${err.stack}`);
+process.on("uncaughtException", (err) => {
+    if (err.code === "ECONNREFUSED" || err.code === "UNCERTAIN_STATE") return;
+    console.log(`UNCAUGHT EXCEPTION: ${err.stack}`);
 });
 
 const fancyConsole = config.get("fancyConsole");
 
+// class ModuleManager {
+// 	constructor() {
+// 		this.modules = {};
+// 		this.modulesInitialized = 0;
+// 		this.totalModules = 0;
+// 		this.modulesLeft = [];
+// 		this.i = 0;
+// 		this.lockdown = false;
+// 		this.fancyConsole = fancyConsole;
+// 	}
+
+// 	addModule(moduleName) {
+// 		console.log("add module", moduleName);
+// 		const moduleClass = new require(`./logic/${moduleName}`);
+// 		this.modules[moduleName] = new moduleClass(moduleName, this);
+// 		this.totalModules++;
+// 		this.modulesLeft.push(moduleName);
+// 	}
+
+// 	initialize() {
+// 		if (!this.modules["logger"]) return console.error("There is no logger module");
+// 		this.logger = this.modules["logger"];
+// 		if (this.fancyConsole) {
+// 			this.replaceConsoleWithLogger();
+// 			this.logger.reservedLines = Object.keys(this.modules).length + 5;
+// 		}
+
+// 		for (let moduleName in this.modules) {
+// 			let module = this.modules[moduleName];
+// 			if (this.lockdown) break;
+
+// 			module._onInitialize().then(() => {
+// 				this.moduleInitialized(moduleName);
+// 			});
+
+// 			let dependenciesInitializedPromises = [];
+
+// 			module.dependsOn.forEach(dependencyName => {
+// 				let dependency = this.modules[dependencyName];
+// 				dependenciesInitializedPromises.push(dependency._onInitialize());
+// 			});
+
+// 			module.lastTime = Date.now();
+
+// 			Promise.all(dependenciesInitializedPromises).then((res, res2) => {
+// 				if (this.lockdown) return;
+// 				this.logger.info("MODULE_MANAGER", `${moduleName} dependencies have been completed`);
+// 				module._initialize();
+// 			});
+// 		}
+// 	}
+
+// 	async printStatus() {
+// 		try { await Promise.race([this.logger._onInitialize(), this.logger._isInitialized()]); } catch { return; }
+// 		if (!this.fancyConsole) return;
+
+// 		let colors = this.logger.colors;
+
+// 		const rows = process.stdout.rows;
+
+// 		process.stdout.cursorTo(0, rows - this.logger.reservedLines);
+// 		process.stdout.clearScreenDown();
+
+// 		process.stdout.cursorTo(0, (rows - this.logger.reservedLines) + 2);
+
+// 		process.stdout.write(`${colors.FgYellow}Modules${colors.FgWhite}:\n`);
+
+// 		for (let moduleName in this.modules) {
+// 			let module = this.modules[moduleName];
+// 			let tabsAmount = Math.max(0, Math.ceil(2 - (moduleName.length / 8)));
+
+// 			let tabs = Array(tabsAmount).fill(`\t`).join("");
+
+// 			let timing = module.timeDifferences.map((timeDifference) => {
+// 				return `${colors.FgMagenta}${timeDifference}${colors.FgCyan}ms${colors.FgWhite}`;
+// 			}).join(", ");
+
+// 			let stateColor;
+// 			if (module.state === "NOT_INITIALIZED") stateColor = colors.FgWhite;
+// 			else if (module.state === "INITIALIZED") stateColor = colors.FgGreen;
+// 			else if (module.state === "LOCKDOWN" && !module.failed) stateColor = colors.FgRed;
+// 			else if (module.state === "LOCKDOWN" && module.failed) stateColor = colors.FgMagenta;
+// 			else stateColor = colors.FgYellow;
+
+// 			process.stdout.write(`${moduleName}${tabs}${stateColor}${module.state}\t${colors.FgYellow}Stage: ${colors.FgRed}${module.stage}${colors.FgWhite}. ${colors.FgYellow}Timing${colors.FgWhite}: [${timing}]${colors.FgWhite}${colors.FgWhite}. ${colors.FgYellow}Total time${colors.FgWhite}: ${colors.FgRed}${module.totalTimeInitialize}${colors.FgCyan}ms${colors.Reset}\n`);
+// 		}
+// 	}
+
+// 	moduleInitialized(moduleName) {
+// 		this.modulesInitialized++;
+// 		this.modulesLeft.splice(this.modulesLeft.indexOf(moduleName), 1);
+
+// 		this.logger.info("MODULE_MANAGER", `Initialized: ${this.modulesInitialized}/${this.totalModules}.`);
+
+// 		if (this.modulesLeft.length === 0) this.allModulesInitialized();
+// 	}
+
+// 	allModulesInitialized() {
+// 		this.logger.success("MODULE_MANAGER", "All modules have started!");
+// 		this.modules["discord"].sendAdminAlertMessage("The backend server started successfully.", "#00AA00", "Startup", false, []);
+// 	}
+
+// 	aModuleFailed(failedModule) {
+// 		this.logger.error("MODULE_MANAGER", `A module has failed, locking down. Module: ${failedModule.name}`);
+// 		this.modules["discord"].sendAdminAlertMessage(`The backend server failed to start due to a failing module: ${failedModule.name}.`, "#AA0000", "Startup", false, []);
+
+// 		this._lockdown();
+// 	}
+
+// 	replaceConsoleWithLogger() {
+// 		this.oldConsole = {
+// 			log: console.log,
+// 			debug: console.debug,
+// 			info: console.info,
+// 			warn: console.warn,
+// 			error: console.error
+// 		};
+// 		console.log = (...args) => this.logger.debug(args.map(arg => util.format(arg)));
+// 		console.debug = (...args) => this.logger.debug(args.map(arg => util.format(arg)));
+// 		console.info = (...args) => this.logger.debug(args.map(arg => util.format(arg)));
+// 		console.warn = (...args) => this.logger.debug(args.map(arg => util.format(arg)));
+// 		console.error = (...args) => this.logger.error("CONSOLE", args.map(arg => util.format(arg)));
+// 	}
+
+// 	replaceLoggerWithConsole() {
+// 		console.log = this.oldConsole.log;
+// 		console.debug = this.oldConsole.debug;
+// 		console.info = this.oldConsole.info;
+// 		console.warn = this.oldConsole.warn;
+// 		console.error = this.oldConsole.error;
+// 	}
+
+// 	_lockdown() {
+// 		this.lockdown = true;
+
+// 		for (let moduleName in this.modules) {
+// 			let module = this.modules[moduleName];
+// 			if (module.lockdownImmune) continue;
+// 			module._lockdown();
+// 		}
+// 	}
+// }
+
+// const moduleManager = new ModuleManager();
+
+// module.exports = moduleManager;
+
+// moduleManager.addModule("cache");
+// moduleManager.addModule("db");
+// moduleManager.addModule("mail");
+// moduleManager.addModule("api");
+// moduleManager.addModule("app");
+// moduleManager.addModule("discord");
+// moduleManager.addModule("io");
+// moduleManager.addModule("logger");
+// moduleManager.addModule("notifications");
+// moduleManager.addModule("activities");
+// moduleManager.addModule("playlists");
+// moduleManager.addModule("punishments");
+// moduleManager.addModule("songs");
+// moduleManager.addModule("spotify");
+// moduleManager.addModule("stations");
+// moduleManager.addModule("tasks");
+// moduleManager.addModule("utils");
+
+// moduleManager.initialize();
+
+// process.stdin.on("data", function (data) {
+//     if(data.toString() === "lockdown\r\n"){
+//         console.log("Locking down.");
+//        	moduleManager._lockdown();
+//     }
+// });
+
+// if (fancyConsole) {
+// 	const rows = process.stdout.rows;
+
+// 	for(let i = 0; i < rows; i++) {
+// 		process.stdout.write("\n");
+// 	}
+// }
+
 class ModuleManager {
-	constructor() {
-		this.modules = {};
-		this.modulesInitialized = 0;
-		this.totalModules = 0;
-		this.modulesLeft = [];
-		this.i = 0;
-		this.lockdown = false;
-		this.fancyConsole = fancyConsole;
-	}
-
-	addModule(moduleName) {
-		console.log("add module", moduleName);
-		const moduleClass = new require(`./logic/${moduleName}`);
-		this.modules[moduleName] = new moduleClass(moduleName, this);
-		this.totalModules++;
-		this.modulesLeft.push(moduleName);
-	}
-
-	initialize() {
-		if (!this.modules["logger"]) return console.error("There is no logger module");
-		this.logger = this.modules["logger"];
-		if (this.fancyConsole) {
-			this.replaceConsoleWithLogger();
-			this.logger.reservedLines = Object.keys(this.modules).length + 5;
-		}
-		
-		for (let moduleName in this.modules) {
-			let module = this.modules[moduleName];
-			if (this.lockdown) break;
-
-			module._onInitialize().then(() => {
-				this.moduleInitialized(moduleName);
-			});
-
-			let dependenciesInitializedPromises = [];
-			
-			module.dependsOn.forEach(dependencyName => {
-				let dependency = this.modules[dependencyName];
-				dependenciesInitializedPromises.push(dependency._onInitialize());
-			});
-
-			module.lastTime = Date.now();
-
-			Promise.all(dependenciesInitializedPromises).then((res, res2) => {
-				if (this.lockdown) return;
-				this.logger.info("MODULE_MANAGER", `${moduleName} dependencies have been completed`);
-				module._initialize();
-			});
-		}
-	}
-
-	async printStatus() {
-		try { await Promise.race([this.logger._onInitialize(), this.logger._isInitialized()]); } catch { return; }
-		if (!this.fancyConsole) return;
-		
-		let colors = this.logger.colors;
-
-		const rows = process.stdout.rows;
-
-		process.stdout.cursorTo(0, rows - this.logger.reservedLines);
-		process.stdout.clearScreenDown();
-
-		process.stdout.cursorTo(0, (rows - this.logger.reservedLines) + 2);
-
-		process.stdout.write(`${colors.FgYellow}Modules${colors.FgWhite}:\n`);
-
-		for (let moduleName in this.modules) {
-			let module = this.modules[moduleName];
-			let tabsAmount = Math.max(0, Math.ceil(2 - (moduleName.length / 8)));
-
-			let tabs = Array(tabsAmount).fill(`\t`).join("");
-
-			let timing = module.timeDifferences.map((timeDifference) => {
-				return `${colors.FgMagenta}${timeDifference}${colors.FgCyan}ms${colors.FgWhite}`;
-			}).join(", ");
-
-			let stateColor;
-			if (module.state === "NOT_INITIALIZED") stateColor = colors.FgWhite;
-			else if (module.state === "INITIALIZED") stateColor = colors.FgGreen;
-			else if (module.state === "LOCKDOWN" && !module.failed) stateColor = colors.FgRed;
-			else if (module.state === "LOCKDOWN" && module.failed) stateColor = colors.FgMagenta;
-			else stateColor = colors.FgYellow;
-			
-			process.stdout.write(`${moduleName}${tabs}${stateColor}${module.state}\t${colors.FgYellow}Stage: ${colors.FgRed}${module.stage}${colors.FgWhite}. ${colors.FgYellow}Timing${colors.FgWhite}: [${timing}]${colors.FgWhite}${colors.FgWhite}. ${colors.FgYellow}Total time${colors.FgWhite}: ${colors.FgRed}${module.totalTimeInitialize}${colors.FgCyan}ms${colors.Reset}\n`);
-		}
-	}
-
-	moduleInitialized(moduleName) {
-		this.modulesInitialized++;
-		this.modulesLeft.splice(this.modulesLeft.indexOf(moduleName), 1);
-
-		this.logger.info("MODULE_MANAGER", `Initialized: ${this.modulesInitialized}/${this.totalModules}.`);
-
-		if (this.modulesLeft.length === 0) this.allModulesInitialized();
-	}
-
-	allModulesInitialized() {
-		this.logger.success("MODULE_MANAGER", "All modules have started!");
-		this.modules["discord"].sendAdminAlertMessage("The backend server started successfully.", "#00AA00", "Startup", false, []);
-	}
-
-	aModuleFailed(failedModule) {
-		this.logger.error("MODULE_MANAGER", `A module has failed, locking down. Module: ${failedModule.name}`);
-		this.modules["discord"].sendAdminAlertMessage(`The backend server failed to start due to a failing module: ${failedModule.name}.`, "#AA0000", "Startup", false, []);
-
-		this._lockdown();
-	}
-
-	replaceConsoleWithLogger() {
-		this.oldConsole = {
-			log: console.log,
-			debug: console.debug,
-			info: console.info,
-			warn: console.warn,
-			error: console.error
-		};
-		console.log = (...args) => this.logger.debug(args.map(arg => util.format(arg)));
-		console.debug = (...args) => this.logger.debug(args.map(arg => util.format(arg)));
-		console.info = (...args) => this.logger.debug(args.map(arg => util.format(arg)));
-		console.warn = (...args) => this.logger.debug(args.map(arg => util.format(arg)));
-		console.error = (...args) => this.logger.error("CONSOLE", args.map(arg => util.format(arg)));
-	}
-
-	replaceLoggerWithConsole() {
-		console.log = this.oldConsole.log;
-		console.debug = this.oldConsole.debug;
-		console.info = this.oldConsole.info;
-		console.warn = this.oldConsole.warn;
-		console.error = this.oldConsole.error;
-	}
-
-	_lockdown() {
-		this.lockdown = true;
-		
-		for (let moduleName in this.modules) {
-			let module = this.modules[moduleName];
-			if (module.lockdownImmune) continue;
-			module._lockdown();
-		}
-	}
+    constructor() {
+        this.modules = {};
+        this.modulesNotInitialized = [];
+        this.i = 0;
+        this.lockdown = false;
+        this.fancyConsole = fancyConsole;
+    }
+
+    addModule(moduleName) {
+        console.log("add module", moduleName);
+        const module = require(`./logic/${moduleName}`);
+        this.modules[moduleName] = module;
+        this.modulesNotInitialized.push(module);
+    }
+
+    initialize() {
+        // if (!this.modules["logger"]) return console.error("There is no logger module");
+        // this.logger = this.modules["logger"];
+        // if (this.fancyConsole) {
+        // this.replaceConsoleWithLogger();
+        this.reservedLines = Object.keys(this.modules).length + 5;
+        // }
+
+        for (let moduleName in this.modules) {
+            let module = this.modules[moduleName];
+            module.setModuleManager(this);
+
+            if (this.lockdown) break;
+
+            module._initialize();
+
+            // let dependenciesInitializedPromises = [];
+
+            // module.dependsOn.forEach(dependencyName => {
+            // 	let dependency = this.modules[dependencyName];
+            // 	dependenciesInitializedPromises.push(dependency._onInitialize());
+            // });
+
+            // module.lastTime = Date.now();
+
+            // Promise.all(dependenciesInitializedPromises).then((res, res2) => {
+            // 	if (this.lockdown) return;
+            // 	this.logger.info("MODULE_MANAGER", `${moduleName} dependencies have been completed`);
+            // 	module._initialize();
+            // });
+        }
+    }
+
+    onInitialize(module) {
+        if (this.modulesNotInitialized.indexOf(module) !== -1) {
+            this.modulesNotInitialized.splice(
+                this.modulesNotInitialized.indexOf(module),
+                1
+            );
+
+            console.log(
+                "MODULE_MANAGER",
+                `Initialized: ${Object.keys(this.modules).length -
+                    this.modulesNotInitialized.length}/${
+                    Object.keys(this.modules).length
+                }.`
+            );
+
+            if (this.modulesNotInitialized.length === 0)
+                this.onAllModulesInitialized();
+        }
+    }
+
+    onFail(module) {
+        if (this.modulesNotInitialized.indexOf(module) !== -1) {
+            console.log("A module failed to initialize!");
+        }
+    }
+
+    onAllModulesInitialized() {
+        console.log("All modules initialized!");
+        this.modules["discord"].runJob("SEND_ADMIN_ALERT_MESSAGE", {
+            message: "The backend server started successfully.",
+            color: "#00AA00",
+            type: "Startup",
+            critical: false,
+            extraFields: [],
+        });
+    }
 }
 
 const moduleManager = new ModuleManager();
 
-module.exports = moduleManager;
-
 moduleManager.addModule("cache");
 moduleManager.addModule("db");
 moduleManager.addModule("mail");
+moduleManager.addModule("activities");
 moduleManager.addModule("api");
 moduleManager.addModule("app");
 moduleManager.addModule("discord");
 moduleManager.addModule("io");
-moduleManager.addModule("logger");
 moduleManager.addModule("notifications");
 moduleManager.addModule("playlists");
 moduleManager.addModule("punishments");
@@ -179,18 +303,33 @@ moduleManager.addModule("utils");
 
 moduleManager.initialize();
 
-process.stdin.on("data", function (data) {
-    if(data.toString() === "lockdown\r\n"){
+process.stdin.on("data", function(data) {
+    if (data.toString() === "lockdown\r\n") {
         console.log("Locking down.");
-       	moduleManager._lockdown();
+        moduleManager._lockdown();
     }
-});
-
-
-if (fancyConsole) {
-	const rows = process.stdout.rows;
+    if (data.toString() === "status\r\n") {
+        console.log("Status:");
+
+        for (let moduleName in moduleManager.modules) {
+            let module = moduleManager.modules[moduleName];
+            const tabsNeeded = 4 - Math.ceil((moduleName.length + 1) / 8);
+            console.log(
+                `${moduleName.toUpperCase()}${Array(tabsNeeded).join(
+                    "\t"
+                )}${module.getStatus()}. Jobs in queue: ${module.jobQueue.length()}. Jobs in progress: ${module.jobQueue.running()}. Concurrency: ${
+                    module.jobQueue.concurrency
+                }. Stage: ${module.getStage()}`
+            );
+        }
+        // moduleManager._lockdown();
+    }
+    if (data.toString().startsWith("running")) {
+        const parts = data
+            .toString()
+            .substr(0, data.toString().length - 2)
+            .split(" ");
 
-	for(let i = 0; i < rows; i++) {
-		process.stdout.write("\n");
-	}
-}
+        console.log(moduleManager.modules[parts[1]].runningJobs);
+    }
+});

+ 99 - 0
backend/logic/actions/activities.js

@@ -0,0 +1,99 @@
+"use strict";
+
+const async = require("async");
+
+const hooks = require("./hooks");
+
+const db = require("../db");
+const utils = require("../utils");
+const activities = require("../activities");
+
+// const logger = moduleManager.modules["logger"];
+
+module.exports = {
+    /**
+     * Gets a set of activities
+     *
+     * @param session
+     * @param {String} userId - the user whose activities we are looking for
+     * @param {Integer} set - the set number to return
+     * @param cb
+     */
+    getSet: async (session, userId, set, cb) => {
+        const activityModel = await db.runJob("GET_MODEL", {
+            modelName: "activity",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    activityModel
+                        .find({ userId, hidden: false })
+                        .skip(15 * (set - 1))
+                        .limit(15)
+                        .sort("createdAt")
+                        .exec(next);
+                },
+            ],
+            async (err, activities) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "ACTIVITIES_GET_SET",
+                        `Failed to get set ${set} from activities. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+
+                console.log(
+                    "SUCCESS",
+                    "ACTIVITIES_GET_SET",
+                    `Set ${set} from activities obtained successfully.`
+                );
+                cb({ status: "success", data: activities });
+            }
+        );
+    },
+
+    /**
+     * Hides an activity for a user
+     *
+     * @param session
+     * @param {String} activityId - the activity which should be hidden
+     * @param cb
+     */
+    hideActivity: hooks.loginRequired(async (session, activityId, cb) => {
+        const activityModel = await db.runJob("GET_MODEL", {
+            modelName: "activity",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    activityModel.updateOne(
+                        { _id: activityId },
+                        { $set: { hidden: true } },
+                        next
+                    );
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "ACTIVITIES_HIDE_ACTIVITY",
+                        `Failed to hide activity ${activityId}. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+
+                console.log(
+                    "SUCCESS",
+                    "ACTIVITIES_HIDE_ACTIVITY",
+                    `Successfully hid activity ${activityId}.`
+                );
+                cb({ status: "success" });
+            }
+        );
+    }),
+};

+ 188 - 134
backend/logic/actions/apis.js

@@ -1,152 +1,206 @@
-'use strict';
+"use strict";
 
 const request = require("request");
 const config = require("config");
 const async = require("async");
 
-const hooks = require('./hooks');
-const moduleManager = require("../../index");
+const hooks = require("./hooks");
+// const moduleManager = require("../../index");
 
-const utils = moduleManager.modules["utils"];
-const logger = moduleManager.modules["logger"];
+const utils = require("../utils");
+// const logger = moduleManager.modules["logger"];
 
 module.exports = {
+    /**
+     * Fetches a list of songs from Youtubes API
+     *
+     * @param session
+     * @param query - the query we'll pass to youtubes api
+     * @param cb
+     * @return {{ status: String, data: Object }}
+     */
+    searchYoutube: (session, query, cb) => {
+        const params = [
+            "part=snippet",
+            `q=${encodeURIComponent(query)}`,
+            `key=${config.get("apis.youtube.key")}`,
+            "type=video",
+            "maxResults=15",
+        ].join("&");
 
-	/**
-	 * Fetches a list of songs from Youtubes API
-	 *
-	 * @param session
-	 * @param query - the query we'll pass to youtubes api
-	 * @param cb
-	 * @return {{ status: String, data: Object }}
-	 */
-	searchYoutube: (session, query, cb) => {
-		const params = [
-			'part=snippet',
-			`q=${encodeURIComponent(query)}`,
-			`key=${config.get('apis.youtube.key')}`,
-			'type=video',
-			'maxResults=15'
-		].join('&');
+        async.waterfall(
+            [
+                (next) => {
+                    request(
+                        `https://www.googleapis.com/youtube/v3/search?${params}`,
+                        next
+                    );
+                },
 
-		async.waterfall([
-			(next) => {
-				request(`https://www.googleapis.com/youtube/v3/search?${params}`, next);
-			},
+                (res, body, next) => {
+                    next(null, JSON.parse(body));
+                },
+            ],
+            async (err, data) => {
+                console.log(data.error);
+                if (err || data.error) {
+                    if (!err) err = data.error.message;
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "APIS_SEARCH_YOUTUBE",
+                        `Searching youtube failed with query "${query}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "APIS_SEARCH_YOUTUBE",
+                    `Searching YouTube successful with query "${query}".`
+                );
+                return cb({ status: "success", data });
+            }
+        );
+    },
 
-			(res, body, next) => {
-				next(null, JSON.parse(body));
-			}
-		], async (err, data) => {
-			console.log(data.error);
-			if (err || data.error) {
-				if (!err) err = data.error.message;
-				err = await utils.getError(err);
-				logger.error("APIS_SEARCH_YOUTUBE", `Searching youtube failed with query "${query}". "${err}"`);
-				return cb({status: 'failure', message: err});
-			}
-			logger.success("APIS_SEARCH_YOUTUBE", `Searching YouTube successful with query "${query}".`);
-			return cb({ status: 'success', data });
-		});
-	},
+    /**
+     * Gets Spotify data
+     *
+     * @param session
+     * @param title - the title of the song
+     * @param artist - an artist for that song
+     * @param cb
+     */
+    getSpotifySongs: hooks.adminRequired((session, title, artist, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    utils
+                        .runJob("GET_SONGS_FROM_SPOTIFY", { title, artist })
+                        .then((songs) => next(null, songs))
+                        .catch(next);
+                },
+            ],
+            (songs) => {
+                console.log(
+                    "SUCCESS",
+                    "APIS_GET_SPOTIFY_SONGS",
+                    `User "${session.userId}" got Spotify songs for title "${title}" successfully.`
+                );
+                cb({ status: "success", songs: songs });
+            }
+        );
+    }),
 
-	/**
-	 * Gets Spotify data
-	 *
-	 * @param session
-	 * @param title - the title of the song
-	 * @param artist - an artist for that song
-	 * @param cb
-	 */
-	getSpotifySongs: hooks.adminRequired((session, title, artist, cb) => {
-		async.waterfall([
-			(next) => {
-				utils.getSongsFromSpotify(title, artist, next);
-			}
-		], (songs) => {
-			logger.success('APIS_GET_SPOTIFY_SONGS', `User "${session.userId}" got Spotify songs for title "${title}" successfully.`);
-			cb({status: 'success', songs: songs});
-		});
-	}),
+    /**
+     * Gets Discogs data
+     *
+     * @param session
+     * @param query - the query
+     * @param cb
+     */
+    searchDiscogs: hooks.adminRequired((session, query, page, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    const params = [
+                        `q=${encodeURIComponent(query)}`,
+                        `per_page=20`,
+                        `page=${page}`,
+                    ].join("&");
 
-	/**
-	 * Gets Discogs data
-	 *
-	 * @param session
-	 * @param query - the query
-	 * @param cb
-	 */
-	searchDiscogs: hooks.adminRequired((session, query, page, cb) => {
-		async.waterfall([
-			(next) => {
-				const params = [
-					`q=${encodeURIComponent(query)}`,
-					`per_page=20`,
-					`page=${page}`
-				].join('&');
-		
-				const options = {
-					url: `https://api.discogs.com/database/search?${params}`,
-					headers: {
-						"User-Agent": "Request",
-						"Authorization": `Discogs key=${config.get("apis.discogs.client")}, secret=${config.get("apis.discogs.secret")}`
-					}
-				};
-		
-				request(options, (err, res, body) => {
-					if (err) next(err);
-					body = JSON.parse(body);
-					next(null, body);
-					if (body.error) next(body.error);
-				});
-			}
-		], async (err, body) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("APIS_SEARCH_DISCOGS", `Searching discogs failed with query "${query}". "${err}"`);
-				return cb({status: 'failure', message: err});
-			}
-			logger.success('APIS_SEARCH_DISCOGS', `User "${session.userId}" searched Discogs succesfully for query "${query}".`);
-			cb({status: 'success', results: body.results, pages: body.pagination.pages});
-		});
-	}),
+                    const options = {
+                        url: `https://api.discogs.com/database/search?${params}`,
+                        headers: {
+                            "User-Agent": "Request",
+                            Authorization: `Discogs key=${config.get(
+                                "apis.discogs.client"
+                            )}, secret=${config.get("apis.discogs.secret")}`,
+                        },
+                    };
 
-	/**
-	 * Joins a room
-	 *
-	 * @param session
-	 * @param page - the room to join
-	 * @param cb
-	 */
-	joinRoom: (session, page, cb) => {
-		if (page === 'home') {
-			utils.socketJoinRoom(session.socketId, page);
-		}
-		cb({});
-	},
+                    request(options, (err, res, body) => {
+                        if (err) next(err);
+                        body = JSON.parse(body);
+                        next(null, body);
+                        if (body.error) next(body.error);
+                    });
+                },
+            ],
+            async (err, body) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "APIS_SEARCH_DISCOGS",
+                        `Searching discogs failed with query "${query}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "APIS_SEARCH_DISCOGS",
+                    `User "${session.userId}" searched Discogs succesfully for query "${query}".`
+                );
+                cb({
+                    status: "success",
+                    results: body.results,
+                    pages: body.pagination.pages,
+                });
+            }
+        );
+    }),
 
-	/**
-	 * Joins an admin room
-	 *
-	 * @param session
-	 * @param page - the admin room to join
-	 * @param cb
-	 */
-	joinAdminRoom: hooks.adminRequired((session, page, cb) => {
-		if (page === 'queue' || page === 'songs' || page === 'stations' || page === 'reports' || page === 'news' || page === 'users' || page === 'statistics' || page === 'punishments') {
-			utils.socketJoinRoom(session.socketId, `admin.${page}`);
-		}
-		cb({});
-	}),
+    /**
+     * Joins a room
+     *
+     * @param session
+     * @param page - the room to join
+     * @param cb
+     */
+    joinRoom: (session, page, cb) => {
+        if (page === "home") {
+            utils.runJob("SOCKET_JOIN_ROOM", {
+                socketId: session.socketId,
+                room: page,
+            });
+        }
+        cb({});
+    },
 
-	/**
-	 * Returns current date
-	 *
-	 * @param session
-	 * @param cb
-	 */
-	ping: (session, cb) => {
-		cb({date: Date.now()});
-	}
+    /**
+     * Joins an admin room
+     *
+     * @param session
+     * @param page - the admin room to join
+     * @param cb
+     */
+    joinAdminRoom: hooks.adminRequired((session, page, cb) => {
+        if (
+            page === "queue" ||
+            page === "songs" ||
+            page === "stations" ||
+            page === "reports" ||
+            page === "news" ||
+            page === "users" ||
+            page === "statistics" ||
+            page === "punishments"
+        ) {
+            utils.runJob("SOCKET_JOIN_ROOM", {
+                socketId: session.socketId,
+                room: `admin.${page}`,
+            });
+        }
+        cb({});
+    }),
 
+    /**
+     * Returns current date
+     *
+     * @param session
+     * @param cb
+     */
+    ping: (session, cb) => {
+        cb({ date: Date.now() });
+    },
 };

+ 54 - 36
backend/logic/actions/hooks/adminRequired.js

@@ -1,39 +1,57 @@
-const async = require('async');
+const async = require("async");
 
-const moduleManager = require("../../../index");
-
-const db = moduleManager.modules["db"];
-const cache = moduleManager.modules["cache"];
-const utils = moduleManager.modules["utils"];
-const logger = moduleManager.modules["logger"];
+const db = require("../../db");
+const cache = require("../../cache");
+const utils = require("../../utils");
 
 module.exports = function(next) {
-	return function(session) {
-		let args = [];
-		for (let prop in arguments) args.push(arguments[prop]);
-		let cb = args[args.length - 1];
-		async.waterfall([
-			(next) => {
-				cache.hget('sessions', session.sessionId, next);
-			},
-			(session, next) => {
-				if (!session || !session.userId) return next('Login required.');
-				this.session = session;
-				db.models.user.findOne({_id: session.userId}, next);
-			},
-			(user, next) => {
-				if (!user) return next('Login required.');
-				if (user.role !== 'admin') return next('Insufficient permissions.');
-				next();
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.info("ADMIN_REQUIRED", `User failed to pass admin required check. "${err}"`);
-				return cb({status: 'failure', message: err});
-			}
-			logger.info("ADMIN_REQUIRED", `User "${session.userId}" passed admin required check.`, false);
-			next.apply(null, args);
-		});
-	}
-};
+    return async function(session) {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        let args = [];
+        for (let prop in arguments) args.push(arguments[prop]);
+        let cb = args[args.length - 1];
+        async.waterfall(
+            [
+                (next) => {
+                    cache
+                        .runJob("HGET", {
+                            table: "sessions",
+                            key: session.sessionId,
+                        })
+                        .then((session) => next(null, session))
+                        .catch(next);
+                },
+                (session, next) => {
+                    if (!session || !session.userId)
+                        return next("Login required.");
+                    this.session = session;
+                    userModel.findOne({ _id: session.userId }, next);
+                },
+                (user, next) => {
+                    if (!user) return next("Login required.");
+                    if (user.role !== "admin")
+                        return next("Insufficient permissions.");
+                    next();
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "INFO",
+                        "ADMIN_REQUIRED",
+                        `User failed to pass admin required check. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "INFO",
+                    "ADMIN_REQUIRED",
+                    `User "${session.userId}" passed admin required check.`,
+                    false
+                );
+                next.apply(null, args);
+            }
+        );
+    };
+};

+ 46 - 29
backend/logic/actions/hooks/loginRequired.js

@@ -1,33 +1,50 @@
-const async = require('async');
+const async = require("async");
 
-const moduleManager = require("../../../index");
+const cache = require("../../cache");
+const utils = require("../../utils");
+// const logger = moduleManager.modules["logger"];
 
-const cache = moduleManager.modules["cache"];
-const utils = moduleManager.modules["utils"];
-const logger = moduleManager.modules["logger"];
+console.log(cache);
 
 module.exports = function(next) {
-	return function(session) {
-		let args = [];
-		for (let prop in arguments) args.push(arguments[prop]);
-		let cb = args[args.length - 1];
-		async.waterfall([
-			(next) => {
-				cache.hget('sessions', session.sessionId, next);
-			},
-			(session, next) => {
-				if (!session || !session.userId) return next('Login required.');
-				this.session = session;
-				next();
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.info("LOGIN_REQUIRED", `User failed to pass login required check.`);
-				return cb({status: 'failure', message: err});
-			}
-			logger.info("LOGIN_REQUIRED", `User "${session.userId}" passed login required check.`, false);
-			next.apply(null, args);
-		});
-	}
-};
+    return function(session) {
+        let args = [];
+        for (let prop in arguments) args.push(arguments[prop]);
+        let cb = args[args.length - 1];
+        async.waterfall(
+            [
+                next => {
+                    cache
+                        .runJob("HGET", {
+                            table: "sessions",
+                            key: session.sessionId
+                        })
+                        .then(session => next(null, session))
+                        .catch(next);
+                },
+                (session, next) => {
+                    if (!session || !session.userId)
+                        return next("Login required.");
+                    this.session = session;
+                    next();
+                }
+            ],
+            async err => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "LOGIN_REQUIRED",
+                        `User failed to pass login required check.`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "LOGIN_REQUIRED",
+                    `User "${session.userId}" passed login required check.`,
+                    false
+                );
+                next.apply(null, args);
+            }
+        );
+    };
+};

+ 66 - 40
backend/logic/actions/hooks/ownerRequired.js

@@ -1,45 +1,71 @@
-const async = require('async');
+const async = require("async");
 
 const moduleManager = require("../../../index");
 
-const db = moduleManager.modules["db"];
-const cache = moduleManager.modules["cache"];
-const utils = moduleManager.modules["utils"];
-const logger = moduleManager.modules["logger"];
-const stations = moduleManager.modules["stations"];
+const db = require("../../db");
+const cache = require("../../cache");
+const utils = require("../../utils");
+const stations = require("../../stations");
 
 module.exports = function(next) {
-	return function(session, stationId) {
-		let args = [];
-		for (let prop in arguments) args.push(arguments[prop]);
-		let cb = args[args.length - 1];
-		async.waterfall([
-			(next) => {
-				cache.hget('sessions', session.sessionId, next);
-			},
-			(session, next) => {
-				if (!session || !session.userId) return next('Login required.');
-				this.session = session;
-				db.models.user.findOne({_id: session.userId}, next);
-			},
-			(user, next) => {
-				if (!user) return next('Login required.');
-				if (user.role === 'admin') return next(true);
-				stations.getStation(stationId, next);
-			},
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				if (station.type === 'community' && station.owner === session.userId) return next(true);
-				next('Invalid permissions.');
-			}
-		], async (err) => {
-			if (err !== true) {
-				err = await utils.getError(err);
-				logger.info("OWNER_REQUIRED", `User failed to pass owner required check for station "${stationId}". "${err}"`);
-				return cb({status: 'failure', message: err});
-			}
-			logger.info("OWNER_REQUIRED", `User "${session.userId}" passed owner required check for station "${stationId}"`, false);
-			next.apply(null, args);
-		});
-	}
-};
+    return async function(session, stationId) {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        let args = [];
+        for (let prop in arguments) args.push(arguments[prop]);
+        let cb = args[args.length - 1];
+        async.waterfall(
+            [
+                (next) => {
+                    cache
+                        .runJob("HGET", {
+                            table: "sessions",
+                            key: session.sessionId,
+                        })
+                        .then((session) => next(null, session))
+                        .catch(next);
+                },
+                (session, next) => {
+                    if (!session || !session.userId)
+                        return next("Login required.");
+                    this.session = session;
+                    userModel.findOne({ _id: session.userId }, next);
+                },
+                (user, next) => {
+                    if (!user) return next("Login required.");
+                    if (user.role === "admin") return next(true);
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+                (station, next) => {
+                    if (!station) return next("Station not found.");
+                    if (
+                        station.type === "community" &&
+                        station.owner === session.userId
+                    )
+                        return next(true);
+                    next("Invalid permissions.");
+                },
+            ],
+            async (err) => {
+                if (err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "INFO",
+                        "OWNER_REQUIRED",
+                        `User failed to pass owner required check for station "${stationId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "INFO",
+                    "OWNER_REQUIRED",
+                    `User "${session.userId}" passed owner required check for station "${stationId}"`,
+                    false
+                );
+                next.apply(null, args);
+            }
+        );
+    };
+};

+ 1 - 0
backend/logic/actions/index.js

@@ -7,6 +7,7 @@ module.exports = {
 	stations: require('./stations'),
 	playlists: require('./playlists'),
 	users: require('./users'),
+	activities: require('./activities'),
 	reports: require('./reports'),
 	news: require('./news'),
 	punishments: require('./punishments')

+ 226 - 139
backend/logic/actions/news.js

@@ -1,155 +1,242 @@
-'use strict';
+"use strict";
 
-const async = require('async');
+const async = require("async");
 
-const hooks = require('./hooks');
+const hooks = require("./hooks");
 const moduleManager = require("../../index");
 
-const db = moduleManager.modules["db"];
-const cache = moduleManager.modules["cache"];
-const utils = moduleManager.modules["utils"];
-const logger = moduleManager.modules["logger"];
+const db = require("../db");
+const cache = require("../cache");
+const utils = require("../utils");
+// const logger = require("logger");
 
-cache.sub('news.create', news => {
-	utils.socketsFromUser(news.createdBy, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:admin.news.created', news);
-		});
-	});
+cache.runJob("SUB", {
+    channel: "news.create",
+    cb: (news) => {
+        utils.runJob("SOCKETS_FROM_USER", {
+            userId: news.createdBy,
+            cb: (response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:admin.news.created", news);
+                });
+            },
+        });
+    },
 });
 
-cache.sub('news.remove', news => {
-	utils.socketsFromUser(news.createdBy, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:admin.news.removed', news);
-		});
-	});
+cache.runJob("SUB", {
+    channel: "news.remove",
+    cb: (news) => {
+        utils.runJob("SOCKETS_FROM_USER", {
+            userId: news.createdBy,
+            cb: (response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:admin.news.removed", news);
+                });
+            },
+        });
+    },
 });
 
-cache.sub('news.update', news => {
-	utils.socketsFromUser(news.createdBy, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:admin.news.updated', news);
-		});
-	});
+cache.runJob("SUB", {
+    channel: "news.update",
+    cb: (news) => {
+        utils.runJob("SOCKETS_FROM_USER", {
+            userId: news.createdBy,
+            cb: (response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:admin.news.updated", news);
+                });
+            },
+        });
+    },
 });
 
 module.exports = {
+    /**
+     * Gets all news items
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Function} cb - gets called with the result
+     */
+    index: async (session, cb) => {
+        const newsModel = await db.runJob("GET_MODEL", { modelName: "news" });
+        async.waterfall(
+            [
+                (next) => {
+                    newsModel
+                        .find({})
+                        .sort({ createdAt: "desc" })
+                        .exec(next);
+                },
+            ],
+            async (err, news) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "NEWS_INDEX",
+                        `Indexing news failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "NEWS_INDEX",
+                    `Indexing news successful.`,
+                    false
+                );
+                return cb({ status: "success", data: news });
+            }
+        );
+    },
 
-	/**
-	 * Gets all news items
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Function} cb - gets called with the result
-	 */
-	index: (session, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.news.find({}).sort({ createdAt: 'desc' }).exec(next);
-			}
-		], async (err, news) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("NEWS_INDEX", `Indexing news failed. "${err}"`);
-				return cb({status: 'failure', message: err});
-			}
-			logger.success("NEWS_INDEX", `Indexing news successful.`, false);
-			return cb({ status: 'success', data: news });
-		});
-	},
+    /**
+     * Creates a news item
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Object} data - the object of the news data
+     * @param {Function} cb - gets called with the result
+     */
+    create: hooks.adminRequired(async (session, data, cb) => {
+        const newsModel = await db.runJob("GET_MODEL", { modelName: "news" });
+        async.waterfall(
+            [
+                (next) => {
+                    data.createdBy = session.userId;
+                    data.createdAt = Date.now();
+                    newsModel.create(data, next);
+                },
+            ],
+            async (err, news) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "NEWS_CREATE",
+                        `Creating news failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                cache.runJob("PUB", { channel: "news.create", value: news });
+                console.log(
+                    "SUCCESS",
+                    "NEWS_CREATE",
+                    `Creating news successful.`
+                );
+                return cb({
+                    status: "success",
+                    message: "Successfully created News",
+                });
+            }
+        );
+    }),
 
-	/**
-	 * Creates a news item
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Object} data - the object of the news data
-	 * @param {Function} cb - gets called with the result
-	 */
-	create: hooks.adminRequired((session, data, cb) => {
-		async.waterfall([
-			(next) => {
-				data.createdBy = session.userId;
-				data.createdAt = Date.now();
-				db.models.news.create(data, next);
-			}
-		], async (err, news) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("NEWS_CREATE", `Creating news failed. "${err}"`);
-				return cb({ 'status': 'failure', 'message': err });
-			}
-			cache.pub('news.create', news);
-			logger.success("NEWS_CREATE", `Creating news successful.`);
-			return cb({ 'status': 'success', 'message': 'Successfully created News' });
-		});
-	}),
+    /**
+     * Gets the latest news item
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Function} cb - gets called with the result
+     */
+    newest: async (session, cb) => {
+        const newsModel = await db.runJob("GET_MODEL", { modelName: "news" });
+        async.waterfall(
+            [
+                (next) => {
+                    newsModel
+                        .findOne({})
+                        .sort({ createdAt: "desc" })
+                        .exec(next);
+                },
+            ],
+            async (err, news) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "NEWS_NEWEST",
+                        `Getting the latest news failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "NEWS_NEWEST",
+                    `Successfully got the latest news.`,
+                    false
+                );
+                return cb({ status: "success", data: news });
+            }
+        );
+    },
 
-	/**
-	 * Gets the latest news item
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Function} cb - gets called with the result
-	 */
-	newest: (session, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.news.findOne({}).sort({ createdAt: 'desc' }).exec(next);
-			}
-		], async (err, news) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("NEWS_NEWEST", `Getting the latest news failed. "${err}"`);
-				return cb({ 'status': 'failure', 'message': err });
-			}
-			logger.success("NEWS_NEWEST", `Successfully got the latest news.`, false);
-			return cb({ status: 'success', data: news });
-		});
-	},
+    /**
+     * Removes a news item
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Object} news - the news object
+     * @param {Function} cb - gets called with the result
+     */
+    //TODO Pass in an id, not an object
+    //TODO Fix this
+    remove: hooks.adminRequired(async (session, news, cb) => {
+        const newsModel = await db.runJob("GET_MODEL", { modelName: "news" });
+        newsModel.deleteOne({ _id: news._id }, async (err) => {
+            if (err) {
+                err = await utils.runJob("GET_ERROR", { error: err });
+                console.log(
+                    "ERROR",
+                    "NEWS_REMOVE",
+                    `Removing news "${news._id}" failed for user "${session.userId}". "${err}"`
+                );
+                return cb({ status: "failure", message: err });
+            } else {
+                cache.runJob("PUB", { channel: "news.remove", value: news });
+                console.log(
+                    "SUCCESS",
+                    "NEWS_REMOVE",
+                    `Removing news "${news._id}" successful by user "${session.userId}".`
+                );
+                return cb({
+                    status: "success",
+                    message: "Successfully removed News",
+                });
+            }
+        });
+    }),
 
-	/**
-	 * Removes a news item
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Object} news - the news object
-	 * @param {Function} cb - gets called with the result
-	 */
-	//TODO Pass in an id, not an object
-	//TODO Fix this
-	remove: hooks.adminRequired((session, news, cb) => {
-		db.models.news.deleteOne({ _id: news._id }, async err => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("NEWS_REMOVE", `Removing news "${news._id}" failed for user "${session.userId}". "${err}"`);
-				return cb({ 'status': 'failure', 'message': err });
-			} else {
-				cache.pub('news.remove', news);
-				logger.success("NEWS_REMOVE", `Removing news "${news._id}" successful by user "${session.userId}".`);
-				return cb({ 'status': 'success', 'message': 'Successfully removed News' });
-			}
-		});
-	}),
-
-	/**
-	 * Removes a news item
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} _id - the news id
-	 * @param {Object} news - the news object
-	 * @param {Function} cb - gets called with the result
-	 */
-	//TODO Fix this
-	update: hooks.adminRequired((session, _id, news, cb) => {
-		db.models.news.updateOne({ _id }, news, { upsert: true }, async err => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("NEWS_UPDATE", `Updating news "${_id}" failed for user "${session.userId}". "${err}"`);
-				return cb({ 'status': 'failure', 'message': err });
-			} else {
-				cache.pub('news.update', news);
-				logger.success("NEWS_UPDATE", `Updating news "${_id}" successful for user "${session.userId}".`);
-				return cb({ 'status': 'success', 'message': 'Successfully updated News' });
-			}
-		});
-	}),
-
-};
+    /**
+     * Removes a news item
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} _id - the news id
+     * @param {Object} news - the news object
+     * @param {Function} cb - gets called with the result
+     */
+    //TODO Fix this
+    update: hooks.adminRequired(async (session, _id, news, cb) => {
+        const newsModel = await db.runJob("GET_MODEL", { modelName: "news" });
+        newsModel.updateOne({ _id }, news, { upsert: true }, async (err) => {
+            if (err) {
+                err = await utils.runJob("GET_ERROR", { error: err });
+                console.log(
+                    "ERROR",
+                    "NEWS_UPDATE",
+                    `Updating news "${_id}" failed for user "${session.userId}". "${err}"`
+                );
+                return cb({ status: "failure", message: err });
+            } else {
+                cache.runJob("PUB", { channel: "news.update", value: news });
+                console.log(
+                    "SUCCESS",
+                    "NEWS_UPDATE",
+                    `Updating news "${_id}" successful for user "${session.userId}".`
+                );
+                return cb({
+                    status: "success",
+                    message: "Successfully updated News",
+                });
+            }
+        });
+    }),
+};

+ 1149 - 523
backend/logic/actions/playlists.js

@@ -1,547 +1,1173 @@
-'use strict';
+"use strict";
 
-const async = require('async');
+const async = require("async");
 
-const hooks = require('./hooks');
+const hooks = require("./hooks");
 const moduleManager = require("../../index");
 
-const db = moduleManager.modules["db"];
-const cache = moduleManager.modules["cache"];
-const utils = moduleManager.modules["utils"];
-const logger = moduleManager.modules["logger"];
-const playlists = moduleManager.modules["playlists"];
-const songs = moduleManager.modules["songs"];
-
-cache.sub('playlist.create', playlistId => {
-	playlists.getPlaylist(playlistId, (err, playlist) => {
-		if (!err) {
-			utils.socketsFromUser(playlist.createdBy, (sockets) => {
-				sockets.forEach(socket => {
-					socket.emit('event:playlist.create', playlist);
-				});
-			});
-		}
-	});
+const db = require("../db");
+const cache = require("../cache");
+const utils = require("../utils");
+const playlists = require("../playlists");
+const songs = require("../songs");
+const activities = require("../activities");
+
+cache.runJob("SUB", {
+    channel: "playlist.create",
+    cb: (playlistId) => {
+        playlists.runJob("GET_PLAYLIST", { playlistId }).then((playlist) => {
+            utils
+                .runJob("SOCKETS_FROM_USER", { userId: playlist.createdBy })
+                .then((response) => {
+                    response.sockets.forEach((socket) => {
+                        socket.emit("event:playlist.create", playlist);
+                    });
+                });
+        });
+    },
 });
 
-cache.sub('playlist.delete', res => {
-	utils.socketsFromUser(res.userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:playlist.delete', res.playlistId);
-		});
-	});
+cache.runJob("SUB", {
+    channel: "playlist.delete",
+    cb: (res) => {
+        utils
+            .runJob("SOCKETS_FROM_USER", { userId: res.userId })
+            .then((response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:playlist.delete", res.playlistId);
+                });
+            });
+    },
 });
 
-cache.sub('playlist.moveSongToTop', res => {
-	utils.socketsFromUser(res.userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:playlist.moveSongToTop', {playlistId: res.playlistId, songId: res.songId});
-		});
-	});
+cache.runJob("SUB", {
+    channel: "playlist.moveSongToTop",
+    cb: (res) => {
+        utils
+            .runJob("SOCKETS_FROM_USER", { userId: res.userId })
+            .then((response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:playlist.moveSongToTop", {
+                        playlistId: res.playlistId,
+                        songId: res.songId,
+                    });
+                });
+            });
+    },
 });
 
-cache.sub('playlist.moveSongToBottom', res => {
-	utils.socketsFromUser(res.userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:playlist.moveSongToBottom', {playlistId: res.playlistId, songId: res.songId});
-		});
-	});
+cache.runJob("SUB", {
+    channel: "playlist.moveSongToBottom",
+    cb: (res) => {
+        utils
+            .runJob("SOCKETS_FROM_USER", { userId: res.userId })
+            .then((response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:playlist.moveSongToBottom", {
+                        playlistId: res.playlistId,
+                        songId: res.songId,
+                    });
+                });
+            });
+    },
 });
 
-cache.sub('playlist.addSong', res => {
-	utils.socketsFromUser(res.userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:playlist.addSong', { playlistId: res.playlistId, song: res.song });
-		});
-	});
+cache.runJob("SUB", {
+    channel: "playlist.addSong",
+    cb: (res) => {
+        utils
+            .runJob("SOCKETS_FROM_USER", { userId: res.userId })
+            .then((response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:playlist.addSong", {
+                        playlistId: res.playlistId,
+                        song: res.song,
+                    });
+                });
+            });
+    },
 });
 
-cache.sub('playlist.removeSong', res => {
-	utils.socketsFromUser(res.userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:playlist.removeSong', { playlistId: res.playlistId, songId: res.songId });
-		});
-	});
+cache.runJob("SUB", {
+    channel: "playlist.removeSong",
+    cb: (res) => {
+        utils
+            .runJob("SOCKETS_FROM_USER", { userId: res.userId })
+            .then((response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:playlist.removeSong", {
+                        playlistId: res.playlistId,
+                        songId: res.songId,
+                    });
+                });
+            });
+    },
 });
 
-cache.sub('playlist.updateDisplayName', res => {
-	utils.socketsFromUser(res.userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:playlist.updateDisplayName', { playlistId: res.playlistId, displayName: res.displayName });
-		});
-	});
+cache.runJob("SUB", {
+    channel: "playlist.updateDisplayName",
+    cb: (res) => {
+        utils
+            .runJob("SOCKETS_FROM_USER", { userId: res.userId })
+            .then((response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:playlist.updateDisplayName", {
+                        playlistId: res.playlistId,
+                        displayName: res.displayName,
+                    });
+                });
+            });
+    },
 });
 
 let lib = {
+    /**
+     * Gets the first song from a private playlist
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} playlistId - the id of the playlist we are getting the first song from
+     * @param {Function} cb - gets called with the result
+     */
+    getFirstSong: hooks.loginRequired((session, playlistId, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    playlists
+                        .runJob("GET_PLAYLIST", { playlistId })
+                        .then((playlist) => next(null, playlist))
+                        .catch(next);
+                },
+
+                (playlist, next) => {
+                    if (!playlist || playlist.createdBy !== session.userId)
+                        return next("Playlist not found.");
+                    next(null, playlist.songs[0]);
+                },
+            ],
+            async (err, song) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "PLAYLIST_GET_FIRST_SONG",
+                        `Getting the first song of playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "PLAYLIST_GET_FIRST_SONG",
+                    `Successfully got the first song of playlist "${playlistId}" for user "${session.userId}".`
+                );
+                cb({
+                    status: "success",
+                    song: song,
+                });
+            }
+        );
+    }),
+
+    /**
+     * Gets all playlists for the user requesting it
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Function} cb - gets called with the result
+     */
+    indexForUser: hooks.loginRequired(async (session, cb) => {
+        const playlistModel = await db.runJob("GET_MODEL", {
+            modelName: "playlist",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    playlistModel.find({ createdBy: session.userId }, next);
+                },
+            ],
+            async (err, playlists) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "PLAYLIST_INDEX_FOR_USER",
+                        `Indexing playlists for user "${session.userId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "PLAYLIST_INDEX_FOR_USER",
+                    `Successfully indexed playlists for user "${session.userId}".`
+                );
+                cb({
+                    status: "success",
+                    data: playlists,
+                });
+            }
+        );
+    }),
+
+    /**
+     * Creates a new private playlist
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Object} data - the data for the new private playlist
+     * @param {Function} cb - gets called with the result
+     */
+    create: hooks.loginRequired(async (session, data, cb) => {
+        const playlistModel = await db.runJob("GET_MODEL", {
+            modelName: "playlist",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    return data
+                        ? next()
+                        : cb({ status: "failure", message: "Invalid data" });
+                },
+
+                (next) => {
+                    const { displayName, songs } = data;
+                    playlistModel.create(
+                        {
+                            displayName,
+                            songs,
+                            createdBy: session.userId,
+                            createdAt: Date.now(),
+                        },
+                        next
+                    );
+                },
+            ],
+            async (err, playlist) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "PLAYLIST_CREATE",
+                        `Creating private playlist failed for user "${session.userId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                cache.runJob("PUB", {
+                    channel: "playlist.create",
+                    value: playlist._id,
+                });
+                activities.runJob("ADD_ACTIVITY", {
+                    userId: session.userId,
+                    activityType: "created_playlist",
+                    payload: [playlist._id],
+                });
+                console.log(
+                    "SUCCESS",
+                    "PLAYLIST_CREATE",
+                    `Successfully created private playlist for user "${session.userId}".`
+                );
+                cb({
+                    status: "success",
+                    message: "Successfully created playlist",
+                    data: {
+                        _id: playlist._id,
+                    },
+                });
+            }
+        );
+    }),
+
+    /**
+     * Gets a playlist from id
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} playlistId - the id of the playlist we are getting
+     * @param {Function} cb - gets called with the result
+     */
+    getPlaylist: hooks.loginRequired((session, playlistId, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    playlists
+                        .runJob("GET_PLAYLIST", { playlistId })
+                        .then((playlist) => next(null, playlist))
+                        .catch(next);
+                },
+
+                (playlist, next) => {
+                    if (!playlist || playlist.createdBy !== session.userId)
+                        return next("Playlist not found");
+                    next(null, playlist);
+                },
+            ],
+            async (err, playlist) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "PLAYLIST_GET",
+                        `Getting private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "PLAYLIST_GET",
+                    `Successfully got private playlist "${playlistId}" for user "${session.userId}".`
+                );
+                cb({
+                    status: "success",
+                    data: playlist,
+                });
+            }
+        );
+    }),
+
+    /**
+     * Obtains basic metadata of a playlist in order to format an activity
+     *
+     * @param session
+     * @param playlistId - the playlist id
+     * @param cb
+     */
+    getPlaylistForActivity: (session, playlistId, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    playlists
+                        .runJob("GET_PLAYLIST", { playlistId })
+                        .then((playlist) => next(null, playlist))
+                        .catch(next);
+                },
+            ],
+            async (err, playlist) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "PLAYLISTS_GET_PLAYLIST_FOR_ACTIVITY",
+                        `Failed to obtain metadata of playlist ${playlistId} for activity formatting. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "PLAYLISTS_GET_PLAYLIST_FOR_ACTIVITY",
+                        `Obtained metadata of playlist ${playlistId} for activity formatting successfully.`
+                    );
+                    cb({
+                        status: "success",
+                        data: {
+                            title: playlist.displayName,
+                        },
+                    });
+                }
+            }
+        );
+    },
+
+    //TODO Remove this
+    /**
+     * Updates a private playlist
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} playlistId - the id of the playlist we are updating
+     * @param {Object} playlist - the new private playlist object
+     * @param {Function} cb - gets called with the result
+     */
+    update: hooks.loginRequired(async (session, playlistId, playlist, cb) => {
+        const playlistModel = await db.runJob("GET_MODEL", {
+            modelName: "playlist",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    playlistModel.updateOne(
+                        { _id: playlistId, createdBy: session.userId },
+                        playlist,
+                        { runValidators: true },
+                        next
+                    );
+                },
+
+                (res, next) => {
+                    playlists
+                        .runJob("UPDATE_PLAYLIST", { playlistId })
+                        .then((playlist) => next(null, playlist))
+                        .catch(next);
+                },
+            ],
+            async (err, playlist) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "PLAYLIST_UPDATE",
+                        `Updating private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "PLAYLIST_UPDATE",
+                    `Successfully updated private playlist "${playlistId}" for user "${session.userId}".`
+                );
+                cb({
+                    status: "success",
+                    data: playlist,
+                });
+            }
+        );
+    }),
+
+    /**
+     * Updates a private playlist
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} playlistId - the id of the playlist we are updating
+     * @param {Function} cb - gets called with the result
+     */
+    shuffle: hooks.loginRequired(async (session, playlistId, cb) => {
+        const playlistModel = await db.runJob("GET_MODEL", {
+            modelName: "playlist",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    if (!playlistId) return next("No playlist id.");
+                    playlistModel.findById(playlistId, next);
+                },
+
+                (playlist, next) => {
+                    utils
+                        .runJob("SHUFFLE", { array: playlist.songs })
+                        .then((songs) => next(null, songs))
+                        .catch(next);
+                },
+
+                (songs, next) => {
+                    playlistModel.updateOne(
+                        { _id: playlistId },
+                        { $set: { songs } },
+                        { runValidators: true },
+                        next
+                    );
+                },
+
+                (res, next) => {
+                    playlists
+                        .runJob("UPDATE_PLAYLIST", { playlistId })
+                        .then((playlist) => next(null, playlist))
+                        .catch(next);
+                },
+            ],
+            async (err, playlist) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "PLAYLIST_SHUFFLE",
+                        `Updating private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "PLAYLIST_SHUFFLE",
+                    `Successfully updated private playlist "${playlistId}" for user "${session.userId}".`
+                );
+                cb({
+                    status: "success",
+                    message: "Successfully shuffled playlist.",
+                    data: playlist,
+                });
+            }
+        );
+    }),
+
+    /**
+     * Adds a song to a private playlist
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Boolean} isSet - is the song part of a set of songs to be added
+     * @param {String} songId - the id of the song we are trying to add
+     * @param {String} playlistId - the id of the playlist we are adding the song to
+     * @param {Function} cb - gets called with the result
+     */
+    addSongToPlaylist: hooks.loginRequired(
+        async (session, isSet, songId, playlistId, cb) => {
+            const playlistModel = await db.runJob("GET_MODEL", {
+                modelName: "playlist",
+            });
+
+            async.waterfall(
+                [
+                    (next) => {
+                        playlists
+                            .runJob("GET_PLAYLIST", { playlistId })
+                            .then((playlist) => {
+                                if (
+                                    !playlist ||
+                                    playlist.createdBy !== session.userId
+                                )
+                                    return next(
+                                        "Something went wrong when trying to get the playlist"
+                                    );
+
+                                async.each(
+                                    playlist.songs,
+                                    (song, next) => {
+                                        if (song.songId === songId)
+                                            return next(
+                                                "That song is already in the playlist"
+                                            );
+                                        next();
+                                    },
+                                    next
+                                );
+                            })
+                            .catch(next);
+                    },
+                    (next) => {
+                        songs
+                            .runJob("GET_SONG", { id: songId })
+                            .then((response) => {
+                                const song = response.song;
+                                next(null, {
+                                    _id: song._id,
+                                    songId: songId,
+                                    title: song.title,
+                                    duration: song.duration,
+                                });
+                            })
+                            .catch(() => {
+                                utils
+                                    .runJob("GET_SONG_FROM_YOUTUBE", { songId })
+                                    .then((response) =>
+                                        next(null, response.song)
+                                    )
+                                    .catch(next);
+                            });
+                    },
+                    (newSong, next) => {
+                        playlistModel.updateOne(
+                            { _id: playlistId },
+                            { $push: { songs: newSong } },
+                            { runValidators: true },
+                            (err) => {
+                                if (err) return next(err);
+                                playlists
+                                    .runJob("UPDATE_PLAYLIST", { playlistId })
+                                    .then((playlist) =>
+                                        next(null, playlist, newSong)
+                                    )
+                                    .catch(next);
+                            }
+                        );
+                    },
+                ],
+                async (err, playlist, newSong) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "PLAYLIST_ADD_SONG",
+                            `Adding song "${songId}" to private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    } else {
+                        console.log(
+                            "SUCCESS",
+                            "PLAYLIST_ADD_SONG",
+                            `Successfully added song "${songId}" to private playlist "${playlistId}" for user "${session.userId}".`
+                        );
+                        if (!isSet)
+                            activities.runJob("ADD_ACTIVITY", {
+                                userId: session.userId,
+                                activityType: "added_song_to_playlist",
+                                payload: [{ songId, playlistId }],
+                            });
+
+                        cache.runJob("PUB", {
+                            channel: "playlist.addSong",
+                            value: {
+                                playlistId: playlist._id,
+                                song: newSong,
+                                userId: session.userId,
+                            },
+                        });
+                        return cb({
+                            status: "success",
+                            message:
+                                "Song has been successfully added to the playlist",
+                            data: playlist.songs,
+                        });
+                    }
+                }
+            );
+        }
+    ),
+
+    /**
+     * Adds a set of songs to a private playlist
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} url - the url of the the YouTube playlist
+     * @param {String} playlistId - the id of the playlist we are adding the set of songs to
+     * @param {Boolean} musicOnly - whether to only add music to the playlist
+     * @param {Function} cb - gets called with the result
+     */
+    addSetToPlaylist: hooks.loginRequired(
+        (session, url, playlistId, musicOnly, cb) => {
+            let videosInPlaylistTotal = 0;
+            let songsInPlaylistTotal = 0;
+            let songsSuccess = 0;
+            let songsFail = 0;
+
+            let addedSongs = [];
+
+            async.waterfall(
+                [
+                    (next) => {
+                        utils
+                            .runJob("GET_PLAYLIST_FROM_YOUTUBE", {
+                                url,
+                                musicOnly,
+                            })
+                            .then((response) => {
+                                if (response.filteredSongs) {
+                                    videosInPlaylistTotal =
+                                        response.songs.length;
+                                    songsInPlaylistTotal =
+                                        response.filteredSongs.length;
+                                } else {
+                                    songsInPlaylistTotal = videosInPlaylistTotal =
+                                        response.songs.length;
+                                }
+                                next(null, response.songs);
+                            });
+                    },
+                    (songIds, next) => {
+                        let processed = 0;
+                        function checkDone() {
+                            if (processed === songIds.length) next();
+                        }
+                        for (let s = 0; s < songIds.length; s++) {
+                            lib.addSongToPlaylist(
+                                session,
+                                true,
+                                songIds[s],
+                                playlistId,
+                                (res) => {
+                                    processed++;
+                                    if (res.status === "success") {
+                                        addedSongs.push(songIds[s]);
+                                        songsSuccess++;
+                                    } else songsFail++;
+                                    checkDone();
+                                }
+                            );
+                        }
+                    },
+
+                    (next) => {
+                        playlists
+                            .runJob("GET_PLAYLIST", { playlistId })
+                            .then((playlist) => next(null, playlist))
+                            .catch(next);
+                    },
+
+                    (playlist, next) => {
+                        if (!playlist || playlist.createdBy !== session.userId)
+                            return next("Playlist not found.");
+                        next(null, playlist);
+                    },
+                ],
+                async (err, playlist) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "PLAYLIST_IMPORT",
+                            `Importing a YouTube playlist to private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    } else {
+                        activities.runJob("ADD_ACTIVITY", {
+                            userId: session.userId,
+                            activityType: "added_songs_to_playlist",
+                            payload: addedSongs,
+                        });
+                        console.log(
+                            "SUCCESS",
+                            "PLAYLIST_IMPORT",
+                            `Successfully imported a YouTube playlist to private playlist "${playlistId}" for user "${session.userId}". Videos in playlist: ${videosInPlaylistTotal}, songs in playlist: ${songsInPlaylistTotal}, songs successfully added: ${songsSuccess}, songs failed: ${songsFail}.`
+                        );
+                        cb({
+                            status: "success",
+                            message: "Playlist has been successfully imported.",
+                            data: playlist.songs,
+                            stats: {
+                                videosInPlaylistTotal,
+                                songsInPlaylistTotal,
+                                songsAddedSuccessfully: songsSuccess,
+                                songsFailedToAdd: songsFail,
+                            },
+                        });
+                    }
+                }
+            );
+        }
+    ),
+
+    /**
+     * Removes a song from a private playlist
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} songId - the id of the song we are removing from the private playlist
+     * @param {String} playlistId - the id of the playlist we are removing the song from
+     * @param {Function} cb - gets called with the result
+     */
+    removeSongFromPlaylist: hooks.loginRequired(
+        async (session, songId, playlistId, cb) => {
+            const playlistModel = await db.runJob("GET_MODEL", {
+                modelName: "playlist",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        if (!songId || typeof songId !== "string")
+                            return next("Invalid song id.");
+                        if (!playlistId || typeof playlistId !== "string")
+                            return next("Invalid playlist id.");
+                        next();
+                    },
+
+                    (next) => {
+                        playlists
+                            .runJob("GET_PLAYLIST", { playlistId })
+                            .then((playlist) => next(null, playlist))
+                            .catch(next);
+                    },
+
+                    (playlist, next) => {
+                        if (!playlist || playlist.createdBy !== session.userId)
+                            return next("Playlist not found");
+                        playlistModel.updateOne(
+                            { _id: playlistId },
+                            { $pull: { songs: { songId: songId } } },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        playlists
+                            .runJob("UPDATE_PLAYLIST", { playlistId })
+                            .then((playlist) => next(null, playlist))
+                            .catch(next);
+                    },
+                ],
+                async (err, playlist) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "PLAYLIST_REMOVE_SONG",
+                            `Removing song "${songId}" from private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    } else {
+                        console.log(
+                            "SUCCESS",
+                            "PLAYLIST_REMOVE_SONG",
+                            `Successfully removed song "${songId}" from private playlist "${playlistId}" for user "${session.userId}".`
+                        );
+                        cache.runJob("PUB", {
+                            channel: "playlist.removeSong",
+                            value: {
+                                playlistId: playlist._id,
+                                songId: songId,
+                                userId: session.userId,
+                            },
+                        });
+                        return cb({
+                            status: "success",
+                            message:
+                                "Song has been successfully removed from playlist",
+                            data: playlist.songs,
+                        });
+                    }
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates the displayName of a private playlist
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} playlistId - the id of the playlist we are updating the displayName for
+     * @param {Function} cb - gets called with the result
+     */
+    updateDisplayName: hooks.loginRequired(
+        async (session, playlistId, displayName, cb) => {
+            const playlistModel = await db.runJob("GET_MODEL", {
+                modelName: "playlist",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        playlistModel.updateOne(
+                            { _id: playlistId, createdBy: session.userId },
+                            { $set: { displayName } },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        playlists
+                            .runJob("UPDATE_PLAYLIST", { playlistId })
+                            .then((playlist) => next(null, playlist))
+                            .catch(next);
+                    },
+                ],
+                async (err, playlist) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "PLAYLIST_UPDATE_DISPLAY_NAME",
+                            `Updating display name to "${displayName}" for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    }
+                    console.log(
+                        "SUCCESS",
+                        "PLAYLIST_UPDATE_DISPLAY_NAME",
+                        `Successfully updated display name to "${displayName}" for private playlist "${playlistId}" for user "${session.userId}".`
+                    );
+                    cache.runJob("PUB", {
+                        channel: "playlist.updateDisplayName",
+                        value: {
+                            playlistId: playlistId,
+                            displayName: displayName,
+                            userId: session.userId,
+                        },
+                    });
+                    return cb({
+                        status: "success",
+                        message: "Playlist has been successfully updated",
+                    });
+                }
+            );
+        }
+    ),
+
+    /**
+     * Moves a song to the top of the list in a private playlist
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} playlistId - the id of the playlist we are moving the song to the top from
+     * @param {String} songId - the id of the song we are moving to the top of the list
+     * @param {Function} cb - gets called with the result
+     */
+    moveSongToTop: hooks.loginRequired(
+        async (session, playlistId, songId, cb) => {
+            const playlistModel = await db.runJob("GET_MODEL", {
+                modelName: "playlist",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        playlists
+                            .runJob("GET_PLAYLIST", { playlistId })
+                            .then((playlist) => next(null, playlist))
+                            .catch(next);
+                    },
+
+                    (playlist, next) => {
+                        if (!playlist || playlist.createdBy !== session.userId)
+                            return next("Playlist not found");
+                        async.each(
+                            playlist.songs,
+                            (song, next) => {
+                                if (song.songId === songId) return next(song);
+                                next();
+                            },
+                            (err) => {
+                                if (err && err.songId) return next(null, err);
+                                next("Song not found");
+                            }
+                        );
+                    },
+
+                    (song, next) => {
+                        playlistModel.updateOne(
+                            { _id: playlistId },
+                            { $pull: { songs: { songId } } },
+                            (err) => {
+                                if (err) return next(err);
+                                return next(null, song);
+                            }
+                        );
+                    },
+
+                    (song, next) => {
+                        playlistModel.updateOne(
+                            { _id: playlistId },
+                            {
+                                $push: {
+                                    songs: {
+                                        $each: [song],
+                                        $position: 0,
+                                    },
+                                },
+                            },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        playlists
+                            .runJob("UPDATE_PLAYLIST", { playlistId })
+                            .then((playlist) => next(null, playlist))
+                            .catch(next);
+                    },
+                ],
+                async (err, playlist) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "PLAYLIST_MOVE_SONG_TO_TOP",
+                            `Moving song "${songId}" to the top for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    }
+                    console.log(
+                        "SUCCESS",
+                        "PLAYLIST_MOVE_SONG_TO_TOP",
+                        `Successfully moved song "${songId}" to the top for private playlist "${playlistId}" for user "${session.userId}".`
+                    );
+                    cache.runJob("PUB", {
+                        channel: "playlist.moveSongToTop",
+                        value: {
+                            playlistId,
+                            songId,
+                            userId: session.userId,
+                        },
+                    });
+                    return cb({
+                        status: "success",
+                        message: "Playlist has been successfully updated",
+                    });
+                }
+            );
+        }
+    ),
+
+    /**
+     * Moves a song to the bottom of the list in a private playlist
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} playlistId - the id of the playlist we are moving the song to the bottom from
+     * @param {String} songId - the id of the song we are moving to the bottom of the list
+     * @param {Function} cb - gets called with the result
+     */
+    moveSongToBottom: hooks.loginRequired(
+        async (session, playlistId, songId, cb) => {
+            const playlistModel = await db.runJob("GET_MODEL", {
+                modelName: "playlist",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        playlists
+                            .runJob("GET_PLAYLIST", { playlistId })
+                            .then((playlist) => next(null, playlist))
+                            .catch(next);
+                    },
+
+                    (playlist, next) => {
+                        if (!playlist || playlist.createdBy !== session.userId)
+                            return next("Playlist not found");
+                        async.each(
+                            playlist.songs,
+                            (song, next) => {
+                                if (song.songId === songId) return next(song);
+                                next();
+                            },
+                            (err) => {
+                                if (err && err.songId) return next(null, err);
+                                next("Song not found");
+                            }
+                        );
+                    },
+
+                    (song, next) => {
+                        playlistModel.updateOne(
+                            { _id: playlistId },
+                            { $pull: { songs: { songId } } },
+                            (err) => {
+                                if (err) return next(err);
+                                return next(null, song);
+                            }
+                        );
+                    },
+
+                    (song, next) => {
+                        playlistModel.updateOne(
+                            { _id: playlistId },
+                            {
+                                $push: {
+                                    songs: song,
+                                },
+                            },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        playlists
+                            .runJob("UPDATE_PLAYLIST", { playlistId })
+                            .then((playlist) => next(null, playlist))
+                            .catch(next);
+                    },
+                ],
+                async (err, playlist) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "PLAYLIST_MOVE_SONG_TO_BOTTOM",
+                            `Moving song "${songId}" to the bottom for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    }
+                    console.log(
+                        "SUCCESS",
+                        "PLAYLIST_MOVE_SONG_TO_BOTTOM",
+                        `Successfully moved song "${songId}" to the bottom for private playlist "${playlistId}" for user "${session.userId}".`
+                    );
+                    cache.runJob("PUB", {
+                        channel: "playlist.moveSongToBottom",
+                        value: {
+                            playlistId,
+                            songId,
+                            userId: session.userId,
+                        },
+                    });
+                    return cb({
+                        status: "success",
+                        message: "Playlist has been successfully updated",
+                    });
+                }
+            );
+        }
+    ),
+
+    /**
+     * Removes a private playlist
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} playlistId - the id of the playlist we are moving the song to the top from
+     * @param {Function} cb - gets called with the result
+     */
+    remove: hooks.loginRequired(async (session, playlistId, cb) => {
+        const stationModel = await db.runJob("GET_MODEL", {
+            modelName: "station",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    playlists
+                        .runJob("DELETE_PLAYLIST", { playlistId })
+                        .then((playlist) => next(null, playlist))
+                        .catch(next);
+                },
+
+                (next) => {
+                    stationModel.find({ privatePlaylist: playlistId }, next);
+                },
 
-	/**
-	 * Gets the first song from a private playlist
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} playlistId - the id of the playlist we are getting the first song from
-	 * @param {Function} cb - gets called with the result
-	 */
-	getFirstSong: hooks.loginRequired((session, playlistId, cb) => {
-		async.waterfall([
-			(next) => {
-				playlists.getPlaylist(playlistId, next);
-			},
-
-			(playlist, next) => {
-				if (!playlist || playlist.createdBy !== session.userId) return next('Playlist not found.');
-				next(null, playlist.songs[0]);
-			}
-		], async (err, song) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PLAYLIST_GET_FIRST_SONG", `Getting the first song of playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
-				return cb({ status: 'failure', message: err});
-			}
-			logger.success("PLAYLIST_GET_FIRST_SONG", `Successfully got the first song of playlist "${playlistId}" for user "${session.userId}".`);
-			cb({
-				status: 'success',
-				song: song
-			});
-		});
-	}),
-
-	/**
-	 * Gets all playlists for the user requesting it
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Function} cb - gets called with the result
-	 */
-	indexForUser: hooks.loginRequired((session, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.playlist.find({ createdBy: session.userId }, next);
-			}
-		], async (err, playlists) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PLAYLIST_INDEX_FOR_USER", `Indexing playlists for user "${session.userId}" failed. "${err}"`);
-				return cb({ status: 'failure', message: err});
-			}
-			logger.success("PLAYLIST_INDEX_FOR_USER", `Successfully indexed playlists for user "${session.userId}".`);
-			cb({
-				status: 'success',
-				data: playlists
-			});
-		});
-	}),
-
-	/**
-	 * Creates a new private playlist
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Object} data - the data for the new private playlist
-	 * @param {Function} cb - gets called with the result
-	 */
-	create: hooks.loginRequired((session, data, cb) => {
-		async.waterfall([
-
-			(next) => {
-				return (data) ? next() : cb({ 'status': 'failure', 'message': 'Invalid data' });
-			},
-
-			(next) => {
-				const { displayName, songs } = data;
-				db.models.playlist.create({
-					displayName,
-					songs,
-					createdBy: session.userId,
-					createdAt: Date.now()
-				}, next);
-			}
-
-		], async (err, playlist) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PLAYLIST_CREATE", `Creating private playlist failed for user "${session.userId}". "${err}"`);
-				return cb({ status: 'failure', message: err});
-			}
-			cache.pub('playlist.create', playlist._id);
-			logger.success("PLAYLIST_CREATE", `Successfully created private playlist for user "${session.userId}".`);
-			cb({ status: 'success', message: 'Successfully created playlist', data: {
-				_id: playlist._id
-			} });
-		});
-	}),
-
-	/**
-	 * Gets a playlist from id
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} playlistId - the id of the playlist we are getting
-	 * @param {Function} cb - gets called with the result
-	 */
-	getPlaylist: hooks.loginRequired((session, playlistId, cb) => {
-		async.waterfall([
-			(next) => {
-				playlists.getPlaylist(playlistId, next);
-			},
-
-			(playlist, next) => {
-				if (!playlist || playlist.createdBy !== session.userId) return next('Playlist not found');
-				next(null, playlist);
-			}
-		], async (err, playlist) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PLAYLIST_GET", `Getting private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
-				return cb({ status: 'failure', message: err});
-			}
-			logger.success("PLAYLIST_GET", `Successfully got private playlist "${playlistId}" for user "${session.userId}".`);
-			cb({
-				status: 'success',
-				data: playlist
-			});
-		});
-	}),
-
-	//TODO Remove this
-	/**
-	 * Updates a private playlist
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} playlistId - the id of the playlist we are updating
-	 * @param {Object} playlist - the new private playlist object
-	 * @param {Function} cb - gets called with the result
-	 */
-	update: hooks.loginRequired((session, playlistId, playlist, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.playlist.updateOne({ _id: playlistId, createdBy: session.userId }, playlist, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				playlists.updatePlaylist(playlistId, next)
-			}
-		], async (err, playlist) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PLAYLIST_UPDATE", `Updating private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
-				return cb({ status: 'failure', message: err});
-			}
-			logger.success("PLAYLIST_UPDATE", `Successfully updated private playlist "${playlistId}" for user "${session.userId}".`);
-			cb({
-				status: 'success',
-				data: playlist
-			});
-		});
-	}),
-
-	/**
-	 * Adds a song to a private playlist
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} songId - the id of the song we are trying to add
-	 * @param {String} playlistId - the id of the playlist we are adding the song to
-	 * @param {Function} cb - gets called with the result
-	 */
-	addSongToPlaylist: hooks.loginRequired((session, songId, playlistId, cb) => {
-		async.waterfall([
-			(next) => {
-				playlists.getPlaylist(playlistId, (err, playlist) => {
-					if (err || !playlist || playlist.createdBy !== session.userId) return next('Something went wrong when trying to get the playlist');
-
-					async.each(playlist.songs, (song, next) => {
-						if (song.songId === songId) return next('That song is already in the playlist');
-						next();
-					}, next);
-				});
-			},
-			(next) => {
-				songs.getSong(songId, (err, song) => {
-					if (err) {
-						utils.getSongFromYouTube(songId, (song) => {
-							next(null, song);
-						});
-					} else {
-						next(null, {
-							_id: song._id,
-							songId: songId,
-							title: song.title,
-							duration: song.duration
-						});
-					}
-				});
-			},
-			(newSong, next) => {
-				db.models.playlist.updateOne({_id: playlistId}, {$push: {songs: newSong}}, {runValidators: true}, (err) => {
-					if (err) return next(err);
-					playlists.updatePlaylist(playlistId, (err, playlist) => {
-						next(err, playlist, newSong);
-					});
-				});
-			}
-		],
-		async (err, playlist, newSong) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PLAYLIST_ADD_SONG", `Adding song "${songId}" to private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
-				return cb({ status: 'failure', message: err});
-			} else {
-				logger.success("PLAYLIST_ADD_SONG", `Successfully added song "${songId}" to private playlist "${playlistId}" for user "${session.userId}".`);
-				cache.pub('playlist.addSong', { playlistId: playlist._id, song: newSong, userId: session.userId });
-				return cb({ status: 'success', message: 'Song has been successfully added to the playlist', data: playlist.songs });
-			}
-		});
-	}),
-
-	/**
-	 * Adds a set of songs to a private playlist
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} url - the url of the the YouTube playlist
-	 * @param {String} playlistId - the id of the playlist we are adding the set of songs to
-	 * @param {Function} cb - gets called with the result
-	 */
-	addSetToPlaylist: hooks.loginRequired((session, url, playlistId, cb) => {
-		async.waterfall([
-			(next) => {
-				utils.getPlaylistFromYouTube(url, songs => {
-					next(null, songs);
-				});
-			},
-			(songs, next) => {
-				let processed = 0;
-				function checkDone() {
-					if (processed === songs.length) next();
-				}
-				for (let s = 0; s < songs.length; s++) {
-					lib.addSongToPlaylist(session, songs[s].contentDetails.videoId, playlistId, () => {
-						processed++;
-						checkDone();
-					});
-				}
-			},
-			(next) => {
-				playlists.getPlaylist(playlistId, next);
-			},
-
-			(playlist, next) => {
-				if (!playlist || playlist.createdBy !== session.userId) return next('Playlist not found.');
-				next(null, playlist);
-			}
-		], async (err, playlist) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PLAYLIST_IMPORT", `Importing a YouTube playlist to private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
-				return cb({ status: 'failure', message: err});
-			} else {
-				logger.success("PLAYLIST_IMPORT", `Successfully imported a YouTube playlist to private playlist "${playlistId}" for user "${session.userId}".`);
-				cb({ status: 'success', message: 'Playlist has been successfully imported.', data: playlist.songs });
-			}
-		});
-	}),
-
-	/**
-	 * Removes a song from a private playlist
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} songId - the id of the song we are removing from the private playlist
-	 * @param {String} playlistId - the id of the playlist we are removing the song from
-	 * @param {Function} cb - gets called with the result
-	 */
-	removeSongFromPlaylist: hooks.loginRequired((session, songId, playlistId, cb) => {
-		async.waterfall([
-			(next) => {
-				if (!songId || typeof songId !== 'string') return next('Invalid song id.');
-				if (!playlistId  || typeof playlistId !== 'string') return next('Invalid playlist id.');
-				next();
-			},
-
-			(next) => {
-				playlists.getPlaylist(playlistId, next);
-			},
-
-			(playlist, next) => {
-				if (!playlist || playlist.createdBy !== session.userId) return next('Playlist not found');
-				db.models.playlist.updateOne({_id: playlistId}, {$pull: {songs: {songId: songId}}}, next);
-			},
-
-			(res, next) => {
-				playlists.updatePlaylist(playlistId, next);
-			}
-		], async (err, playlist) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PLAYLIST_REMOVE_SONG", `Removing song "${songId}" from private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
-				return cb({ status: 'failure', message: err});
-			} else {
-				logger.success("PLAYLIST_REMOVE_SONG", `Successfully removed song "${songId}" from private playlist "${playlistId}" for user "${session.userId}".`);
-				cache.pub('playlist.removeSong', { playlistId: playlist._id, songId: songId, userId: session.userId });
-				return cb({ status: 'success', message: 'Song has been successfully removed from playlist', data: playlist.songs });
-			}
-		});
-	}),
-
-	/**
-	 * Updates the displayName of a private playlist
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} playlistId - the id of the playlist we are updating the displayName for
-	 * @param {Function} cb - gets called with the result
-	 */
-	updateDisplayName: hooks.loginRequired((session, playlistId, displayName, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.playlist.updateOne({ _id: playlistId, createdBy: session.userId }, { $set: { displayName } }, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				playlists.updatePlaylist(playlistId, next);
-			}
-		], async (err, playlist) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PLAYLIST_UPDATE_DISPLAY_NAME", `Updating display name to "${displayName}" for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
-				return cb({ status: 'failure', message: err});
-			}
-			logger.success("PLAYLIST_UPDATE_DISPLAY_NAME", `Successfully updated display name to "${displayName}" for private playlist "${playlistId}" for user "${session.userId}".`);
-			cache.pub('playlist.updateDisplayName', {playlistId: playlistId, displayName: displayName, userId: session.userId});
-			return cb({ status: 'success', message: 'Playlist has been successfully updated' });
-		});
-	}),
-
-	/**
-	 * Moves a song to the top of the list in a private playlist
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} playlistId - the id of the playlist we are moving the song to the top from
-	 * @param {String} songId - the id of the song we are moving to the top of the list
-	 * @param {Function} cb - gets called with the result
-	 */
-	moveSongToTop: hooks.loginRequired((session, playlistId, songId, cb) => {
-		async.waterfall([
-			(next) => {
-				playlists.getPlaylist(playlistId, next);
-			},
-
-			(playlist, next) => {
-				if (!playlist || playlist.createdBy !== session.userId) return next('Playlist not found');
-				async.each(playlist.songs, (song, next) => {
-					if (song.songId === songId) return next(song);
-					next();
-				}, (err) => {
-					if (err && err.songId) return next(null, err);
-					next('Song not found');
-				});
-			},
-
-			(song, next) => {
-				db.models.playlist.updateOne({_id: playlistId}, {$pull: {songs: {songId}}}, (err) => {
-					if (err) return next(err);
-					return next(null, song);
-				});
-			},
-
-			(song, next) => {
-				db.models.playlist.updateOne({_id: playlistId}, {
-					$push: {
-						songs: {
-							$each: [song],
-							$position: 0
-						}
-					}
-				}, next);
-			},
-
-			(res, next) => {
-				playlists.updatePlaylist(playlistId, next);
-			}
-		], async (err, playlist) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PLAYLIST_MOVE_SONG_TO_TOP", `Moving song "${songId}" to the top for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
-				return cb({ status: 'failure', message: err});
-			}
-			logger.success("PLAYLIST_MOVE_SONG_TO_TOP", `Successfully moved song "${songId}" to the top for private playlist "${playlistId}" for user "${session.userId}".`);
-			cache.pub('playlist.moveSongToTop', {playlistId, songId, userId: session.userId});
-			return cb({ status: 'success', message: 'Playlist has been successfully updated' });
-		});
-	}),
-
-	/**
-	 * Moves a song to the bottom of the list in a private playlist
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} playlistId - the id of the playlist we are moving the song to the bottom from
-	 * @param {String} songId - the id of the song we are moving to the bottom of the list
-	 * @param {Function} cb - gets called with the result
-	 */
-	moveSongToBottom: hooks.loginRequired((session, playlistId, songId, cb) => {
-		async.waterfall([
-			(next) => {
-				playlists.getPlaylist(playlistId, next);
-			},
-
-			(playlist, next) => {
-				if (!playlist || playlist.createdBy !== session.userId) return next('Playlist not found');
-				async.each(playlist.songs, (song, next) => {
-					if (song.songId === songId) return next(song);
-					next();
-				}, (err) => {
-					if (err && err.songId) return next(null, err);
-					next('Song not found');
-				});
-			},
-
-			(song, next) => {
-				db.models.playlist.updateOne({_id: playlistId}, {$pull: {songs: {songId}}}, (err) => {
-					if (err) return next(err);
-					return next(null, song);
-				});
-			},
-
-			(song, next) => {
-				db.models.playlist.updateOne({_id: playlistId}, {
-					$push: {
-						songs: song
-					}
-				}, next);
-			},
-
-			(res, next) => {
-				playlists.updatePlaylist(playlistId, next);
-			}
-		], async (err, playlist) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PLAYLIST_MOVE_SONG_TO_BOTTOM", `Moving song "${songId}" to the bottom for private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
-				return cb({ status: 'failure', message: err});
-			}
-			logger.success("PLAYLIST_MOVE_SONG_TO_BOTTOM", `Successfully moved song "${songId}" to the bottom for private playlist "${playlistId}" for user "${session.userId}".`);
-			cache.pub('playlist.moveSongToBottom', {playlistId, songId, userId: session.userId});
-			return cb({ status: 'success', message: 'Playlist has been successfully updated' });
-		});
-	}),
-
-	/**
-	 * Removes a private playlist
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} playlistId - the id of the playlist we are moving the song to the top from
-	 * @param {Function} cb - gets called with the result
-	 */
-	remove: hooks.loginRequired((session, playlistId, cb) => {
-		async.waterfall([
-			(next) => {
-				playlists.deletePlaylist(playlistId, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PLAYLIST_REMOVE", `Removing private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`);
-				return cb({ status: 'failure', message: err});
-			}
-			logger.success("PLAYLIST_REMOVE", `Successfully removed private playlist "${playlistId}" for user "${session.userId}".`);
-			cache.pub('playlist.delete', {userId: session.userId, playlistId});
-			return cb({ status: 'success', message: 'Playlist successfully removed' });
-		});
-	})
+                (stations, next) => {
+                    async.each(
+                        stations,
+                        (station, next) => {
+                            async.waterfall(
+                                [
+                                    (next) => {
+                                        stationModel.updateOne(
+                                            { _id: station._id },
+                                            { $set: { privatePlaylist: null } },
+                                            { runValidators: true },
+                                            next
+                                        );
+                                    },
 
+                                    (res, next) => {
+                                        if (!station.partyMode) {
+                                            moduleManager.modules["stations"]
+                                                .runJob("UPDATE_STATION", {
+                                                    stationId: station._id,
+                                                })
+                                                .then((station) =>
+                                                    next(null, station)
+                                                )
+                                                .catch(next);
+                                            cache.runJob("PUB", {
+                                                channel:
+                                                    "privatePlaylist.selected",
+                                                value: {
+                                                    playlistId: null,
+                                                    stationId: station._id,
+                                                },
+                                            });
+                                        } else next();
+                                    },
+                                ],
+                                (err) => {
+                                    next();
+                                }
+                            );
+                        },
+                        (err) => {
+                            next();
+                        }
+                    );
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "PLAYLIST_REMOVE",
+                        `Removing private playlist "${playlistId}" failed for user "${session.userId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "PLAYLIST_REMOVE",
+                    `Successfully removed private playlist "${playlistId}" for user "${session.userId}".`
+                );
+                cache.runJob("PUB", {
+                    channel: "playlist.delete",
+                    value: {
+                        userId: session.userId,
+                        playlistId,
+                    },
+                });
+                activities.runJob("ADD_ACTIVITY", {
+                    userId: session.userId,
+                    activityType: "deleted_playlist",
+                    payload: [playlistId],
+                });
+                return cb({
+                    status: "success",
+                    message: "Playlist successfully removed",
+                });
+            }
+        );
+    }),
 };
 
 module.exports = lib;

+ 154 - 108
backend/logic/actions/punishments.js

@@ -1,120 +1,166 @@
-'use strict';
+"use strict";
 
-const async = require('async');
+const async = require("async");
 
-const hooks = require('./hooks');
-const moduleManager = require("../../index");
+const hooks = require("./hooks");
+// const moduleManager = require("../../index");
 
-const logger = moduleManager.modules["logger"];
-const utils = moduleManager.modules["utils"];
-const cache = moduleManager.modules["cache"];
-const db = moduleManager.modules["db"];
-const punishments = moduleManager.modules["punishments"];
+// const logger = require("logger");
+const utils = require("../utils");
+const cache = require("../cache");
+const db = require("../db");
+const punishments = require("../punishments");
 
-cache.sub('ip.ban', data => {
-	utils.emitToRoom('admin.punishments', 'event:admin.punishment.added', data.punishment);
-	utils.socketsFromIP(data.ip, sockets => {
-		sockets.forEach(socket => {
-			socket.disconnect(true);
-		});
-	});
+cache.runJob("SUB", {
+    channel: "ip.ban",
+    cb: (data) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: "admin.punishments",
+            args: ["event:admin.punishment.added", data.punishment],
+        });
+        utils.runJob("SOCKETS_FROM_IP", { ip: data.ip }).then((sockets) => {
+            sockets.forEach((socket) => {
+                socket.disconnect(true);
+            });
+        });
+    },
 });
 
 module.exports = {
+    /**
+     * Gets all punishments
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Function} cb - gets called with the result
+     */
+    index: hooks.adminRequired(async (session, cb) => {
+        const punishmentModel = await db.runJob("GET_MODEL", {
+            modelName: "punishment",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    punishmentModel.find({}, next);
+                },
+            ],
+            async (err, punishments) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "PUNISHMENTS_INDEX",
+                        `Indexing punishments failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "PUNISHMENTS_INDEX",
+                    "Indexing punishments successful."
+                );
+                cb({ status: "success", data: punishments });
+            }
+        );
+    }),
 
-	/**
-	 * Gets all punishments
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Function} cb - gets called with the result
-	 */
-	index: hooks.adminRequired((session, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.punishment.find({}, next);
-			}
-		], async (err, punishments) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("PUNISHMENTS_INDEX", `Indexing punishments failed. "${err}"`);
-				return cb({ 'status': 'failure', 'message': err});
-			}
-			logger.success("PUNISHMENTS_INDEX", "Indexing punishments successful.");
-			cb({ status: 'success', data: punishments });
-		});
-	}),
+    /**
+     * Bans an IP address
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} value - the ip address that is going to be banned
+     * @param {String} reason - the reason for the ban
+     * @param {String} expiresAt - the time the ban expires
+     * @param {Function} cb - gets called with the result
+     */
+    banIP: hooks.adminRequired((session, value, reason, expiresAt, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    if (!value)
+                        return next("You must provide an IP address to ban.");
+                    else if (!reason)
+                        return next("You must provide a reason for the ban.");
+                    else return next();
+                },
 
-	/**
-	 * Bans an IP address
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} value - the ip address that is going to be banned
-	 * @param {String} reason - the reason for the ban
-	 * @param {String} expiresAt - the time the ban expires
-	 * @param {Function} cb - gets called with the result
-	 */
-	banIP: hooks.adminRequired((session, value, reason, expiresAt, cb) => {
-		async.waterfall([
-			(next) => {
-				if (!value) return next('You must provide an IP address to ban.');
-				else if (!reason) return next('You must provide a reason for the ban.');
-				else return next();
-			},
+                (next) => {
+                    if (!expiresAt || typeof expiresAt !== "string")
+                        return next("Invalid expire date.");
+                    let date = new Date();
+                    switch (expiresAt) {
+                        case "1h":
+                            expiresAt = date.setHours(date.getHours() + 1);
+                            break;
+                        case "12h":
+                            expiresAt = date.setHours(date.getHours() + 12);
+                            break;
+                        case "1d":
+                            expiresAt = date.setDate(date.getDate() + 1);
+                            break;
+                        case "1w":
+                            expiresAt = date.setDate(date.getDate() + 7);
+                            break;
+                        case "1m":
+                            expiresAt = date.setMonth(date.getMonth() + 1);
+                            break;
+                        case "3m":
+                            expiresAt = date.setMonth(date.getMonth() + 3);
+                            break;
+                        case "6m":
+                            expiresAt = date.setMonth(date.getMonth() + 6);
+                            break;
+                        case "1y":
+                            expiresAt = date.setFullYear(
+                                date.getFullYear() + 1
+                            );
+                            break;
+                        case "never":
+                            expiresAt = new Date(3093527980800000);
+                            break;
+                        default:
+                            return next("Invalid expire date.");
+                    }
 
-			(next) => {
-				if (!expiresAt || typeof expiresAt !== 'string') return next('Invalid expire date.');
-				let date = new Date();
-				switch(expiresAt) {
-					case '1h':
-						expiresAt = date.setHours(date.getHours() + 1);
-						break;
-					case '12h':
-						expiresAt = date.setHours(date.getHours() + 12);
-						break;
-					case '1d':
-						expiresAt = date.setDate(date.getDate() + 1);
-						break;
-					case '1w':
-						expiresAt = date.setDate(date.getDate() + 7);
-						break;
-					case '1m':
-						expiresAt = date.setMonth(date.getMonth() + 1);
-						break;
-					case '3m':
-						expiresAt = date.setMonth(date.getMonth() + 3);
-						break;
-					case '6m':
-						expiresAt = date.setMonth(date.getMonth() + 6);
-						break;
-					case '1y':
-						expiresAt = date.setFullYear(date.getFullYear() + 1);
-						break;
-					case 'never':
-						expiresAt = new Date(3093527980800000);
-						break;
-					default:
-						return next('Invalid expire date.');
-				}
-
-				next();
-			},
-
-			(next) => {
-				punishments.addPunishment('banUserIp', value, reason, expiresAt, session.userId, next)
-			}
-		], async (err, punishment) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("BAN_IP", `User ${session.userId} failed to ban IP address ${value} with the reason ${reason}. '${err}'`);
-				cb({ status: 'failure', message: err });
-			}
-			logger.success("BAN_IP", `User ${session.userId} has successfully banned IP address ${value} with the reason ${reason}.`);
-			cache.pub('ip.ban', { ip: value, punishment });
-			return cb({
-				status: 'success',
-				message: 'Successfully banned IP address.'
-			});
-		});
-	}),
+                    next();
+                },
 
+                (next) => {
+                    punishments
+                        .runJob("ADD_PUNISHMENT", {
+                            type: "banUserIp",
+                            value,
+                            reason,
+                            expiresAt,
+                            punishedBy: session.userId,
+                        })
+                        .then((punishment) => next(null, punishment))
+                        .catch(next);
+                },
+            ],
+            async (err, punishment) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "BAN_IP",
+                        `User ${session.userId} failed to ban IP address ${value} with the reason ${reason}. '${err}'`
+                    );
+                    cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "BAN_IP",
+                    `User ${session.userId} has successfully banned IP address ${value} with the reason ${reason}.`
+                );
+                cache.runJob("PUB", {
+                    channel: "ip.ban",
+                    value: { ip: value, punishment },
+                });
+                return cb({
+                    status: "success",
+                    message: "Successfully banned IP address.",
+                });
+            }
+        );
+    }),
 };

+ 374 - 229
backend/logic/actions/queueSongs.js

@@ -1,249 +1,394 @@
-'use strict';
+"use strict";
 
-const config = require('config');
-const async = require('async');
-const request = require('request');
+const config = require("config");
+const async = require("async");
+const request = require("request");
 
-const hooks = require('./hooks');
+const hooks = require("./hooks");
 
-const moduleManager = require("../../index");
+const db = require("../db");
+const utils = require("../utils");
+const cache = require("../cache");
+// const logger = moduleManager.modules["logger"];
 
-const db = moduleManager.modules["db"];
-const utils = moduleManager.modules["utils"];
-const logger = moduleManager.modules["logger"];
-const cache = moduleManager.modules["cache"];
-
-cache.sub('queue.newSong', songId => {
-	db.models.queueSong.findOne({_id: songId}, (err, song) => {
-		utils.emitToRoom('admin.queue', 'event:admin.queueSong.added', song);
-	});
+cache.runJob("SUB", {
+    channel: "queue.newSong",
+    cb: async (songId) => {
+        const queueSongModel = await db.runJob("GET_MODEL", {
+            modelName: "queueSong",
+        });
+        queueSongModel.findOne({ _id: songId }, (err, song) => {
+            utils.runJob("EMIT_TO_ROOM", {
+                room: "admin.queue",
+                args: ["event:admin.queueSong.added", song],
+            });
+        });
+    },
 });
 
-cache.sub('queue.removedSong', songId => {
-	utils.emitToRoom('admin.queue', 'event:admin.queueSong.removed', songId);
+cache.runJob("SUB", {
+    channel: "queue.removedSong",
+    cb: (songId) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: "admin.queue",
+            args: ["event:admin.queueSong.removed", songId],
+        });
+    },
 });
 
-cache.sub('queue.update', songId => {
-	db.models.queueSong.findOne({_id: songId}, (err, song) => {
-		utils.emitToRoom('admin.queue', 'event:admin.queueSong.updated', song);
-	});
+cache.runJob("SUB", {
+    channel: "queue.update",
+    cb: async (songId) => {
+        const queueSongModel = await db.runJob("GET_MODEL", {
+            modelName: "queueSong",
+        });
+        queueSongModel.findOne({ _id: songId }, (err, song) => {
+            utils.runJob("EMIT_TO_ROOM", {
+                room: "admin.queue",
+                args: ["event:admin.queueSong.updated", song],
+            });
+        });
+    },
 });
 
 let lib = {
+    /**
+     * Returns the length of the queue songs list
+     *
+     * @param session
+     * @param cb
+     */
+    length: hooks.adminRequired(async (session, cb) => {
+        const queueSongModel = await db.runJob("GET_MODEL", {
+            modelName: "queueSong",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    queueSongModel.countDocuments({}, next);
+                },
+            ],
+            async (err, count) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "QUEUE_SONGS_LENGTH",
+                        `Failed to get length from queue songs. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "QUEUE_SONGS_LENGTH",
+                    `Got length from queue songs successfully.`
+                );
+                cb(count);
+            }
+        );
+    }),
 
-	/**
-	 * Returns the length of the queue songs list
-	 *
-	 * @param session
-	 * @param cb
-	 */
-	length: hooks.adminRequired((session, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.queueSong.countDocuments({}, next);
-			}
-		], async (err, count) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("QUEUE_SONGS_LENGTH", `Failed to get length from queue songs. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("QUEUE_SONGS_LENGTH", `Got length from queue songs successfully.`);
-			cb(count);
-		});
-	}),
-
-	/**
-	 * Gets a set of queue songs
-	 *
-	 * @param session
-	 * @param set - the set number to return
-	 * @param cb
-	 */
-	getSet: hooks.adminRequired((session, set, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.queueSong.find({}).skip(15 * (set - 1)).limit(15).exec(next);
-			},
-		], async (err, songs) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("QUEUE_SONGS_GET_SET", `Failed to get set from queue songs. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("QUEUE_SONGS_GET_SET", `Got set from queue songs successfully.`);
-			cb(songs);
-		});
-	}),
-
-	/**
-	 * Updates a queuesong
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} songId - the id of the queuesong that gets updated
-	 * @param {Object} updatedSong - the object of the updated queueSong
-	 * @param {Function} cb - gets called with the result
-	 */
-	update: hooks.adminRequired((session, songId, updatedSong, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.queueSong.findOne({_id: songId}, next);
-			},
-
-			(song, next) => {
-				if(!song) return next('Song not found');
-				let updated = false;
-				let $set = {};
-				for (let prop in updatedSong) if (updatedSong[prop] !== song[prop]) $set[prop] = updatedSong[prop]; updated = true;
-				if (!updated) return next('No properties changed');
-				db.models.queueSong.updateOne({_id: songId}, {$set}, {runValidators: true}, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await  utils.getError(err);
-				logger.error("QUEUE_UPDATE", `Updating queuesong "${songId}" failed for user ${session.userId}. "${err}"`);
-				return cb({status: 'failure', message: err});
-			}
-			cache.pub('queue.update', songId);
-			logger.success("QUEUE_UPDATE", `User "${session.userId}" successfully update queuesong "${songId}".`);
-			return cb({status: 'success', message: 'Successfully updated song.'});
-		});
-	}),
-
-	/**
-	 * Removes a queuesong
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} songId - the id of the queuesong that gets removed
-	 * @param {Function} cb - gets called with the result
-	 */
-	remove: hooks.adminRequired((session, songId, cb, userId) => {
-		async.waterfall([
-			(next) => {
-				db.models.queueSong.deleteOne({_id: songId}, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("QUEUE_REMOVE", `Removing queuesong "${songId}" failed for user ${session.userId}. "${err}"`);
-				return cb({status: 'failure', message: err});
-			}
-			cache.pub('queue.removedSong', songId);
-			logger.success("QUEUE_REMOVE", `User "${session.userId}" successfully removed queuesong "${songId}".`);
-			return cb({status: 'success', message: 'Successfully updated song.'});
-		});
-	}),
-
-	/**
-	 * Creates a queuesong
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} songId - the id of the song that gets added
-	 * @param {Function} cb - gets called with the result
-	 */
-	add: hooks.loginRequired((session, songId, cb) => {
-		let requestedAt = Date.now();
-
-		async.waterfall([
-			(next) => {
-				db.models.queueSong.findOne({songId}, next);
-			},
-
-			(song, next) => {
-				if (song) return next('This song is already in the queue.');
-				db.models.song.findOne({songId}, next);
-			},
-
-			// Get YouTube data from id
-			(song, next) => {
-				if (song) return next('This song has already been added.');
-				//TODO Add err object as first param of callback
-				utils.getSongFromYouTube(songId, (song) => {
-					song.duration = -1;
-					song.artists = [];
-					song.genres = [];
-					song.skipDuration = 0;
-					song.thumbnail = `${config.get("domain")}/assets/notes.png`;
-					song.explicit = false;
-					song.requestedBy = session.userId;
-					song.requestedAt = requestedAt;
-					next(null, song);
-				});
-			},
-			/*(newSong, next) => {
+    /**
+     * Gets a set of queue songs
+     *
+     * @param session
+     * @param set - the set number to return
+     * @param cb
+     */
+    getSet: hooks.adminRequired(async (session, set, cb) => {
+        const queueSongModel = await db.runJob("GET_MODEL", {
+            modelName: "queueSong",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    queueSongModel
+                        .find({})
+                        .skip(15 * (set - 1))
+                        .limit(15)
+                        .exec(next);
+                },
+            ],
+            async (err, songs) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "QUEUE_SONGS_GET_SET",
+                        `Failed to get set from queue songs. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "QUEUE_SONGS_GET_SET",
+                    `Got set from queue songs successfully.`
+                );
+                cb(songs);
+            }
+        );
+    }),
+
+    /**
+     * Updates a queuesong
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} songId - the id of the queuesong that gets updated
+     * @param {Object} updatedSong - the object of the updated queueSong
+     * @param {Function} cb - gets called with the result
+     */
+    update: hooks.adminRequired(async (session, songId, updatedSong, cb) => {
+        const queueSongModel = await db.runJob("GET_MODEL", {
+            modelName: "queueSong",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    queueSongModel.findOne({ _id: songId }, next);
+                },
+
+                (song, next) => {
+                    if (!song) return next("Song not found");
+                    let updated = false;
+                    let $set = {};
+                    for (let prop in updatedSong)
+                        if (updatedSong[prop] !== song[prop])
+                            $set[prop] = updatedSong[prop];
+                    updated = true;
+                    if (!updated) return next("No properties changed");
+                    queueSongModel.updateOne(
+                        { _id: songId },
+                        { $set },
+                        { runValidators: true },
+                        next
+                    );
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "QUEUE_UPDATE",
+                        `Updating queuesong "${songId}" failed for user ${session.userId}. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                cache.runJob("PUB", { channel: "queue.update", value: songId });
+                console.log(
+                    "SUCCESS",
+                    "QUEUE_UPDATE",
+                    `User "${session.userId}" successfully update queuesong "${songId}".`
+                );
+                return cb({
+                    status: "success",
+                    message: "Successfully updated song.",
+                });
+            }
+        );
+    }),
+
+    /**
+     * Removes a queuesong
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} songId - the id of the queuesong that gets removed
+     * @param {Function} cb - gets called with the result
+     */
+    remove: hooks.adminRequired(async (session, songId, cb, userId) => {
+        const queueSongModel = await db.runJob("GET_MODEL", {
+            modelName: "queueSong",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    queueSongModel.deleteOne({ _id: songId }, next);
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "QUEUE_REMOVE",
+                        `Removing queuesong "${songId}" failed for user ${session.userId}. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                cache.runJob("PUB", {
+                    channel: "queue.removedSong",
+                    value: songId,
+                });
+                console.log(
+                    "SUCCESS",
+                    "QUEUE_REMOVE",
+                    `User "${session.userId}" successfully removed queuesong "${songId}".`
+                );
+                return cb({
+                    status: "success",
+                    message: "Successfully updated song.",
+                });
+            }
+        );
+    }),
+
+    /**
+     * Creates a queuesong
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} songId - the id of the song that gets added
+     * @param {Function} cb - gets called with the result
+     */
+    add: hooks.loginRequired(async (session, songId, cb) => {
+        let requestedAt = Date.now();
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        const queueSongModel = await db.runJob("GET_MODEL", {
+            modelName: "queueSong",
+        });
+
+        async.waterfall(
+            [
+                (next) => {
+                    queueSongModel.findOne({ songId }, next);
+                },
+
+                (song, next) => {
+                    if (song) return next("This song is already in the queue.");
+                    songModel.findOne({ songId }, next);
+                },
+
+                // Get YouTube data from id
+                (song, next) => {
+                    if (song) return next("This song has already been added.");
+                    //TODO Add err object as first param of callback
+                    utils
+                        .runJob("GET_SONG_FROM_YOUTUBE", { songId })
+                        .then((response) => {
+                            const song = response.song;
+                            song.duration = -1;
+                            song.artists = [];
+                            song.genres = [];
+                            song.skipDuration = 0;
+                            song.thumbnail = `${config.get(
+                                "domain"
+                            )}/assets/notes.png`;
+                            song.explicit = false;
+                            song.requestedBy = session.userId;
+                            song.requestedAt = requestedAt;
+                            next(null, song);
+                        })
+                        .catch(next);
+                },
+                /*(newSong, next) => {
 				utils.getSongFromSpotify(newSong, (err, song) => {
 					if (!song) next(null, newSong);
 					else next(err, song);
 				});
 			},*/
-			(newSong, next) => {
-				const song = new db.models.queueSong(newSong);
-				song.save({ validateBeforeSave: false }, (err, song) => {
-					if (err) return next(err);
-					next(null, song);
-				});
-			},
-			(newSong, next) => {
-				db.models.user.findOne({ _id: session.userId }, (err, user) => {
-					if (err) next(err, newSong);
-					else {
-						user.statistics.songsRequested = user.statistics.songsRequested + 1;
-						user.save(err => {
-							if (err) return next(err, newSong);
-							else next(null, newSong);
-						});
-					}
-				});
-			}
-		], async (err, newSong) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("QUEUE_ADD", `Adding queuesong "${songId}" failed for user ${session.userId}. "${err}"`);
-				return cb({status: 'failure', message: err});
-			}
-			cache.pub('queue.newSong', newSong._id);
-			logger.success("QUEUE_ADD", `User "${session.userId}" successfully added queuesong "${songId}".`);
-			return cb({ status: 'success', message: 'Successfully added that song to the queue' });
-		});
-	}),
-
-	/**
-	 * Adds a set of songs to the queue
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} url - the url of the the YouTube playlist
-	 * @param {Function} cb - gets called with the result
-	 */
-	addSetToQueue: hooks.loginRequired((session, url, cb) => {
-		async.waterfall([
-			(next) => {
-				utils.getPlaylistFromYouTube(url, songs => {
-					next(null, songs);
-				});
-			},
-			(songs, next) => {
-				let processed = 0;
-				function checkDone() {
-					if (processed === songs.length) next();
-				}
-				for (let s = 0; s < songs.length; s++) {
-					lib.add(session, songs[s].contentDetails.videoId, () => {
-						processed++;
-						checkDone();
-					});
-				}
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("QUEUE_IMPORT", `Importing a YouTube playlist to the queue failed for user "${session.userId}". "${err}"`);
-				return cb({ status: 'failure', message: err});
-			} else {
-				logger.success("QUEUE_IMPORT", `Successfully imported a YouTube playlist to the queue for user "${session.userId}".`);
-				cb({ status: 'success', message: 'Playlist has been successfully imported.' });
-			}
-		});
-	})
+                (newSong, next) => {
+                    const song = new queueSongModel(newSong);
+                    song.save({ validateBeforeSave: false }, (err, song) => {
+                        if (err) return next(err);
+                        next(null, song);
+                    });
+                },
+                (newSong, next) => {
+                    userModel.findOne({ _id: session.userId }, (err, user) => {
+                        if (err) next(err, newSong);
+                        else {
+                            user.statistics.songsRequested =
+                                user.statistics.songsRequested + 1;
+                            user.save((err) => {
+                                if (err) return next(err, newSong);
+                                else next(null, newSong);
+                            });
+                        }
+                    });
+                },
+            ],
+            async (err, newSong) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "QUEUE_ADD",
+                        `Adding queuesong "${songId}" failed for user ${session.userId}. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                cache.runJob("PUB", {
+                    channel: "queue.newSong",
+                    value: newSong._id,
+                });
+                console.log(
+                    "SUCCESS",
+                    "QUEUE_ADD",
+                    `User "${session.userId}" successfully added queuesong "${songId}".`
+                );
+                return cb({
+                    status: "success",
+                    message: "Successfully added that song to the queue",
+                });
+            }
+        );
+    }),
+
+    /**
+     * Adds a set of songs to the queue
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} url - the url of the the YouTube playlist
+     * @param {Function} cb - gets called with the result
+     */
+    addSetToQueue: hooks.loginRequired((session, url, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    utils
+                        .runJob("GET_PLAYLIST_FROM_YOUTUBE", {
+                            url,
+                            musicOnly: false,
+                        })
+                        .then((songIds) => next(null, songIds))
+                        .catch(next);
+                },
+                (songIds, next) => {
+                    let processed = 0;
+                    function checkDone() {
+                        if (processed === songIds.length) next();
+                    }
+                    for (let s = 0; s < songIds.length; s++) {
+                        lib.add(session, songIds[s], () => {
+                            processed++;
+                            checkDone();
+                        });
+                    }
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "QUEUE_IMPORT",
+                        `Importing a YouTube playlist to the queue failed for user "${session.userId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "QUEUE_IMPORT",
+                        `Successfully imported a YouTube playlist to the queue for user "${session.userId}".`
+                    );
+                    cb({
+                        status: "success",
+                        message: "Playlist has been successfully imported.",
+                    });
+                }
+            }
+        );
+    }),
 };
 
 module.exports = lib;

+ 342 - 233
backend/logic/actions/reports.js

@@ -1,250 +1,359 @@
-'use strict';
+"use strict";
 
-const async = require('async');
+const async = require("async");
 
-const hooks = require('./hooks');
+const hooks = require("./hooks");
 
 const moduleManager = require("../../index");
 
-const db = moduleManager.modules["db"];
-const cache = moduleManager.modules["cache"];
-const utils = moduleManager.modules["utils"];
-const logger = moduleManager.modules["logger"];
-const songs = moduleManager.modules["songs"];
+const db = require("../db");
+const cache = require("../cache");
+const utils = require("../utils");
+// const logger = require("../logger");
+const songs = require("../songs");
 
 const reportableIssues = [
-	{
-		name: 'Video',
-		reasons: [
-			'Doesn\'t exist',
-			'It\'s private',
-			'It\'s not available in my country'
-		]
-	},
-	{
-		name: 'Title',
-		reasons: [
-			'Incorrect',
-			'Inappropriate'
-		]
-	},
-	{
-		name: 'Duration',
-		reasons: [
-			'Skips too soon',
-			'Skips too late',
-			'Starts too soon',
-			'Skips too late'
-		]
-	},
-	{
-		name: 'Artists',
-		reasons: [
-			'Incorrect',
-			'Inappropriate'
-		]
-	},
-	{
-		name: 'Thumbnail',
-		reasons: [
-			'Incorrect',
-			'Inappropriate',
-			'Doesn\'t exist'
-		]
-	}
+    {
+        name: "Video",
+        reasons: [
+            "Doesn't exist",
+            "It's private",
+            "It's not available in my country",
+        ],
+    },
+    {
+        name: "Title",
+        reasons: ["Incorrect", "Inappropriate"],
+    },
+    {
+        name: "Duration",
+        reasons: [
+            "Skips too soon",
+            "Skips too late",
+            "Starts too soon",
+            "Skips too late",
+        ],
+    },
+    {
+        name: "Artists",
+        reasons: ["Incorrect", "Inappropriate"],
+    },
+    {
+        name: "Thumbnail",
+        reasons: ["Incorrect", "Inappropriate", "Doesn't exist"],
+    },
 ];
 
-cache.sub('report.resolve', reportId => {
-	utils.emitToRoom('admin.reports', 'event:admin.report.resolved', reportId);
+cache.runJob("SUB", {
+    channel: "report.resolve",
+    cb: (reportId) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: "admin.reports",
+            args: ["event:admin.report.resolved", reportId],
+        });
+    },
 });
 
-cache.sub('report.create', report => {
-	utils.emitToRoom('admin.reports', 'event:admin.report.created', report);
+cache.runJob("SUB", {
+    channel: "report.create",
+    cb: (report) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: "admin.reports",
+            args: ["event:admin.report.created", report],
+        });
+    },
 });
 
 module.exports = {
+    /**
+     * Gets all reports
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Function} cb - gets called with the result
+     */
+    index: hooks.adminRequired(async (session, cb) => {
+        const reportModel = await db.runJob("GET_MODEL", {
+            modelName: "report",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    reportModel
+                        .find({ resolved: false })
+                        .sort({ released: "desc" })
+                        .exec(next);
+                },
+            ],
+            async (err, reports) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "REPORTS_INDEX",
+                        `Indexing reports failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "REPORTS_INDEX",
+                    "Indexing reports successful."
+                );
+                cb({ status: "success", data: reports });
+            }
+        );
+    }),
 
-	/**
-	 * Gets all reports
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Function} cb - gets called with the result
-	 */
-	index: hooks.adminRequired((session, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.report.find({ resolved: false }).sort({ released: 'desc' }).exec(next);
-			}
-		], async (err, reports) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("REPORTS_INDEX", `Indexing reports failed. "${err}"`);
-				return cb({ 'status': 'failure', 'message': err});
-			}
-			logger.success("REPORTS_INDEX", "Indexing reports successful.");
-			cb({ status: 'success', data: reports });
-		});
-	}),
-
-	/**
-	 * Gets a specific report
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} reportId - the id of the report to return
-	 * @param {Function} cb - gets called with the result
-	 */
-	findOne: hooks.adminRequired((session, reportId, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.report.findOne({ _id: reportId }).exec(next);
-			}
-		], async (err, report) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("REPORTS_FIND_ONE", `Finding report "${reportId}" failed. "${err}"`);
-				return cb({ 'status': 'failure', 'message': err });
-			}
-			logger.success("REPORTS_FIND_ONE", `Finding report "${reportId}" successful.`);
-			cb({ status: 'success', data: report });
-		});
-	}),
-
-	/**
-	 * Gets all reports for a songId (_id)
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} songId - the id of the song to index reports for
-	 * @param {Function} cb - gets called with the result
-	 */
-	getReportsForSong: hooks.adminRequired((session, songId, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.report.find({ song: { _id: songId }, resolved: false }).sort({ released: 'desc' }).exec(next);
-			},
-
-			(reports, next) => {
-				let data = [];
-				for (let i = 0; i < reports.length; i++) {
-					data.push(reports[i]._id);
-				}
-				next(null, data);
-			}
-		], async (err, data) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("GET_REPORTS_FOR_SONG", `Indexing reports for song "${songId}" failed. "${err}"`);
-				return cb({ 'status': 'failure', 'message': err});
-			} else {
-				logger.success("GET_REPORTS_FOR_SONG", `Indexing reports for song "${songId}" successful.`);
-				return cb({ status: 'success', data });
-			}
-		});
-	}),
-
-	/**
-	 * Resolves a report
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} reportId - the id of the report that is getting resolved
-	 * @param {Function} cb - gets called with the result
-	 */
-	resolve: hooks.adminRequired((session, reportId, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.report.findOne({ _id: reportId }).exec(next);
-			},
-
-			(report, next) => {
-				if (!report) return next('Report not found.');
-				report.resolved = true;
-				report.save(err => {
-					if (err) next(err.message);
-					else next();
-				});
-			}
-		], async (err) => {
-			if (err) {
-				err = await  utils.getError(err);
-				logger.error("REPORTS_RESOLVE", `Resolving report "${reportId}" failed by user "${session.userId}". "${err}"`);
-				return cb({ 'status': 'failure', 'message': err});
-			} else {
-				cache.pub('report.resolve', reportId);
-				logger.success("REPORTS_RESOLVE", `User "${session.userId}" resolved report "${reportId}".`);
-				cb({ status: 'success', message: 'Successfully resolved Report' });
-			}
-		});
-	}),
-
-	/**
-	 * Creates a new report
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Object} data - the object of the report data
-	 * @param {Function} cb - gets called with the result
-	 */
-	create: hooks.loginRequired((session, data, cb) => {
-		async.waterfall([
-
-			(next) => {
-				db.models.song.findOne({ songId: data.songId }).exec(next);
-			},
-
-			(song, next) => {
-				if (!song) return next('Song not found.');
-				songs.getSong(song._id, next);
-			},
-
-			(song, next) => {
-				if (!song) return next('Song not found.');
-
-				delete data.songId;
-				data.song = {
-					_id: song._id,
-					songId: song.songId
-				}
-
-				for (let z = 0; z < data.issues.length; z++) {
-					if (reportableIssues.filter(issue => { return issue.name == data.issues[z].name; }).length > 0) {
-						for (let r = 0; r < reportableIssues.length; r++) {
-							if (reportableIssues[r].reasons.every(reason => data.issues[z].reasons.indexOf(reason) < -1)) {
-								return cb({ status: 'failure', message: 'Invalid data' });
-							}
-						}
-					} else return cb({ status: 'failure', message: 'Invalid data' });
-				}
-
-				next();
-			},
-
-			(next) => {
-				let issues = [];
-
-				for (let r = 0; r < data.issues.length; r++) {
-					if (!data.issues[r].reasons.length <= 0) issues.push(data.issues[r]);
-				}
-
-				data.issues = issues;
-
-				next();
-			},
-
-			(next) => {
-				data.createdBy = session.userId;
-				data.createdAt = Date.now();
-				db.models.report.create(data, next);
-			}
-
-		], async (err, report) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("REPORTS_CREATE", `Creating report for "${data.song._id}" failed by user "${session.userId}". "${err}"`);
-				return cb({ 'status': 'failure', 'message': err });
-			} else {
-				cache.pub('report.create', report);
-				logger.success("REPORTS_CREATE", `User "${session.userId}" created report for "${data.songId}".`);
-				return cb({ 'status': 'success', 'message': 'Successfully created report' });
-			}
-		});
-	})
+    /**
+     * Gets a specific report
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} reportId - the id of the report to return
+     * @param {Function} cb - gets called with the result
+     */
+    findOne: hooks.adminRequired(async (session, reportId, cb) => {
+        const reportModel = await db.runJob("GET_MODEL", {
+            modelName: "report",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    reportModel.findOne({ _id: reportId }).exec(next);
+                },
+            ],
+            async (err, report) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "REPORTS_FIND_ONE",
+                        `Finding report "${reportId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "REPORTS_FIND_ONE",
+                    `Finding report "${reportId}" successful.`
+                );
+                cb({ status: "success", data: report });
+            }
+        );
+    }),
 
+    /**
+     * Gets all reports for a songId (_id)
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} songId - the id of the song to index reports for
+     * @param {Function} cb - gets called with the result
+     */
+    getReportsForSong: hooks.adminRequired(async (session, songId, cb) => {
+        const reportModel = await db.runJob("GET_MODEL", {
+            modelName: "report",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    reportModel
+                        .find({ song: { _id: songId }, resolved: false })
+                        .sort({ released: "desc" })
+                        .exec(next);
+                },
+
+                (reports, next) => {
+                    let data = [];
+                    for (let i = 0; i < reports.length; i++) {
+                        data.push(reports[i]._id);
+                    }
+                    next(null, data);
+                },
+            ],
+            async (err, data) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "GET_REPORTS_FOR_SONG",
+                        `Indexing reports for song "${songId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "GET_REPORTS_FOR_SONG",
+                        `Indexing reports for song "${songId}" successful.`
+                    );
+                    return cb({ status: "success", data });
+                }
+            }
+        );
+    }),
+
+    /**
+     * Resolves a report
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} reportId - the id of the report that is getting resolved
+     * @param {Function} cb - gets called with the result
+     */
+    resolve: hooks.adminRequired(async (session, reportId, cb) => {
+        const reportModel = await db.runJob("GET_MODEL", {
+            modelName: "report",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    reportModel.findOne({ _id: reportId }).exec(next);
+                },
+
+                (report, next) => {
+                    if (!report) return next("Report not found.");
+                    report.resolved = true;
+                    report.save((err) => {
+                        if (err) next(err.message);
+                        else next();
+                    });
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "REPORTS_RESOLVE",
+                        `Resolving report "${reportId}" failed by user "${session.userId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    cache.runJob("PUB", {
+                        channel: "report.resolve",
+                        value: reportId,
+                    });
+                    console.log(
+                        "SUCCESS",
+                        "REPORTS_RESOLVE",
+                        `User "${session.userId}" resolved report "${reportId}".`
+                    );
+                    cb({
+                        status: "success",
+                        message: "Successfully resolved Report",
+                    });
+                }
+            }
+        );
+    }),
+
+    /**
+     * Creates a new report
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Object} data - the object of the report data
+     * @param {Function} cb - gets called with the result
+     */
+    create: hooks.loginRequired(async (session, data, cb) => {
+        const reportModel = await db.runJob("GET_MODEL", {
+            modelName: "report",
+        });
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        async.waterfall(
+            [
+                (next) => {
+                    songModel.findOne({ songId: data.songId }).exec(next);
+                },
+
+                (song, next) => {
+                    if (!song) return next("Song not found.");
+                    songs
+                        .runJob("GET_SONG", { id: song._id })
+                        .then((response) => next(null, response.song))
+                        .catch(next);
+                },
+
+                (song, next) => {
+                    if (!song) return next("Song not found.");
+
+                    delete data.songId;
+                    data.song = {
+                        _id: song._id,
+                        songId: song.songId,
+                    };
+
+                    for (let z = 0; z < data.issues.length; z++) {
+                        if (
+                            reportableIssues.filter((issue) => {
+                                return issue.name == data.issues[z].name;
+                            }).length > 0
+                        ) {
+                            for (let r = 0; r < reportableIssues.length; r++) {
+                                if (
+                                    reportableIssues[r].reasons.every(
+                                        (reason) =>
+                                            data.issues[z].reasons.indexOf(
+                                                reason
+                                            ) < -1
+                                    )
+                                ) {
+                                    return cb({
+                                        status: "failure",
+                                        message: "Invalid data",
+                                    });
+                                }
+                            }
+                        } else
+                            return cb({
+                                status: "failure",
+                                message: "Invalid data",
+                            });
+                    }
+
+                    next();
+                },
+
+                (next) => {
+                    let issues = [];
+
+                    for (let r = 0; r < data.issues.length; r++) {
+                        if (!data.issues[r].reasons.length <= 0)
+                            issues.push(data.issues[r]);
+                    }
+
+                    data.issues = issues;
+
+                    next();
+                },
+
+                (next) => {
+                    data.createdBy = session.userId;
+                    data.createdAt = Date.now();
+                    reportModel.create(data, next);
+                },
+            ],
+            async (err, report) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "REPORTS_CREATE",
+                        `Creating report for "${data.song._id}" failed by user "${session.userId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    cache.runJob("PUB", {
+                        channel: "report.create",
+                        value: report,
+                    });
+                    console.log(
+                        "SUCCESS",
+                        "REPORTS_CREATE",
+                        `User "${session.userId}" created report for "${data.songId}".`
+                    );
+                    return cb({
+                        status: "success",
+                        message: "Successfully created report",
+                    });
+                }
+            }
+        );
+    }),
 };

+ 1022 - 469
backend/logic/actions/songs.js

@@ -1,491 +1,1044 @@
-'use strict';
+"use strict";
 
-const async = require('async');
+const async = require("async");
 
-const hooks = require('./hooks');
-const queueSongs = require('./queueSongs');
+const hooks = require("./hooks");
+const queueSongs = require("./queueSongs");
 
-const moduleManager = require("../../index");
+// const moduleManager = require("../../index");
 
-const db = moduleManager.modules["db"];
-const songs = moduleManager.modules["songs"];
-const cache = moduleManager.modules["cache"];
-const utils = moduleManager.modules["utils"];
-const logger = moduleManager.modules["logger"];
+const db = require("../db");
+const songs = require("../songs");
+const cache = require("../cache");
+const utils = require("../utils");
+const activities = require("../activities");
+// const logger = moduleManager.modules["logger"];
 
-cache.sub('song.removed', songId => {
-	utils.emitToRoom('admin.songs', 'event:admin.song.removed', songId);
+cache.runJob("SUB", {
+    channel: "song.removed",
+    cb: (songId) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: "admin.songs",
+            args: ["event:admin.song.removed", songId],
+        });
+    },
 });
 
-cache.sub('song.added', songId => {
-	db.models.song.findOne({_id: songId}, (err, song) => {
-		utils.emitToRoom('admin.songs', 'event:admin.song.added', song);
-	});
+cache.runJob("SUB", {
+    channel: "song.added",
+    cb: async (songId) => {
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        songModel.findOne({ _id: songId }, (err, song) => {
+            utils.runJob("EMIT_TO_ROOM", {
+                room: "admin.songs",
+                args: ["event:admin.song.added", song],
+            });
+        });
+    },
 });
 
-cache.sub('song.updated', songId => {
-	db.models.song.findOne({_id: songId}, (err, song) => {
-		utils.emitToRoom('admin.songs', 'event:admin.song.updated', song);
-	});
+cache.runJob("SUB", {
+    channel: "song.updated",
+    cb: async (songId) => {
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        songModel.findOne({ _id: songId }, (err, song) => {
+            utils.runJob("EMIT_TO_ROOM", {
+                room: "admin.songs",
+                args: ["event:admin.song.updated", song],
+            });
+        });
+    },
 });
 
-cache.sub('song.like', (data) => {
-	utils.emitToRoom(`song.${data.songId}`, 'event:song.like', {songId: data.songId, likes: data.likes, dislikes: data.dislikes});
-	utils.socketsFromUser(data.userId, (sockets) => {
-		sockets.forEach((socket) => {
-			socket.emit('event:song.newRatings', {songId: data.songId, liked: true, disliked: false});
-		});
-	});
+cache.runJob("SUB", {
+    channel: "song.like",
+    cb: (data) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: `song.${data.songId}`,
+            args: [
+                "event:song.like",
+                {
+                    songId: data.songId,
+                    likes: data.likes,
+                    dislikes: data.dislikes,
+                },
+            ],
+        });
+        utils
+            .runJob("SOCKETS_FROM_USER", { userId: data.userId })
+            .then((response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:song.newRatings", {
+                        songId: data.songId,
+                        liked: true,
+                        disliked: false,
+                    });
+                });
+            });
+    },
 });
 
-cache.sub('song.dislike', (data) => {
-	utils.emitToRoom(`song.${data.songId}`, 'event:song.dislike', {songId: data.songId, likes: data.likes, dislikes: data.dislikes});
-	utils.socketsFromUser(data.userId, (sockets) => {
-		sockets.forEach((socket) => {
-			socket.emit('event:song.newRatings', {songId: data.songId, liked: false, disliked: true});
-		});
-	});
+cache.runJob("SUB", {
+    channel: "song.dislike",
+    cb: (data) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: `song.${data.songId}`,
+            args: [
+                "event:song.dislike",
+                {
+                    songId: data.songId,
+                    likes: data.likes,
+                    dislikes: data.dislikes,
+                },
+            ],
+        });
+        utils
+            .runJob("SOCKETS_FROM_USER", { userId: data.userId })
+            .then((response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:song.newRatings", {
+                        songId: data.songId,
+                        liked: false,
+                        disliked: true,
+                    });
+                });
+            });
+    },
 });
 
-cache.sub('song.unlike', (data) => {
-	utils.emitToRoom(`song.${data.songId}`, 'event:song.unlike', {songId: data.songId, likes: data.likes, dislikes: data.dislikes});
-	utils.socketsFromUser(data.userId, (sockets) => {
-		sockets.forEach((socket) => {
-			socket.emit('event:song.newRatings', {songId: data.songId, liked: false, disliked: false});
-		});
-	});
+cache.runJob("SUB", {
+    channel: "song.unlike",
+    cb: (data) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: `song.${data.songId}`,
+            args: [
+                "event:song.unlike",
+                {
+                    songId: data.songId,
+                    likes: data.likes,
+                    dislikes: data.dislikes,
+                },
+            ],
+        });
+        utils
+            .runJob("SOCKETS_FROM_USER", { userId: data.userId })
+            .then((response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:song.newRatings", {
+                        songId: data.songId,
+                        liked: false,
+                        disliked: false,
+                    });
+                });
+            });
+    },
 });
 
-cache.sub('song.undislike', (data) => {
-	utils.emitToRoom(`song.${data.songId}`, 'event:song.undislike', {songId: data.songId, likes: data.likes, dislikes: data.dislikes});
-	utils.socketsFromUser(data.userId, (sockets) => {
-		sockets.forEach((socket) => {
-			socket.emit('event:song.newRatings', {songId: data.songId, liked: false, disliked: false});
-		});
-	});
+cache.runJob("SUB", {
+    channel: "song.undislike",
+    cb: (data) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: `song.${data.songId}`,
+            args: [
+                "event:song.undislike",
+                {
+                    songId: data.songId,
+                    likes: data.likes,
+                    dislikes: data.dislikes,
+                },
+            ],
+        });
+        utils
+            .runJob("SOCKETS_FROM_USER", { userId: data.userId })
+            .then((response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:song.newRatings", {
+                        songId: data.songId,
+                        liked: false,
+                        disliked: false,
+                    });
+                });
+            });
+    },
 });
 
 module.exports = {
+    /**
+     * Returns the length of the songs list
+     *
+     * @param session
+     * @param cb
+     */
+    length: hooks.adminRequired(async (session, cb) => {
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        async.waterfall(
+            [
+                (next) => {
+                    songModel.countDocuments({}, next);
+                },
+            ],
+            async (err, count) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "SONGS_LENGTH",
+                        `Failed to get length from songs. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "SONGS_LENGTH",
+                    `Got length from songs successfully.`
+                );
+                cb(count);
+            }
+        );
+    }),
 
-	/**
-	 * Returns the length of the songs list
-	 *
-	 * @param session
-	 * @param cb
-	 */
-	length: hooks.adminRequired((session, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.song.countDocuments({}, next);
-			}
-		], async (err, count) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("SONGS_LENGTH", `Failed to get length from songs. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("SONGS_LENGTH", `Got length from songs successfully.`);
-			cb(count);
-		});
-	}),
-
-	/**
-	 * Gets a set of songs
-	 *
-	 * @param session
-	 * @param set - the set number to return
-	 * @param cb
-	 */
-	getSet: hooks.adminRequired((session, set, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.song.find({}).skip(15 * (set - 1)).limit(15).exec(next);
-			},
-		], async (err, songs) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("SONGS_GET_SET", `Failed to get set from songs. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("SONGS_GET_SET", `Got set from songs successfully.`);
-			cb(songs);
-		});
-	}),
-
-	/**
-	 * Gets a song
-	 *
-	 * @param session
-	 * @param songId - the song id
-	 * @param cb
-	 */
-	getSong: hooks.adminRequired((session, songId, cb) => {
-		console.log(songId);
-
-		async.waterfall([
-			(next) => {
-				db.models.song.findOne({ songId }).exec(next);
-			}
-		], async (err, song) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("SONGS_GET_SONG", `Failed to get song ${songId}. "${err}"`);
-				return cb({ status: 'failure', message: err });
-			} else {
-				logger.success("SONGS_GET_SONG", `Got song ${songId} successfully.`);
-				cb({ status: "success", data: song });
-			}
-		});
-	}),
-
-	/**
-	 * Updates a song
-	 *
-	 * @param session
-	 * @param songId - the song id
-	 * @param song - the updated song object
-	 * @param cb
-	 */
-	update: hooks.adminRequired((session, songId, song, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.song.updateOne({_id: songId}, song, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				songs.updateSong(songId, next);
-			}
-		], async (err, song) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("SONGS_UPDATE", `Failed to update song "${songId}". "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("SONGS_UPDATE", `Successfully updated song "${songId}".`);
-			cache.pub('song.updated', song.songId);
-			cb({ status: 'success', message: 'Song has been successfully updated', data: song });
-		});
-	}),
-
-	/**
-	 * Removes a song
-	 *
-	 * @param session
-	 * @param songId - the song id
-	 * @param cb
-	 */
-	remove: hooks.adminRequired((session, songId, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.song.deleteOne({_id: songId}, next);
-			},
-
-			(res, next) => {//TODO Check if res gets returned from above
-				cache.hdel('songs', songId, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("SONGS_UPDATE", `Failed to remove song "${songId}". "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("SONGS_UPDATE", `Successfully remove song "${songId}".`);
-			cache.pub('song.removed', songId);
-			cb({status: 'success', message: 'Song has been successfully updated'});
-		});
-	}),
-
-	/**
-	 * Adds a song
-	 *
-	 * @param session
-	 * @param song - the song object
-	 * @param cb
-	 */
-	add: hooks.adminRequired((session, song, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.song.findOne({songId: song.songId}, next);
-			},
-
-			(existingSong, next) => {
-				if (existingSong) return next('Song is already in rotation.');
-				next();
-			},
-
-			(next) => {
-				const newSong = new db.models.song(song);
-				newSong.acceptedBy = session.userId;
-				newSong.acceptedAt = Date.now();
-				newSong.save(next);
-			},
-
-			(res, next) => {
-				queueSongs.remove(session, song._id, () => {
-					next();
-				});
-			},
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("SONGS_ADD", `User "${session.userId}" failed to add song. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("SONGS_ADD", `User "${session.userId}" successfully added song "${song.songId}".`);
-			cache.pub('song.added', song.songId);
-			cb({status: 'success', message: 'Song has been moved from the queue successfully.'});
-		});
-		//TODO Check if video is in queue and Add the song to the appropriate stations
-	}),
-
-	/**
-	 * Likes a song
-	 *
-	 * @param session
-	 * @param songId - the song id
-	 * @param cb
-	 */
-	like: hooks.loginRequired((session, songId, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.song.findOne({songId}, next);
-			},
-
-			(song, next) => {
-				if (!song) return next('No song found with that id.');
-				next(null, song);
-			}
-		], async (err, song) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("SONGS_LIKE", `User "${session.userId}" failed to like song ${songId}. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			let oldSongId = songId;
-			songId = song._id;
-			db.models.user.findOne({ _id: session.userId }, (err, user) => {
-				if (user.liked.indexOf(songId) !== -1) return cb({ status: 'failure', message: 'You have already liked this song.' });
-				db.models.user.updateOne({_id: session.userId}, {$push: {liked: songId}, $pull: {disliked: songId}}, err => {
-					if (!err) {
-						db.models.user.countDocuments({"liked": songId}, (err, likes) => {
-							if (err) return cb({ status: 'failure', message: 'Something went wrong while liking this song.' });
-							db.models.user.countDocuments({"disliked": songId}, (err, dislikes) => {
-								if (err) return cb({ status: 'failure', message: 'Something went wrong while liking this song.' });
-								db.models.song.update({_id: songId}, {$set: {likes: likes, dislikes: dislikes}}, (err) => {
-									if (err) return cb({ status: 'failure', message: 'Something went wrong while liking this song.' });
-									songs.updateSong(songId, (err, song) => {});
-									cache.pub('song.like', JSON.stringify({ songId: oldSongId, userId: session.userId, likes: likes, dislikes: dislikes }));
-									return cb({ status: 'success', message: 'You have successfully liked this song.' });
-								});
-							});
-						});
-					} else return cb({ status: 'failure', message: 'Something went wrong while liking this song.' });
-				});
-			});
-		});
-	}),
-
-	/**
-	 * Dislikes a song
-	 *
-	 * @param session
-	 * @param songId - the song id
-	 * @param cb
-	 */
-	dislike: hooks.loginRequired((session, songId, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.song.findOne({songId}, next);
-			},
-
-			(song, next) => {
-				if (!song) return next('No song found with that id.');
-				next(null, song);
-			}
-		], async (err, song) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("SONGS_DISLIKE", `User "${session.userId}" failed to like song ${songId}. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			let oldSongId = songId;
-			songId = song._id;
-			db.models.user.findOne({ _id: session.userId }, (err, user) => {
-				if (user.disliked.indexOf(songId) !== -1) return cb({ status: 'failure', message: 'You have already disliked this song.' });
-				db.models.user.updateOne({_id: session.userId}, {$push: {disliked: songId}, $pull: {liked: songId}}, err => {
-					if (!err) {
-						db.models.user.countDocuments({"liked": songId}, (err, likes) => {
-							if (err) return cb({ status: 'failure', message: 'Something went wrong while disliking this song.' });
-							db.models.user.countDocuments({"disliked": songId}, (err, dislikes) => {
-								if (err) return cb({ status: 'failure', message: 'Something went wrong while disliking this song.' });
-								db.models.song.update({_id: songId}, {$set: {likes: likes, dislikes: dislikes}}, (err, res) => {
-									if (err) return cb({ status: 'failure', message: 'Something went wrong while disliking this song.' });
-									songs.updateSong(songId, (err, song) => {});
-									cache.pub('song.dislike', JSON.stringify({ songId: oldSongId, userId: session.userId, likes: likes, dislikes: dislikes }));
-									return cb({ status: 'success', message: 'You have successfully disliked this song.' });
-								});
-							});
-						});
-					} else return cb({ status: 'failure', message: 'Something went wrong while disliking this song.' });
-				});
-			});
-		});
-	}),
-
-	/**
-	 * Undislikes a song
-	 *
-	 * @param session
-	 * @param songId - the song id
-	 * @param cb
-	 */
-	undislike: hooks.loginRequired((session, songId, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.song.findOne({songId}, next);
-			},
-
-			(song, next) => {
-				if (!song) return next('No song found with that id.');
-				next(null, song);
-			}
-		], async (err, song) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("SONGS_UNDISLIKE", `User "${session.userId}" failed to like song ${songId}. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			let oldSongId = songId;
-			songId = song._id;
-			db.models.user.findOne({_id: session.userId}, (err, user) => {
-				if (user.disliked.indexOf(songId) === -1) return cb({
-					status: 'failure',
-					message: 'You have not disliked this song.'
-				});
-				db.models.user.updateOne({_id: session.userId}, {$pull: {liked: songId, disliked: songId}}, err => {
-					if (!err) {
-						db.models.user.countDocuments({"liked": songId}, (err, likes) => {
-							if (err) return cb({
-								status: 'failure',
-								message: 'Something went wrong while undisliking this song.'
-							});
-							db.models.user.countDocuments({"disliked": songId}, (err, dislikes) => {
-								if (err) return cb({
-									status: 'failure',
-									message: 'Something went wrong while undisliking this song.'
-								});
-								db.models.song.update({_id: songId}, {$set: {likes: likes, dislikes: dislikes}}, (err) => {
-									if (err) return cb({
-										status: 'failure',
-										message: 'Something went wrong while undisliking this song.'
-									});
-									songs.updateSong(songId, (err, song) => {
-									});
-									cache.pub('song.undislike', JSON.stringify({
-										songId: oldSongId,
-										userId: session.userId,
-										likes: likes,
-										dislikes: dislikes
-									}));
-									return cb({
-										status: 'success',
-										message: 'You have successfully undisliked this song.'
-									});
-								});
-							});
-						});
-					} else return cb({status: 'failure', message: 'Something went wrong while undisliking this song.'});
-				});
-			});
-		});
-	}),
-
-	/**
-	 * Unlikes a song
-	 *
-	 * @param session
-	 * @param songId - the song id
-	 * @param cb
-	 */
-	unlike: hooks.loginRequired((session, songId, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.song.findOne({songId}, next);
-			},
-
-			(song, next) => {
-				if (!song) return next('No song found with that id.');
-				next(null, song);
-			}
-		], async (err, song) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("SONGS_UNLIKE", `User "${session.userId}" failed to like song ${songId}. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			let oldSongId = songId;
-			songId = song._id;
-			db.models.user.findOne({ _id: session.userId }, (err, user) => {
-				if (user.liked.indexOf(songId) === -1) return cb({ status: 'failure', message: 'You have not liked this song.' });
-				db.models.user.updateOne({_id: session.userId}, {$pull: {liked: songId, disliked: songId}}, err => {
-					if (!err) {
-						db.models.user.countDocuments({"liked": songId}, (err, likes) => {
-							if (err) return cb({ status: 'failure', message: 'Something went wrong while unliking this song.' });
-							db.models.user.countDocuments({"disliked": songId}, (err, dislikes) => {
-								if (err) return cb({ status: 'failure', message: 'Something went wrong while undiking this song.' });
-								db.models.song.updateOne({_id: songId}, {$set: {likes: likes, dislikes: dislikes}}, (err) => {
-									if (err) return cb({ status: 'failure', message: 'Something went wrong while unliking this song.' });
-									songs.updateSong(songId, (err, song) => {});
-									cache.pub('song.unlike', JSON.stringify({ songId: oldSongId, userId: session.userId, likes: likes, dislikes: dislikes }));
-									return cb({ status: 'success', message: 'You have successfully unliked this song.' });
-								});
-							});
-						});
-					} else return cb({ status: 'failure', message: 'Something went wrong while unliking this song.' });
-				});
-			});
-		});
-	}),
-
-	/**
-	 * Gets user's own song ratings
-	 *
-	 * @param session
-	 * @param songId - the song id
-	 * @param cb
-	 */
-	getOwnSongRatings: hooks.loginRequired((session, songId, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.song.findOne({songId}, next);
-			},
-
-			(song, next) => {
-				if (!song) return next('No song found with that id.');
-				next(null, song);
-			}
-		], async (err, song) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("SONGS_GET_OWN_RATINGS", `User "${session.userId}" failed to get ratings for ${songId}. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			let newSongId = song._id;
-			db.models.user.findOne({_id: session.userId}, (err, user) => {
-				if (!err && user) {
-					return cb({
-						status: 'success',
-						songId: songId,
-						liked: (user.liked.indexOf(newSongId) !== -1),
-						disliked: (user.disliked.indexOf(newSongId) !== -1)
-					});
-				} else {
-					return cb({
-						status: 'failure',
-						message: utils.getError(err)
-					});
-				}
-			});
-		});
-	})
+    /**
+     * Gets a set of songs
+     *
+     * @param session
+     * @param set - the set number to return
+     * @param cb
+     */
+    getSet: hooks.adminRequired(async (session, set, cb) => {
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        async.waterfall(
+            [
+                (next) => {
+                    songModel
+                        .find({})
+                        .skip(15 * (set - 1))
+                        .limit(15)
+                        .exec(next);
+                },
+            ],
+            async (err, songs) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "SONGS_GET_SET",
+                        `Failed to get set from songs. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "SONGS_GET_SET",
+                    `Got set from songs successfully.`
+                );
+                cb(songs);
+            }
+        );
+    }),
+
+    /**
+     * Gets a song
+     *
+     * @param session
+     * @param songId - the song id
+     * @param cb
+     */
+    getSong: hooks.adminRequired((session, songId, cb) => {
+        console.log(songId);
+
+        async.waterfall(
+            [
+                (next) => {
+                    song.getSong(songId, next);
+                },
+            ],
+            async (err, song) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "SONGS_GET_SONG",
+                        `Failed to get song ${songId}. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "SONGS_GET_SONG",
+                        `Got song ${songId} successfully.`
+                    );
+                    cb({ status: "success", data: song });
+                }
+            }
+        );
+    }),
+
+    /**
+     * Obtains basic metadata of a song in order to format an activity
+     *
+     * @param session
+     * @param songId - the song id
+     * @param cb
+     */
+    getSongForActivity: (session, songId, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    songs
+                        .runJob("GET_SONG_FROM_ID", { songId })
+                        .then((responsesong) => next(null, response.song))
+                        .catch(next);
+                },
+            ],
+            async (err, song) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "SONGS_GET_SONG_FOR_ACTIVITY",
+                        `Failed to obtain metadata of song ${songId} for activity formatting. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    if (song) {
+                        console.log(
+                            "SUCCESS",
+                            "SONGS_GET_SONG_FOR_ACTIVITY",
+                            `Obtained metadata of song ${songId} for activity formatting successfully.`
+                        );
+                        cb({
+                            status: "success",
+                            data: {
+                                title: song.title,
+                                thumbnail: song.thumbnail,
+                            },
+                        });
+                    } else {
+                        console.log(
+                            "ERROR",
+                            "SONGS_GET_SONG_FOR_ACTIVITY",
+                            `Song ${songId} does not exist so failed to obtain for activity formatting.`
+                        );
+                        cb({ status: "failure" });
+                    }
+                }
+            }
+        );
+    },
+
+    /**
+     * Updates a song
+     *
+     * @param session
+     * @param songId - the song id
+     * @param song - the updated song object
+     * @param cb
+     */
+    update: hooks.adminRequired(async (session, songId, song, cb) => {
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        async.waterfall(
+            [
+                (next) => {
+                    songModel.updateOne(
+                        { _id: songId },
+                        song,
+                        { runValidators: true },
+                        next
+                    );
+                },
+
+                (res, next) => {
+                    songs
+                        .runJob("UPDATE_SONG", { songId })
+                        .then((song) => next(null, song))
+                        .catch(next);
+                },
+            ],
+            async (err, song) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "SONGS_UPDATE",
+                        `Failed to update song "${songId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "SONGS_UPDATE",
+                    `Successfully updated song "${songId}".`
+                );
+                cache.runJob("PUB", {
+                    channel: "song.updated",
+                    value: song.songId,
+                });
+                cb({
+                    status: "success",
+                    message: "Song has been successfully updated",
+                    data: song,
+                });
+            }
+        );
+    }),
+
+    /**
+     * Removes a song
+     *
+     * @param session
+     * @param songId - the song id
+     * @param cb
+     */
+    remove: hooks.adminRequired(async (session, songId, cb) => {
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        async.waterfall(
+            [
+                (next) => {
+                    songModel.deleteOne({ _id: songId }, next);
+                },
+
+                (res, next) => {
+                    //TODO Check if res gets returned from above
+                    cache
+                        .runJob("HDEL", { table: "songs", key: songId })
+                        .then(() => next())
+                        .catch(next);
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "SONGS_UPDATE",
+                        `Failed to remove song "${songId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "SONGS_UPDATE",
+                    `Successfully remove song "${songId}".`
+                );
+                cache.runJob("PUB", { channel: "song.removed", value: songId });
+                cb({
+                    status: "success",
+                    message: "Song has been successfully updated",
+                });
+            }
+        );
+    }),
+
+    /**
+     * Adds a song
+     *
+     * @param session
+     * @param song - the song object
+     * @param cb
+     */
+    add: hooks.adminRequired(async (session, song, cb) => {
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        async.waterfall(
+            [
+                (next) => {
+                    songModel.findOne({ songId: song.songId }, next);
+                },
+
+                (existingSong, next) => {
+                    if (existingSong)
+                        return next("Song is already in rotation.");
+                    next();
+                },
+
+                (next) => {
+                    const newSong = new songModel(song);
+                    newSong.acceptedBy = session.userId;
+                    newSong.acceptedAt = Date.now();
+                    newSong.save(next);
+                },
+
+                (res, next) => {
+                    queueSongs.remove(session, song._id, () => {
+                        next();
+                    });
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "SONGS_ADD",
+                        `User "${session.userId}" failed to add song. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "SONGS_ADD",
+                    `User "${session.userId}" successfully added song "${song.songId}".`
+                );
+                cache.runJob("PUB", {
+                    channel: "song.added",
+                    value: song.songId,
+                });
+                cb({
+                    status: "success",
+                    message: "Song has been moved from the queue successfully.",
+                });
+            }
+        );
+        //TODO Check if video is in queue and Add the song to the appropriate stations
+    }),
+
+    /**
+     * Likes a song
+     *
+     * @param session
+     * @param songId - the song id
+     * @param cb
+     */
+    like: hooks.loginRequired(async (session, songId, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        async.waterfall(
+            [
+                (next) => {
+                    songModel.findOne({ songId }, next);
+                },
+
+                (song, next) => {
+                    if (!song) return next("No song found with that id.");
+                    next(null, song);
+                },
+            ],
+            async (err, song) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "SONGS_LIKE",
+                        `User "${session.userId}" failed to like song ${songId}. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                let oldSongId = songId;
+                songId = song._id;
+                userModel.findOne({ _id: session.userId }, (err, user) => {
+                    if (user.liked.indexOf(songId) !== -1)
+                        return cb({
+                            status: "failure",
+                            message: "You have already liked this song.",
+                        });
+                    userModel.updateOne(
+                        { _id: session.userId },
+                        {
+                            $push: { liked: songId },
+                            $pull: { disliked: songId },
+                        },
+                        (err) => {
+                            if (!err) {
+                                userModel.countDocuments(
+                                    { liked: songId },
+                                    (err, likes) => {
+                                        if (err)
+                                            return cb({
+                                                status: "failure",
+                                                message:
+                                                    "Something went wrong while liking this song.",
+                                            });
+                                        userModel.countDocuments(
+                                            { disliked: songId },
+                                            (err, dislikes) => {
+                                                if (err)
+                                                    return cb({
+                                                        status: "failure",
+                                                        message:
+                                                            "Something went wrong while liking this song.",
+                                                    });
+                                                songModel.update(
+                                                    { _id: songId },
+                                                    {
+                                                        $set: {
+                                                            likes: likes,
+                                                            dislikes: dislikes,
+                                                        },
+                                                    },
+                                                    (err) => {
+                                                        if (err)
+                                                            return cb({
+                                                                status:
+                                                                    "failure",
+                                                                message:
+                                                                    "Something went wrong while liking this song.",
+                                                            });
+                                                        songs.runJob(
+                                                            "UPDATE_SONG",
+                                                            { songId }
+                                                        );
+                                                        cache.runJob("PUB", {
+                                                            channel:
+                                                                "song.like",
+                                                            value: JSON.stringify(
+                                                                {
+                                                                    songId: oldSongId,
+                                                                    userId:
+                                                                        session.userId,
+                                                                    likes: likes,
+                                                                    dislikes: dislikes,
+                                                                }
+                                                            ),
+                                                        });
+                                                        activities.runJob(
+                                                            "ADD_ACTIVITY",
+                                                            {
+                                                                userId:
+                                                                    session.userId,
+                                                                activityType:
+                                                                    "liked_song",
+                                                                payload: [
+                                                                    songId,
+                                                                ],
+                                                            }
+                                                        );
+                                                        return cb({
+                                                            status: "success",
+                                                            message:
+                                                                "You have successfully liked this song.",
+                                                        });
+                                                    }
+                                                );
+                                            }
+                                        );
+                                    }
+                                );
+                            } else
+                                return cb({
+                                    status: "failure",
+                                    message:
+                                        "Something went wrong while liking this song.",
+                                });
+                        }
+                    );
+                });
+            }
+        );
+    }),
+
+    /**
+     * Dislikes a song
+     *
+     * @param session
+     * @param songId - the song id
+     * @param cb
+     */
+    dislike: hooks.loginRequired(async (session, songId, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        async.waterfall(
+            [
+                (next) => {
+                    songModel.findOne({ songId }, next);
+                },
+
+                (song, next) => {
+                    if (!song) return next("No song found with that id.");
+                    next(null, song);
+                },
+            ],
+            async (err, song) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "SONGS_DISLIKE",
+                        `User "${session.userId}" failed to like song ${songId}. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                let oldSongId = songId;
+                songId = song._id;
+                userModel.findOne({ _id: session.userId }, (err, user) => {
+                    if (user.disliked.indexOf(songId) !== -1)
+                        return cb({
+                            status: "failure",
+                            message: "You have already disliked this song.",
+                        });
+                    userModel.updateOne(
+                        { _id: session.userId },
+                        {
+                            $push: { disliked: songId },
+                            $pull: { liked: songId },
+                        },
+                        (err) => {
+                            if (!err) {
+                                userModel.countDocuments(
+                                    { liked: songId },
+                                    (err, likes) => {
+                                        if (err)
+                                            return cb({
+                                                status: "failure",
+                                                message:
+                                                    "Something went wrong while disliking this song.",
+                                            });
+                                        userModel.countDocuments(
+                                            { disliked: songId },
+                                            (err, dislikes) => {
+                                                if (err)
+                                                    return cb({
+                                                        status: "failure",
+                                                        message:
+                                                            "Something went wrong while disliking this song.",
+                                                    });
+                                                songModel.update(
+                                                    { _id: songId },
+                                                    {
+                                                        $set: {
+                                                            likes: likes,
+                                                            dislikes: dislikes,
+                                                        },
+                                                    },
+                                                    (err, res) => {
+                                                        if (err)
+                                                            return cb({
+                                                                status:
+                                                                    "failure",
+                                                                message:
+                                                                    "Something went wrong while disliking this song.",
+                                                            });
+                                                        songs.runJob(
+                                                            "UPDATE_SONG",
+                                                            { songId }
+                                                        );
+                                                        cache.runJob("PUB", {
+                                                            channel:
+                                                                "song.dislike",
+                                                            value: JSON.stringify(
+                                                                {
+                                                                    songId: oldSongId,
+                                                                    userId:
+                                                                        session.userId,
+                                                                    likes: likes,
+                                                                    dislikes: dislikes,
+                                                                }
+                                                            ),
+                                                        });
+                                                        return cb({
+                                                            status: "success",
+                                                            message:
+                                                                "You have successfully disliked this song.",
+                                                        });
+                                                    }
+                                                );
+                                            }
+                                        );
+                                    }
+                                );
+                            } else
+                                return cb({
+                                    status: "failure",
+                                    message:
+                                        "Something went wrong while disliking this song.",
+                                });
+                        }
+                    );
+                });
+            }
+        );
+    }),
+
+    /**
+     * Undislikes a song
+     *
+     * @param session
+     * @param songId - the song id
+     * @param cb
+     */
+    undislike: hooks.loginRequired(async (session, songId, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        async.waterfall(
+            [
+                (next) => {
+                    songModel.findOne({ songId }, next);
+                },
+
+                (song, next) => {
+                    if (!song) return next("No song found with that id.");
+                    next(null, song);
+                },
+            ],
+            async (err, song) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "SONGS_UNDISLIKE",
+                        `User "${session.userId}" failed to like song ${songId}. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                let oldSongId = songId;
+                songId = song._id;
+                userModel.findOne({ _id: session.userId }, (err, user) => {
+                    if (user.disliked.indexOf(songId) === -1)
+                        return cb({
+                            status: "failure",
+                            message: "You have not disliked this song.",
+                        });
+                    userModel.updateOne(
+                        { _id: session.userId },
+                        { $pull: { liked: songId, disliked: songId } },
+                        (err) => {
+                            if (!err) {
+                                userModel.countDocuments(
+                                    { liked: songId },
+                                    (err, likes) => {
+                                        if (err)
+                                            return cb({
+                                                status: "failure",
+                                                message:
+                                                    "Something went wrong while undisliking this song.",
+                                            });
+                                        userModel.countDocuments(
+                                            { disliked: songId },
+                                            (err, dislikes) => {
+                                                if (err)
+                                                    return cb({
+                                                        status: "failure",
+                                                        message:
+                                                            "Something went wrong while undisliking this song.",
+                                                    });
+                                                songModel.update(
+                                                    { _id: songId },
+                                                    {
+                                                        $set: {
+                                                            likes: likes,
+                                                            dislikes: dislikes,
+                                                        },
+                                                    },
+                                                    (err) => {
+                                                        if (err)
+                                                            return cb({
+                                                                status:
+                                                                    "failure",
+                                                                message:
+                                                                    "Something went wrong while undisliking this song.",
+                                                            });
+                                                        songs.runJob(
+                                                            "UPDATE_SONG",
+                                                            { songId }
+                                                        );
+                                                        cache.runJob("PUB", {
+                                                            channel:
+                                                                "song.undislike",
+                                                            value: JSON.stringify(
+                                                                {
+                                                                    songId: oldSongId,
+                                                                    userId:
+                                                                        session.userId,
+                                                                    likes: likes,
+                                                                    dislikes: dislikes,
+                                                                }
+                                                            ),
+                                                        });
+                                                        return cb({
+                                                            status: "success",
+                                                            message:
+                                                                "You have successfully undisliked this song.",
+                                                        });
+                                                    }
+                                                );
+                                            }
+                                        );
+                                    }
+                                );
+                            } else
+                                return cb({
+                                    status: "failure",
+                                    message:
+                                        "Something went wrong while undisliking this song.",
+                                });
+                        }
+                    );
+                });
+            }
+        );
+    }),
+
+    /**
+     * Unlikes a song
+     *
+     * @param session
+     * @param songId - the song id
+     * @param cb
+     */
+    unlike: hooks.loginRequired(async (session, songId, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        async.waterfall(
+            [
+                (next) => {
+                    songModel.findOne({ songId }, next);
+                },
+
+                (song, next) => {
+                    if (!song) return next("No song found with that id.");
+                    next(null, song);
+                },
+            ],
+            async (err, song) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "SONGS_UNLIKE",
+                        `User "${session.userId}" failed to like song ${songId}. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                let oldSongId = songId;
+                songId = song._id;
+                userModel.findOne({ _id: session.userId }, (err, user) => {
+                    if (user.liked.indexOf(songId) === -1)
+                        return cb({
+                            status: "failure",
+                            message: "You have not liked this song.",
+                        });
+                    userModel.updateOne(
+                        { _id: session.userId },
+                        { $pull: { liked: songId, disliked: songId } },
+                        (err) => {
+                            if (!err) {
+                                userModel.countDocuments(
+                                    { liked: songId },
+                                    (err, likes) => {
+                                        if (err)
+                                            return cb({
+                                                status: "failure",
+                                                message:
+                                                    "Something went wrong while unliking this song.",
+                                            });
+                                        userModel.countDocuments(
+                                            { disliked: songId },
+                                            (err, dislikes) => {
+                                                if (err)
+                                                    return cb({
+                                                        status: "failure",
+                                                        message:
+                                                            "Something went wrong while undiking this song.",
+                                                    });
+                                                songModel.updateOne(
+                                                    { _id: songId },
+                                                    {
+                                                        $set: {
+                                                            likes: likes,
+                                                            dislikes: dislikes,
+                                                        },
+                                                    },
+                                                    (err) => {
+                                                        if (err)
+                                                            return cb({
+                                                                status:
+                                                                    "failure",
+                                                                message:
+                                                                    "Something went wrong while unliking this song.",
+                                                            });
+                                                        songs.runJob(
+                                                            "UPDATE_SONG",
+                                                            { songId }
+                                                        );
+                                                        cache.runJob("PUB", {
+                                                            channel:
+                                                                "song.unlike",
+                                                            value: JSON.stringify(
+                                                                {
+                                                                    songId: oldSongId,
+                                                                    userId:
+                                                                        session.userId,
+                                                                    likes: likes,
+                                                                    dislikes: dislikes,
+                                                                }
+                                                            ),
+                                                        });
+                                                        return cb({
+                                                            status: "success",
+                                                            message:
+                                                                "You have successfully unliked this song.",
+                                                        });
+                                                    }
+                                                );
+                                            }
+                                        );
+                                    }
+                                );
+                            } else
+                                return cb({
+                                    status: "failure",
+                                    message:
+                                        "Something went wrong while unliking this song.",
+                                });
+                        }
+                    );
+                });
+            }
+        );
+    }),
+
+    /**
+     * Gets user's own song ratings
+     *
+     * @param session
+     * @param songId - the song id
+     * @param cb
+     */
+    getOwnSongRatings: hooks.loginRequired(async (session, songId, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        const songModel = await db.runJob("GET_MODEL", { modelName: "song" });
+        async.waterfall(
+            [
+                (next) => {
+                    songModel.findOne({ songId }, next);
+                },
+
+                (song, next) => {
+                    if (!song) return next("No song found with that id.");
+                    next(null, song);
+                },
+            ],
+            async (err, song) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "SONGS_GET_OWN_RATINGS",
+                        `User "${session.userId}" failed to get ratings for ${songId}. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                let newSongId = song._id;
+                userModel.findOne(
+                    { _id: session.userId },
+                    async (err, user) => {
+                        if (!err && user) {
+                            return cb({
+                                status: "success",
+                                songId: songId,
+                                liked: user.liked.indexOf(newSongId) !== -1,
+                                disliked:
+                                    user.disliked.indexOf(newSongId) !== -1,
+                            });
+                        } else {
+                            return cb({
+                                status: "failure",
+                                message: await utils.runJob("GET_ERROR", {
+                                    error: err,
+                                }),
+                            });
+                        }
+                    }
+                );
+            }
+        );
+    }),
 };

+ 2349 - 1185
backend/logic/actions/stations.js

@@ -1,1218 +1,2382 @@
-'use strict';
+"use strict";
 
-const async   = require('async'),
-	  request = require('request'),
-	  config  = require('config'),
-	  _		  =  require('underscore')._;
+const async = require("async"),
+    request = require("request"),
+    config = require("config"),
+    _ = require("underscore")._;
 
-const hooks = require('./hooks');
+const hooks = require("./hooks");
 
-const moduleManager = require("../../index");
+const db = require("../db");
+const cache = require("../cache");
+const notifications = require("../notifications");
+const utils = require("../utils");
+const stations = require("../stations");
+const songs = require("../songs");
+const activities = require("../activities");
 
-const db = moduleManager.modules["db"];
-const cache = moduleManager.modules["cache"];
-const notifications = moduleManager.modules["notifications"];
-const utils = moduleManager.modules["utils"];
-const logger = moduleManager.modules["logger"];
-const stations = moduleManager.modules["stations"];
-const songs = moduleManager.modules["songs"];
+// const logger = moduleManager.modules["logger"];
 
 let userList = {};
 let usersPerStation = {};
 let usersPerStationCount = {};
 
-setInterval(() => {
-	let stationsCountUpdated = [];
-	let stationsUpdated = [];
-
-	let oldUsersPerStation = usersPerStation;
-	usersPerStation = {};
-
-	let oldUsersPerStationCount = usersPerStationCount;
-	usersPerStationCount = {};
-
-	async.each(Object.keys(userList), function(socketId, next) {
-		utils.socketFromSession(socketId).then((socket) => {
-			let stationId = userList[socketId];
-			if (!socket || Object.keys(socket.rooms).indexOf(`station.${stationId}`) === -1) {
-				if (stationsCountUpdated.indexOf(stationId) === -1) stationsCountUpdated.push(stationId);
-				if (stationsUpdated.indexOf(stationId) === -1) stationsUpdated.push(stationId);
-				delete userList[socketId];
-				return next();
-			}
-			if (!usersPerStationCount[stationId]) usersPerStationCount[stationId] = 0;
-			usersPerStationCount[stationId]++;
-			if (!usersPerStation[stationId]) usersPerStation[stationId] = [];
-
-			async.waterfall([
-				(next) => {
-					if (!socket.session || !socket.session.sessionId) return next('No session found.');
-					cache.hget('sessions', socket.session.sessionId, next);
-				},
-
-				(session, next) => {
-					if (!session) return next('Session not found.');
-					db.models.user.findOne({_id: session.userId}, next);
-				},
-
-				(user, next) => {
-					if (!user) return next('User not found.');
-					if (usersPerStation[stationId].indexOf(user.username) !== -1) return next('User already in the list.');
-					next(null, user.username);
-				}
-			], (err, username) => {
-				if (!err) {
-					usersPerStation[stationId].push(username);
-				}
-				next();
-			});
-		});
-		//TODO Code to show users
-	}, (err) => {
-		for (let stationId in usersPerStationCount) {
-			if (oldUsersPerStationCount[stationId] !== usersPerStationCount[stationId]) {
-				if (stationsCountUpdated.indexOf(stationId) === -1) stationsCountUpdated.push(stationId);
-			}
-		}
-
-		for (let stationId in usersPerStation) {
-			if (_.difference(usersPerStation[stationId], oldUsersPerStation[stationId]).length > 0 || _.difference(oldUsersPerStation[stationId], usersPerStation[stationId]).length > 0) {
-				if (stationsUpdated.indexOf(stationId) === -1) stationsUpdated.push(stationId);
-			}
-		}
-
-		stationsCountUpdated.forEach((stationId) => {
-			//logger.info("UPDATE_STATION_USER_COUNT", `Updating user count of ${stationId}.`);
-			cache.pub('station.updateUserCount', stationId);
-		});
-
-		stationsUpdated.forEach((stationId) => {
-			//logger.info("UPDATE_STATION_USER_LIST", `Updating user list of ${stationId}.`);
-			cache.pub('station.updateUsers', stationId);
-		});
-
-		//console.log("Userlist", usersPerStation);
-	});
-}, 3000);
-
-cache.sub('station.updateUsers', stationId => {
-	let list = usersPerStation[stationId] || [];
-	utils.emitToRoom(`station.${stationId}`, "event:users.updated", list);
+// Temporarily disabled until the messages in console can be limited
+// setInterval(async () => {
+//     let stationsCountUpdated = [];
+//     let stationsUpdated = [];
+
+//     let oldUsersPerStation = usersPerStation;
+//     usersPerStation = {};
+
+//     let oldUsersPerStationCount = usersPerStationCount;
+//     usersPerStationCount = {};
+
+//     const userModel = await db.runJob("GET_MODEL", {
+//         modelName: "user",
+//     });
+//
+//     async.each(
+//         Object.keys(userList),
+//         function(socketId, next) {
+//             utils.runJob("SOCKET_FROM_SESSION", { socketId }).then((socket) => {
+//                 let stationId = userList[socketId];
+//                 if (
+//                     !socket ||
+//                     Object.keys(socket.rooms).indexOf(
+//                         `station.${stationId}`
+//                     ) === -1
+//                 ) {
+//                     if (stationsCountUpdated.indexOf(stationId) === -1)
+//                         stationsCountUpdated.push(stationId);
+//                     if (stationsUpdated.indexOf(stationId) === -1)
+//                         stationsUpdated.push(stationId);
+//                     delete userList[socketId];
+//                     return next();
+//                 }
+//                 if (!usersPerStationCount[stationId])
+//                     usersPerStationCount[stationId] = 0;
+//                 usersPerStationCount[stationId]++;
+//                 if (!usersPerStation[stationId])
+//                     usersPerStation[stationId] = [];
+
+//                 async.waterfall(
+//                     [
+//                         (next) => {
+//                             if (!socket.session || !socket.session.sessionId)
+//                                 return next("No session found.");
+//                             cache
+//                                 .runJob("HGET", {
+//                                     table: "sessions",
+//                                     key: socket.session.sessionId,
+//                                 })
+//                                 .then((session) => next(null, session))
+//                                 .catch(next);
+//                         },
+
+//                         (session, next) => {
+//                             if (!session) return next("Session not found.");
+//                             userModel.findOne({ _id: session.userId }, next);
+//                         },
+
+//                         (user, next) => {
+//                             if (!user) return next("User not found.");
+//                             if (
+//                                 usersPerStation[stationId].indexOf(
+//                                     user.username
+//                                 ) !== -1
+//                             )
+//                                 return next("User already in the list.");
+//                             next(null, user.username);
+//                         },
+//                     ],
+//                     (err, username) => {
+//                         if (!err) {
+//                             usersPerStation[stationId].push(username);
+//                         }
+//                         next();
+//                     }
+//                 );
+//             });
+//             //TODO Code to show users
+//         },
+//         (err) => {
+//             for (let stationId in usersPerStationCount) {
+//                 if (
+//                     oldUsersPerStationCount[stationId] !==
+//                     usersPerStationCount[stationId]
+//                 ) {
+//                     if (stationsCountUpdated.indexOf(stationId) === -1)
+//                         stationsCountUpdated.push(stationId);
+//                 }
+//             }
+
+//             for (let stationId in usersPerStation) {
+//                 if (
+//                     _.difference(
+//                         usersPerStation[stationId],
+//                         oldUsersPerStation[stationId]
+//                     ).length > 0 ||
+//                     _.difference(
+//                         oldUsersPerStation[stationId],
+//                         usersPerStation[stationId]
+//                     ).length > 0
+//                 ) {
+//                     if (stationsUpdated.indexOf(stationId) === -1)
+//                         stationsUpdated.push(stationId);
+//                 }
+//             }
+
+//             stationsCountUpdated.forEach((stationId) => {
+//                 //console.log("INFO", "UPDATE_STATION_USER_COUNT", `Updating user count of ${stationId}.`);
+//                 cache.runJob("PUB", {
+//                     table: "station.updateUserCount",
+//                     value: stationId,
+//                 });
+//             });
+
+//             stationsUpdated.forEach((stationId) => {
+//                 //console.log("INFO", "UPDATE_STATION_USER_LIST", `Updating user list of ${stationId}.`);
+//                 cache.runJob("PUB", {
+//                     table: "station.updateUsers",
+//                     value: stationId,
+//                 });
+//             });
+
+//             //console.log("Userlist", usersPerStation);
+//         }
+//     );
+// }, 3000);
+
+cache.runJob("SUB", {
+    channel: "station.updateUsers",
+    cb: (stationId) => {
+        let list = usersPerStation[stationId] || [];
+        utils.runJob("EMIT_TO_ROOM", {
+            room: `station.${stationId}`,
+            args: ["event:users.updated", list],
+        });
+    },
 });
 
-cache.sub('station.updateUserCount', stationId => {
-	let count = usersPerStationCount[stationId] || 0;
-	utils.emitToRoom(`station.${stationId}`, "event:userCount.updated", count);
-	stations.getStation(stationId, async (err, station) => {
-		if (station.privacy === 'public') utils.emitToRoom('home', "event:userCount.updated", stationId, count);
-		else {
-			let sockets = await utils.getRoomSockets('home');
-			for (let socketId in sockets) {
-				let socket = sockets[socketId];
-				let session = sockets[socketId].session;
-				if (session.sessionId) {
-					cache.hget('sessions', session.sessionId, (err, session) => {
-						if (!err && session) {
-							db.models.user.findOne({_id: session.userId}, (err, user) => {
-								if (user.role === 'admin') socket.emit("event:userCount.updated", stationId, count);
-								else if (station.type === "community" && station.owner === session.userId) socket.emit("event:userCount.updated", stationId, count);
-							});
-						}
-					});
-				}
-			}
-		}
-	})
+cache.runJob("SUB", {
+    channel: "station.updateUserCount",
+    cb: (stationId) => {
+        let count = usersPerStationCount[stationId] || 0;
+        utils.runJob("EMIT_TO_ROOM", {
+            room: `station.${stationId}`,
+            args: ["event:userCount.updated", count],
+        });
+        stations.runJob("GET_STATION", { stationId }).then(async (station) => {
+            if (station.privacy === "public")
+                utils.runJob("EMIT_TO_ROOM", {
+                    room: "home",
+                    args: ["event:userCount.updated", stationId, count],
+                });
+            else {
+                let sockets = await utils.runJob("GET_ROOM_SOCKETS", {
+                    room: "home",
+                });
+                for (let socketId in sockets) {
+                    let socket = sockets[socketId];
+                    let session = sockets[socketId].session;
+                    if (session.sessionId) {
+                        cache
+                            .runJob("HGET", {
+                                table: "sessions",
+                                key: session.sessionId,
+                            })
+                            .then((session) => {
+                                if (session)
+                                    db.runJob("GET_MODEL", {
+                                        modelName: "user",
+                                    }).then((userModel) =>
+                                        userModel.findOne(
+                                            { _id: session.userId },
+                                            (err, user) => {
+                                                if (user.role === "admin")
+                                                    socket.emit(
+                                                        "event:userCount.updated",
+                                                        stationId,
+                                                        count
+                                                    );
+                                                else if (
+                                                    station.type ===
+                                                        "community" &&
+                                                    station.owner ===
+                                                        session.userId
+                                                )
+                                                    socket.emit(
+                                                        "event:userCount.updated",
+                                                        stationId,
+                                                        count
+                                                    );
+                                            }
+                                        )
+                                    );
+                            });
+                    }
+                }
+            }
+        });
+    },
 });
 
-cache.sub('station.queueLockToggled', data => {
-	utils.emitToRoom(`station.${data.stationId}`, "event:queueLockToggled", data.locked)
+cache.runJob("SUB", {
+    channel: "station.queueLockToggled",
+    cb: (data) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: `station.${data.stationId}`,
+            args: ["event:queueLockToggled", data.locked],
+        });
+    },
 });
 
-cache.sub('station.updatePartyMode', data => {
-	utils.emitToRoom(`station.${data.stationId}`, "event:partyMode.updated", data.partyMode);
+cache.runJob("SUB", {
+    channel: "station.updatePartyMode",
+    cb: (data) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: `station.${data.stationId}`,
+            args: ["event:partyMode.updated", data.partyMode],
+        });
+    },
 });
 
-cache.sub('privatePlaylist.selected', data => {
-	utils.emitToRoom(`station.${data.stationId}`, "event:privatePlaylist.selected", data.playlistId);
+cache.runJob("SUB", {
+    channel: "privatePlaylist.selected",
+    cb: (data) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: `station.${data.stationId}`,
+            args: ["event:privatePlaylist.selected", data.playlistId],
+        });
+    },
 });
 
-cache.sub('station.pause', stationId => {
-	stations.getStation(stationId, (err, station) => {
-		utils.emitToRoom(`station.${stationId}`, "event:stations.pause", { pausedAt: station.pausedAt });
-	});
+cache.runJob("SUB", {
+    channel: "station.pause",
+    cb: (stationId) => {
+        stations.runJob("GET_STATION", { stationId }).then((station) => {
+            utils.runJob("EMIT_TO_ROOM", {
+                room: `station.${stationId}`,
+                args: ["event:stations.pause", { pausedAt: station.pausedAt }],
+            });
+        });
+    },
 });
 
-cache.sub('station.resume', stationId => {
-	stations.getStation(stationId, (err, station) => {
-		utils.emitToRoom(`station.${stationId}`, "event:stations.resume", { timePaused: station.timePaused });
-	});
+cache.runJob("SUB", {
+    channel: "station.resume",
+    cb: (stationId) => {
+        stations.runJob("GET_STATION", { stationId }).then((station) => {
+            utils.runJob("EMIT_TO_ROOM", {
+                room: `station.${stationId}`,
+                args: [
+                    "event:stations.resume",
+                    { timePaused: station.timePaused },
+                ],
+            });
+        });
+    },
 });
 
-cache.sub('station.queueUpdate', stationId => {
-	stations.getStation(stationId, (err, station) => {
-		if (!err) utils.emitToRoom(`station.${stationId}`, "event:queue.update", station.queue);
-	});
+cache.runJob("SUB", {
+    channel: "station.queueUpdate",
+    cb: (stationId) => {
+        stations.runJob("GET_STATION", { stationId }).then((station) => {
+            utils.runJob("EMIT_TO_ROOM", {
+                room: `station.${stationId}`,
+                args: ["event:queue.update", station.queue],
+            });
+        });
+    },
 });
 
-cache.sub('station.voteSkipSong', stationId => {
-	utils.emitToRoom(`station.${stationId}`, "event:song.voteSkipSong");
+cache.runJob("SUB", {
+    channel: "station.voteSkipSong",
+    cb: (stationId) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: `station.${stationId}`,
+            args: ["event:song.voteSkipSong"],
+        });
+    },
 });
 
-cache.sub('station.remove', stationId => {
-	utils.emitToRoom(`station.${stationId}`, 'event:stations.remove');
-	utils.emitToRoom('admin.stations', 'event:admin.station.removed', stationId);
+cache.runJob("SUB", {
+    channel: "station.remove",
+    cb: (stationId) => {
+        utils.runJob("EMIT_TO_ROOM", {
+            room: `station.${stationId}`,
+            args: ["event:stations.remove"],
+        });
+        utils.runJob("EMIT_TO_ROOM", {
+            room: "admin.stations",
+            args: ["event:admin.station.removed", stationId],
+        });
+    },
 });
 
-cache.sub('station.create', stationId => {
-	stations.initializeStation(stationId, async (err, station) => {
-		station.userCount = usersPerStationCount[stationId] || 0;
-		if (err) console.error(err);
-		utils.emitToRoom('admin.stations', 'event:admin.station.added', station);
-		// TODO If community, check if on whitelist
-		if (station.privacy === 'public') utils.emitToRoom('home', "event:stations.created", station);
-		else {
-			let sockets = await utils.getRoomSockets('home');
-			for (let socketId in sockets) {
-				let socket = sockets[socketId];
-				let session = sockets[socketId].session;
-				if (session.sessionId) {
-					cache.hget('sessions', session.sessionId, (err, session) => {
-						if (!err && session) {
-							db.models.user.findOne({_id: session.userId}, (err, user) => {
-								if (user.role === 'admin') socket.emit("event:stations.created", station);
-								else if (station.type === "community" && station.owner === session.userId) socket.emit("event:stations.created", station);
-							});
-						}
-					});
-				}
-			}
-		}
-	});
+cache.runJob("SUB", {
+    channel: "station.create",
+    cb: async (stationId) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+
+        stations
+            .runJob("INITIALIZE_STATION", { stationId })
+            .then(async (response) => {
+                const station = response.station;
+                station.userCount = usersPerStationCount[stationId] || 0;
+                utils.runJob("EMIT_TO_ROOM", {
+                    room: "admin.stations",
+                    args: ["event:admin.station.added", station],
+                });
+                // TODO If community, check if on whitelist
+                if (station.privacy === "public")
+                    utils.runJob("EMIT_TO_ROOM", {
+                        room: "home",
+                        args: ["event:stations.created", station],
+                    });
+                else {
+                    let sockets = await utils.runJob("GET_ROOM_SOCKETS", {
+                        room: "home",
+                    });
+                    for (let socketId in sockets) {
+                        let socket = sockets[socketId];
+                        let session = sockets[socketId].session;
+                        if (session.sessionId) {
+                            cache
+                                .runJob("HGET", {
+                                    table: "sessions",
+                                    key: session.sessionId,
+                                })
+                                .then((session) => {
+                                    if (session) {
+                                        userModel.findOne(
+                                            { _id: session.userId },
+                                            (err, user) => {
+                                                if (user.role === "admin")
+                                                    socket.emit(
+                                                        "event:stations.created",
+                                                        station
+                                                    );
+                                                else if (
+                                                    station.type ===
+                                                        "community" &&
+                                                    station.owner ===
+                                                        session.userId
+                                                )
+                                                    socket.emit(
+                                                        "event:stations.created",
+                                                        station
+                                                    );
+                                            }
+                                        );
+                                    }
+                                });
+                        }
+                    }
+                }
+            });
+    },
 });
 
 module.exports = {
-
-	/**
-	 * Get a list of all the stations
-	 *
-	 * @param session
-	 * @param cb
-	 * @return {{ status: String, stations: Array }}
-	 */
-	index: (session, cb) => {
-		async.waterfall([
-			(next) => {
-				cache.hgetall('stations', next);
-			},
-
-			(stations, next) => {
-				let resultStations = [];
-				for (let id in stations) {
-					resultStations.push(stations[id]);
-				}
-				next(null, stations);
-			},
-
-			(stationsArray, next) => {
-				let resultStations = [];
-				async.each(stationsArray, (station, next) => {
-					async.waterfall([
-						(next) => {
-							stations.canUserViewStation(station, session.userId, (err, exists) => {
-								next(err, exists);
-							});
-						}
-					], (err, exists) => {
-						station.userCount = usersPerStationCount[station._id] || 0;
-						if (exists) resultStations.push(station);
-						next();
-					});
-				}, () => {
-					next(null, resultStations);
-				});
-			}
-		], async (err, stations) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_INDEX", `Indexing stations failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_INDEX", `Indexing stations successful.`, false);
-			return cb({'status': 'success', 'stations': stations});
-		});
-	},
-
-	/**
-	 * Verifies that a station exists
-	 *
-	 * @param session
-	 * @param stationName - the station name
-	 * @param cb
-	 */
-	existsByName: (session, stationName, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStationByName(stationName, next);
-			},
-
-			(station, next) => {
-				if (!station) return next(null, false);
-				stations.canUserViewStation(station, session.userId, (err, exists) => {
-					next(err, exists);
-				});
-			}
-		], async (err, exists) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATION_EXISTS_BY_NAME", `Checking if station "${stationName}" exists failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATION_EXISTS_BY_NAME", `Station "${stationName}" exists successfully.`/*, false*/);
-			cb({status: 'success', exists});
-		});
-	},
-
-	/**
-	 * Gets the official playlist for a station
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param cb
-	 */
-	getPlaylist: (session, stationId, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				stations.canUserViewStation(station, session.userId, (err, canView) => {
-					if (err) return next(err);
-					if (canView) return next(null, station);
-					return next('Insufficient permissions.');
-				});
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				else if (station.type !== 'official') return next('This is not an official station.');
-				else next();
-			},
-
-			(next) => {
-				cache.hget('officialPlaylists', stationId, next);
-			},
-
-			(playlist, next) => {
-				if (!playlist) return next('Playlist not found.');
-				next(null, playlist);
-			}
-		], async (err, playlist) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_GET_PLAYLIST", `Getting playlist for station "${stationId}" failed. "${err}"`);
-				return cb({ status: 'failure', message: err });
-			} else {
-				logger.success("STATIONS_GET_PLAYLIST", `Got playlist for station "${stationId}" successfully.`, false);
-				cb({ status: 'success', data: playlist.songs });
-			}
-		});
-	},
-
-	/**
-	 * Joins the station by its name
-	 *
-	 * @param session
-	 * @param stationName - the station name
-	 * @param cb
-	 * @return {{ status: String, userCount: Integer }}
-	 */
-	join: (session, stationName, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStationByName(stationName, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				stations.canUserViewStation(station, session.userId, (err, canView) => {
-					if (err) return next(err);
-					if (!canView) next("Not allowed to join station.");
-					else next(null, station);
-				});
-			},
-
-			(station, next) => {
-				utils.socketJoinRoom(session.socketId, `station.${station._id}`);
-				let data = {
-					_id: station._id,
-					type: station.type,
-					currentSong: station.currentSong,
-					startedAt: station.startedAt,
-					paused: station.paused,
-					timePaused: station.timePaused,
-					pausedAt: station.pausedAt,
-					description: station.description,
-					displayName: station.displayName,
-					privacy: station.privacy,
-					locked: station.locked,
-					partyMode: station.partyMode,
-					owner: station.owner,
-					privatePlaylist: station.privatePlaylist
-				};
-				userList[session.socketId] = station._id;
-				next(null, data);
-			},
-
-			(data, next) => {
-				data.userCount = usersPerStationCount[data._id] || 0;
-				data.users = usersPerStation[data._id] || [];
-				if (!data.currentSong || !data.currentSong.title) return next(null, data);
-				utils.socketJoinSongRoom(session.socketId, `song.${data.currentSong.songId}`);
-				data.currentSong.skipVotes = data.currentSong.skipVotes.length;
-				songs.getSongFromId(data.currentSong.songId, (err, song) => {
-					if (!err && song) {
-						data.currentSong.likes = song.likes;
-						data.currentSong.dislikes = song.dislikes;
-					} else {
-						data.currentSong.likes = -1;
-						data.currentSong.dislikes = -1;
-					}
-					next(null, data);
-				});
-			}
-		], async (err, data) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_JOIN", `Joining station "${stationName}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_JOIN", `Joined station "${data._id}" successfully.`);
-			cb({status: 'success', data});
-		});
-	},
-
-	/**
-	 * Toggles if a station is locked
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param cb
-	 */
-	toggleLock: hooks.ownerRequired((session, stationId, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				db.models.station.updateOne({ _id: stationId }, { $set: { locked: !station.locked} }, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err, station) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_UPDATE_LOCKED_STATUS", `Toggling the queue lock for station "${stationId}" failed. "${err}"`);
-				return cb({ status: 'failure', message: err });
-			} else {
-				logger.success("STATIONS_UPDATE_LOCKED_STATUS", `Toggled the queue lock for station "${stationId}" successfully to "${station.locked}".`);
-				cache.pub('station.queueLockToggled', {stationId, locked: station.locked});
-				return cb({ status: 'success', data: station.locked });
-			}
-		});
-	}),
-
-	/**
-	 * Votes to skip a station
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param cb
-	 */
-	voteSkip: hooks.loginRequired((session, stationId, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				stations.canUserViewStation(station, session.userId, (err, canView) => {
-					if (err) return next(err);
-					if (canView) return next(null, station);
-					return next('Insufficient permissions.');
-				});
-			},
-
-			(station, next) => {
-				if (!station.currentSong) return next('There is currently no song to skip.');
-				if (station.currentSong.skipVotes.indexOf(session.userId) !== -1) return next('You have already voted to skip this song.');
-				next(null, station);
-			},
-
-			(station, next) => {
-				db.models.station.updateOne({_id: stationId}, {$push: {"currentSong.skipVotes": session.userId}}, next)
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				next(null, station);
-			}
-		], async (err, station) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_VOTE_SKIP", `Vote skipping station "${stationId}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_VOTE_SKIP", `Vote skipping "${stationId}" successful.`);
-			cache.pub('station.voteSkipSong', stationId);
-			if (station.currentSong && station.currentSong.skipVotes.length >= 3) stations.skipStation(stationId)();
-			cb({ status: 'success', message: 'Successfully voted to skip the song.' });
-		});
-	}),
-
-	/**
-	 * Force skips a station
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param cb
-	 */
-	forceSkip: hooks.ownerRequired((session, stationId, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				next();
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_FORCE_SKIP", `Force skipping station "${stationId}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			notifications.unschedule(`stations.nextSong?id=${stationId}`);
-			stations.skipStation(stationId)();
-			logger.success("STATIONS_FORCE_SKIP", `Force skipped station "${stationId}" successfully.`);
-			return cb({'status': 'success', 'message': 'Successfully skipped station.'});
-		});
-	}),
-
-	/**
-	 * Leaves the user's current station
-	 *
-	 * @param session
-	 * @param stationId
-	 * @param cb
-	 * @return {{ status: String, userCount: Integer }}
-	 */
-	leave: (session, stationId, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				next();
-			}
-		], async (err, userCount) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_LEAVE", `Leaving station "${stationId}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_LEAVE", `Left station "${stationId}" successfully.`);
-			utils.socketLeaveRooms(session);
-			delete userList[session.socketId];
-			return cb({'status': 'success', 'message': 'Successfully left station.', userCount});
-		});
-	},
-
-	/**
-	 * Updates a station's name
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param newName - the new station name
-	 * @param cb
-	 */
-	updateName: hooks.ownerRequired((session, stationId, newName, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.station.updateOne({_id: stationId}, {$set: {name: newName}}, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_UPDATE_NAME", `Updating station "${stationId}" name to "${newName}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_UPDATE_NAME", `Updated station "${stationId}" name to "${newName}" successfully.`);
-			return cb({'status': 'success', 'message': 'Successfully updated the name.'});
-		});
-	}),
-
-	/**
-	 * Updates a station's display name
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param newDisplayName - the new station display name
-	 * @param cb
-	 */
-	updateDisplayName: hooks.ownerRequired((session, stationId, newDisplayName, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.station.updateOne({_id: stationId}, {$set: {displayName: newDisplayName}}, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_UPDATE_DISPLAY_NAME", `Updating station "${stationId}" displayName to "${newDisplayName}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_UPDATE_DISPLAY_NAME", `Updated station "${stationId}" displayName to "${newDisplayName}" successfully.`);
-			return cb({'status': 'success', 'message': 'Successfully updated the display name.'});
-		});
-	}),
-
-	/**
-	 * Updates a station's description
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param newDescription - the new station description
-	 * @param cb
-	 */
-	updateDescription: hooks.ownerRequired((session, stationId, newDescription, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.station.updateOne({_id: stationId}, {$set: {description: newDescription}}, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_UPDATE_DESCRIPTION", `Updating station "${stationId}" description to "${newDescription}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_UPDATE_DESCRIPTION", `Updated station "${stationId}" description to "${newDescription}" successfully.`);
-			return cb({'status': 'success', 'message': 'Successfully updated the description.'});
-		});
-	}),
-
-	/**
-	 * Updates a station's privacy
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param newPrivacy - the new station privacy
-	 * @param cb
-	 */
-	updatePrivacy: hooks.ownerRequired((session, stationId, newPrivacy, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.station.updateOne({_id: stationId}, {$set: {privacy: newPrivacy}}, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_UPDATE_PRIVACY", `Updating station "${stationId}" privacy to "${newPrivacy}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_UPDATE_PRIVACY", `Updated station "${stationId}" privacy to "${newPrivacy}" successfully.`);
-			return cb({'status': 'success', 'message': 'Successfully updated the privacy.'});
-		});
-	}),
-
-	/**
-	 * Updates a station's genres
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param newGenres - the new station genres
-	 * @param cb
-	 */
-	updateGenres: hooks.ownerRequired((session, stationId, newGenres, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.station.updateOne({_id: stationId}, {$set: {genres: newGenres}}, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_UPDATE_GENRES", `Updating station "${stationId}" genres to "${newGenres}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_UPDATE_GENRES", `Updated station "${stationId}" genres to "${newGenres}" successfully.`);
-			return cb({'status': 'success', 'message': 'Successfully updated the genres.'});
-		});
-	}),
-
-	/**
-	 * Updates a station's blacklisted genres
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param newBlacklistedGenres - the new station blacklisted genres
-	 * @param cb
-	 */
-	updateBlacklistedGenres: hooks.ownerRequired((session, stationId, newBlacklistedGenres, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.station.updateOne({_id: stationId}, {$set: {blacklistedGenres: newBlacklistedGenres}}, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_UPDATE_BLACKLISTED_GENRES", `Updating station "${stationId}" blacklisted genres to "${newBlacklistedGenres}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_UPDATE_BLACKLISTED_GENRES", `Updated station "${stationId}" blacklisted genres to "${newBlacklistedGenres}" successfully.`);
-			return cb({'status': 'success', 'message': 'Successfully updated the blacklisted genres.'});
-		});
-	}),
-
-	/**
-	 * Updates a station's party mode
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param newPartyMode - the new station party mode
-	 * @param cb
-	 */
-	updatePartyMode: hooks.ownerRequired((session, stationId, newPartyMode, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				if (station.partyMode === newPartyMode) return next('The party mode was already ' + ((newPartyMode) ? 'enabled.' : 'disabled.'));
-				db.models.station.updateOne({_id: stationId}, {$set: {partyMode: newPartyMode}}, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_UPDATE_PARTY_MODE", `Updating station "${stationId}" party mode to "${newPartyMode}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_UPDATE_PARTY_MODE", `Updated station "${stationId}" party mode to "${newPartyMode}" successfully.`);
-			cache.pub('station.updatePartyMode', {stationId: stationId, partyMode: newPartyMode});
-			stations.skipStation(stationId)();
-			return cb({'status': 'success', 'message': 'Successfully updated the party mode.'});
-		});
-	}),
-
-	/**
-	 * Pauses a station
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param cb
-	 */
-	pause: hooks.ownerRequired((session, stationId, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				if (station.paused) return next('That station was already paused.');
-				db.models.station.updateOne({_id: stationId}, {$set: {paused: true, pausedAt: Date.now()}}, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_PAUSE", `Pausing station "${stationId}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_PAUSE", `Paused station "${stationId}" successfully.`);
-			cache.pub('station.pause', stationId);
-			notifications.unschedule(`stations.nextSong?id=${stationId}`);
-			return cb({'status': 'success', 'message': 'Successfully paused.'});
-		});
-	}),
-
-	/**
-	 * Resumes a station
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param cb
-	 */
-	resume: hooks.ownerRequired((session, stationId, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				if (!station.paused) return next('That station is not paused.');
-				station.timePaused += (Date.now() - station.pausedAt);
-				db.models.station.updateOne({_id: stationId}, {$set: {paused: false}, $inc: {timePaused: Date.now() - station.pausedAt}}, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_RESUME", `Resuming station "${stationId}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_RESUME", `Resuming station "${stationId}" successfully.`);
-			cache.pub('station.resume', stationId);
-			return cb({'status': 'success', 'message': 'Successfully resumed.'});
-		});
-	}),
-
-	/**
-	 * Removes a station
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param cb
-	 */
-	remove: hooks.ownerRequired((session, stationId, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.station.deleteOne({ _id: stationId }, err => next(err));
-			},
-
-			(next) => {
-				cache.hdel('stations', stationId, err => next(err));
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_REMOVE", `Removing station "${stationId}" failed. "${err}"`);
-				return cb({ 'status': 'failure', 'message': err });
-			}
-			logger.success("STATIONS_REMOVE", `Removing station "${stationId}" successfully.`);
-			cache.pub('station.remove', stationId);
-			return cb({ 'status': 'success', 'message': 'Successfully removed.' });
-		});
-	}),
-
-	/**
-	 * Create a station
-	 *
-	 * @param session
-	 * @param data - the station data
-	 * @param cb
-	 */
-	create: hooks.loginRequired((session, data, cb) => {
-		data.name = data.name.toLowerCase();
-		let blacklist = ["country", "edm", "musare", "hip-hop", "rap", "top-hits", "todays-hits", "old-school", "christmas", "about", "support", "staff", "help", "news", "terms", "privacy", "profile", "c", "community", "tos", "login", "register", "p", "official", "o", "trap", "faq", "team", "donate", "buy", "shop", "forums", "explore", "settings", "admin", "auth", "reset_password"];
-		async.waterfall([
-			(next) => {
-				if (!data) return next('Invalid data.');
-				next();
-			},
-
-			(next) => {
-				db.models.station.findOne({ $or: [{name: data.name}, {displayName: new RegExp(`^${data.displayName}$`, 'i')}] }, next);
-			},
-
-			(station, next) => {
-				if (station) return next('A station with that name or display name already exists.');
-				const { name, displayName, description, genres, playlist, type, blacklistedGenres } = data;
-				if (type === 'official') {
-					db.models.user.findOne({_id: session.userId}, (err, user) => {
-						if (err) return next(err);
-						if (!user) return next('User not found.');
-						if (user.role !== 'admin') return next('Admin required.');
-						db.models.station.create({
-							name,
-							displayName,
-							description,
-							type,
-							privacy: 'private',
-							playlist,
-							genres,
-							blacklistedGenres,
-							currentSong: stations.defaultSong
-						}, next);
-					});
-				} else if (type === 'community') {
-					if (blacklist.indexOf(name) !== -1) return next('That name is blacklisted. Please use a different name.');
-					db.models.station.create({
-						name,
-						displayName,
-						description,
-						type,
-						privacy: 'private',
-						owner: session.userId,
-						queue: [],
-						currentSong: null
-					}, next);
-				}
-			}
-		], async (err, station) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_CREATE", `Creating station failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_CREATE", `Created station "${station._id}" successfully.`);
-			cache.pub('station.create', station._id);
-			return cb({'status': 'success', 'message': 'Successfully created station.'});
-		});
-	}),
-
-	/**
-	 * Adds song to station queue
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param songId - the song id
-	 * @param cb
-	 */
-	addToQueue: hooks.loginRequired((session, stationId, songId, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				if (station.locked) {
-					db.models.user.findOne({ _id: session.userId }, (err, user) => {
-						if (user.role !== 'admin' && station.owner !== session.userId) return next('Only owners and admins can add songs to a locked queue.');
-						else return next(null, station);
-					});
-				} else {
-					return next(null, station);
-				}
-			},
-
-			(station, next) => {
-				if (station.type !== 'community') return next('That station is not a community station.');
-				stations.canUserViewStation(station, session.userId, (err, canView) => {
-					if (err) return next(err);
-					if (canView) return next(null, station);
-					return next('Insufficient permissions.');
-				});
-			},
-
-			(station, next) => {
-				if (station.currentSong && station.currentSong.songId === songId) return next('That song is currently playing.');
-				async.each(station.queue, (queueSong, next) => {
-					if (queueSong.songId === songId) return next('That song is already in the queue.');
-					next();
-				}, (err) => {
-					next(err, station);
-				});
-			},
-
-			(station, next) => {
-				songs.getSong(songId, (err, song) => {
-					if (!err && song) return next(null, song, station);
-					utils.getSongFromYouTube(songId, (song) => {
-						song.artists = [];
-						song.skipDuration = 0;
-						song.likes = -1;
-						song.dislikes = -1;
-						song.thumbnail = "empty";
-						song.explicit = false;
-						next(null, song, station);
-					});
-				});
-			},
-
-			(song, station, next) => {
-				let queue = station.queue;
-				song.requestedBy = session.userId;
-				queue.push(song);
-
-				let totalDuration = 0;
-				queue.forEach((song) => {
-					totalDuration += song.duration;
-				});
-				if (totalDuration >= 3600 * 3) return next('The max length of the queue is 3 hours.');
-				next(null, song, station);
-			},
-
-			(song, station, next) => {
-				let queue = station.queue;
-				if (queue.length === 0) return next(null, song, station);
-				let totalDuration = 0;
-				const userId = queue[queue.length - 1].requestedBy;
-				station.queue.forEach((song) => {
-					if (userId === song.requestedBy) {
-						totalDuration += song.duration;
-					}
-				});
-
-				if(totalDuration >= 900) return next('The max length of songs per user is 15 minutes.');
-				next(null, song, station);
-			},
-
-			(song, station, next) => {
-				let queue = station.queue;
-				if (queue.length === 0) return next(null, song);
-				let totalSongs = 0;
-				const userId = queue[queue.length - 1].requestedBy;
-				queue.forEach((song) => {
-					if (userId === song.requestedBy) {
-						totalSongs++;
-					}
-				});
-
-				if (totalSongs <= 2) return next(null, song);
-				if (totalSongs > 3) return next('The max amount of songs per user is 3, and only 2 in a row is allowed.');
-				if (queue[queue.length - 2].requestedBy !== userId || queue[queue.length - 3] !== userId) return next('The max amount of songs per user is 3, and only 2 in a row is allowed.');
-				next(null, song);
-			},
-
-			(song, next) => {
-				db.models.station.updateOne({_id: stationId}, {$push: {queue: song}}, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err, station) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_ADD_SONG_TO_QUEUE", `Adding song "${songId}" to station "${stationId}" queue failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_ADD_SONG_TO_QUEUE", `Added song "${songId}" to station "${stationId}" successfully.`);
-			cache.pub('station.queueUpdate', stationId);
-			return cb({'status': 'success', 'message': 'Successfully added song to queue.'});
-		});
-	}),
-
-	/**
-	 * Removes song from station queue
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param songId - the song id
-	 * @param cb
-	 */
-	removeFromQueue: hooks.ownerRequired((session, stationId, songId, cb) => {
-		async.waterfall([
-			(next) => {
-				if (!songId) return next('Invalid song id.');
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				if (station.type !== 'community') return next('Station is not a community station.');
-				async.each(station.queue, (queueSong, next) => {
-					if (queueSong.songId === songId) return next(true);
-					next();
-				}, (err) => {
-					if (err === true) return next();
-					next('Song is not currently in the queue.');
-				});
-			},
-
-			(next) => {
-				db.models.station.updateOne({_id: stationId}, {$pull: {queue: {songId: songId}}}, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err, station) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_REMOVE_SONG_TO_QUEUE", `Removing song "${songId}" from station "${stationId}" queue failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_REMOVE_SONG_TO_QUEUE", `Removed song "${songId}" from station "${stationId}" successfully.`);
-			cache.pub('station.queueUpdate', stationId);
-			return cb({'status': 'success', 'message': 'Successfully removed song from queue.'});
-		});
-	}),
-
-	/**
-	 * Gets the queue from a station
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param cb
-	 */
-	getQueue: (session, stationId, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				if (station.type !== 'community') return next('Station is not a community station.');
-				next(null, station);
-			},
-
-			(station, next) => {
-				stations.canUserViewStation(station, session.userId, (err, canView) => {
-					if (err) return next(err);
-					if (canView) return next(null, station);
-					return next('Insufficient permissions.');
-				});
-			}
-		], async (err, station) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_GET_QUEUE", `Getting queue for station "${stationId}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_GET_QUEUE", `Got queue for station "${stationId}" successfully.`);
-			return cb({'status': 'success', 'message': 'Successfully got queue.', queue: station.queue});
-		});
-	},
-
-	/**
-	 * Selects a private playlist for a station
-	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param playlistId - the private playlist id
-	 * @param cb
-	 */
-	selectPrivatePlaylist: hooks.ownerRequired((session, stationId, playlistId, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				if (station.type !== 'community') return next('Station is not a community station.');
-				if (station.privatePlaylist === playlistId) return next('That private playlist is already selected.');
-				db.models.playlist.findOne({_id: playlistId}, next);
-			},
-
-			(playlist, next) => {
-				if (!playlist) return next('Playlist not found.');
-				let currentSongIndex = (playlist.songs.length > 0) ? playlist.songs.length - 1 : 0;
-				db.models.station.updateOne({_id: stationId}, {$set: {privatePlaylist: playlistId, currentSongIndex: currentSongIndex}}, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				stations.updateStation(stationId, next);
-			}
-		], async (err, station) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("STATIONS_SELECT_PRIVATE_PLAYLIST", `Selecting private playlist "${playlistId}" for station "${stationId}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("STATIONS_SELECT_PRIVATE_PLAYLIST", `Selected private playlist "${playlistId}" for station "${stationId}" successfully.`);
-			notifications.unschedule(`stations.nextSong?id${stationId}`);
-			if (!station.partyMode) stations.skipStation(stationId)();
-			cache.pub('privatePlaylist.selected', {playlistId, stationId});
-			return cb({'status': 'success', 'message': 'Successfully selected playlist.'});
-		});
-	}),
-
-	favoriteStation: hooks.loginRequired((session, stationId, cb) => {
-		async.waterfall([
-			(next) => {
-				stations.getStation(stationId, next);
-			},
-
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				stations.canUserViewStation(station, session.userId, (err, canView) => {
-					if (err) return next(err);
-					if (canView) return next();
-					return next('Insufficient permissions.');
-				});
-			},
-
-			(next) => {
-				db.models.user.updateOne({ _id: session.userId }, { $addToSet: { favoriteStations: stationId } }, next);
-			},
-
-			(res, next) => {
-				if (res.nModified === 0) return next("The station was already favorited.");
-				next();
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("FAVORITE_STATION", `Favoriting station "${stationId}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("FAVORITE_STATION", `Favorited station "${stationId}" successfully.`);
-			cache.pub('user.favoritedStation', { userId: session.userId, stationId });
-			return cb({'status': 'success', 'message': 'Succesfully favorited station.'});
-		});
-	}),
-
-	unfavoriteStation: hooks.loginRequired((session, stationId, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.user.updateOne({ _id: session.userId }, { $pull: { favoriteStations: stationId } }, next);
-			},
-
-			(res, next) => {
-				if (res.nModified === 0) return next("The station wasn't favorited.");
-				next();
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("UNFAVORITE_STATION", `Unfavoriting station "${stationId}" failed. "${err}"`);
-				return cb({'status': 'failure', 'message': err});
-			}
-			logger.success("UNFAVORITE_STATION", `Unfavorited station "${stationId}" successfully.`);
-			cache.pub('user.unfavoritedStation', { userId: session.userId, stationId });
-			return cb({'status': 'success', 'message': 'Succesfully unfavorited station.'});
-		});
-	}),
+    /**
+     * Get a list of all the stations
+     *
+     * @param session
+     * @param cb
+     * @return {{ status: String, stations: Array }}
+     */
+    index: (session, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    console.log(111);
+                    cache
+                        .runJob("HGETALL", { table: "stations" })
+                        .then((stations) => {
+                            next(null, stations);
+                        });
+                },
+
+                (stations, next) => {
+                    console.log(222);
+
+                    let resultStations = [];
+                    for (let id in stations) {
+                        resultStations.push(stations[id]);
+                    }
+                    next(null, stations);
+                },
+
+                (stationsArray, next) => {
+                    console.log(333);
+
+                    let resultStations = [];
+                    async.each(
+                        stationsArray,
+                        (station, next) => {
+                            async.waterfall(
+                                [
+                                    (next) => {
+                                        stations
+                                            .runJob("CAN_USER_VIEW_STATION", {
+                                                station,
+                                                userId: session.userId,
+                                            })
+                                            .then((exists) => {
+                                                console.log(444, exists);
+
+                                                next(null, exists);
+                                            })
+                                            .catch(next);
+                                    },
+                                ],
+                                (err, exists) => {
+                                    if (err) console.log(err);
+                                    station.userCount =
+                                        usersPerStationCount[station._id] || 0;
+                                    if (exists) resultStations.push(station);
+                                    next();
+                                }
+                            );
+                        },
+                        () => {
+                            next(null, resultStations);
+                        }
+                    );
+                },
+            ],
+            async (err, stations) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_INDEX",
+                        `Indexing stations failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "STATIONS_INDEX",
+                    `Indexing stations successful.`,
+                    false
+                );
+                return cb({ status: "success", stations: stations });
+            }
+        );
+    },
+
+    /**
+     * Obtains basic metadata of a station in order to format an activity
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param cb
+     */
+    getStationForActivity: (session, stationId, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+            ],
+            async (err, station) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_GET_STATION_FOR_ACTIVITY",
+                        `Failed to obtain metadata of station ${stationId} for activity formatting. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "STATIONS_GET_STATION_FOR_ACTIVITY",
+                        `Obtained metadata of station ${stationId} for activity formatting successfully.`
+                    );
+                    return cb({
+                        status: "success",
+                        data: {
+                            title: station.displayName,
+                            thumbnail: station.currentSong
+                                ? station.currentSong.thumbnail
+                                : "",
+                        },
+                    });
+                }
+            }
+        );
+    },
+
+    /**
+     * Verifies that a station exists
+     *
+     * @param session
+     * @param stationName - the station name
+     * @param cb
+     */
+    existsByName: (session, stationName, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION_BY_NAME", { stationName })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    if (!station) return next(null, false);
+                    stations
+                        .runJob("CAN_USER_VIEW_STATION", {
+                            station,
+                            userId: session.userId,
+                        })
+                        .then((exists) => next(null, exists))
+                        .catch(next);
+                },
+            ],
+            async (err, exists) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATION_EXISTS_BY_NAME",
+                        `Checking if station "${stationName}" exists failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "STATION_EXISTS_BY_NAME",
+                    `Station "${stationName}" exists successfully.` /*, false*/
+                );
+                cb({ status: "success", exists });
+            }
+        );
+    },
+
+    /**
+     * Gets the official playlist for a station
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param cb
+     */
+    getPlaylist: (session, stationId, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    stations
+                        .runJob("CAN_USER_VIEW_STATION", {
+                            station,
+                            userId: session.userId,
+                        })
+                        .then((canView) => {
+                            if (canView) return next(null, station);
+                            return next("Insufficient permissions.");
+                        })
+                        .catch((err) => {
+                            return next(err);
+                        });
+                },
+
+                (station, next) => {
+                    if (!station) return next("Station not found.");
+                    else if (station.type !== "official")
+                        return next("This is not an official station.");
+                    else next();
+                },
+
+                (next) => {
+                    cache
+                        .runJob("HGET", {
+                            table: "officialPlaylists",
+                            key: stationId,
+                        })
+                        .then((playlist) => next(null, playlist))
+                        .catch(next);
+                },
+
+                (playlist, next) => {
+                    if (!playlist) return next("Playlist not found.");
+                    next(null, playlist);
+                },
+            ],
+            async (err, playlist) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_GET_PLAYLIST",
+                        `Getting playlist for station "${stationId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "STATIONS_GET_PLAYLIST",
+                        `Got playlist for station "${stationId}" successfully.`,
+                        false
+                    );
+                    cb({ status: "success", data: playlist.songs });
+                }
+            }
+        );
+    },
+
+    /**
+     * Joins the station by its name
+     *
+     * @param session
+     * @param stationName - the station name
+     * @param cb
+     * @return {{ status: String, userCount: Integer }}
+     */
+    join: (session, stationName, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION_BY_NAME", { stationName })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    if (!station) return next("Station not found.");
+                    stations
+                        .runJob("CAN_USER_VIEW_STATION", {
+                            station,
+                            userId: session.userId,
+                        })
+                        .then((canView) => {
+                            if (!canView) next("Not allowed to join station.");
+                            else next(null, station);
+                        })
+                        .catch((err) => {
+                            return next(err);
+                        });
+                },
+
+                (station, next) => {
+                    utils.runJob("SOCKET_JOIN_ROOM", {
+                        socketId: session.socketId,
+                        room: `station.${station._id}`,
+                    });
+                    let data = {
+                        _id: station._id,
+                        type: station.type,
+                        currentSong: station.currentSong,
+                        startedAt: station.startedAt,
+                        paused: station.paused,
+                        timePaused: station.timePaused,
+                        pausedAt: station.pausedAt,
+                        description: station.description,
+                        displayName: station.displayName,
+                        privacy: station.privacy,
+                        locked: station.locked,
+                        partyMode: station.partyMode,
+                        owner: station.owner,
+                        privatePlaylist: station.privatePlaylist,
+                    };
+                    userList[session.socketId] = station._id;
+                    next(null, data);
+                },
+
+                (data, next) => {
+                    data = JSON.parse(JSON.stringify(data));
+                    data.userCount = usersPerStationCount[data._id] || 0;
+                    data.users = usersPerStation[data._id] || [];
+                    if (!data.currentSong || !data.currentSong.title)
+                        return next(null, data);
+                    utils.runJob("SOCKET_JOIN_SONG_ROOM", {
+                        socketId: session.socketId,
+                        room: `song.${data.currentSong.songId}`,
+                    });
+                    data.currentSong.skipVotes =
+                        data.currentSong.skipVotes.length;
+                    songs
+                        .runJob("GET_SONG_FROM_ID", {
+                            songId: data.currentSong.songId,
+                        })
+                        .then((response) => {
+                            const song = response.song;
+                            if (song) {
+                                data.currentSong.likes = song.likes;
+                                data.currentSong.dislikes = song.dislikes;
+                            } else {
+                                data.currentSong.likes = -1;
+                                data.currentSong.dislikes = -1;
+                            }
+                        })
+                        .catch((err) => {
+                            data.currentSong.likes = -1;
+                            data.currentSong.dislikes = -1;
+                        })
+                        .finally(() => {
+                            next(null, data);
+                        });
+                },
+            ],
+            async (err, data) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_JOIN",
+                        `Joining station "${stationName}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "STATIONS_JOIN",
+                    `Joined station "${data._id}" successfully.`
+                );
+                cb({ status: "success", data });
+            }
+        );
+    },
+
+    /**
+     * Toggles if a station is locked
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param cb
+     */
+    toggleLock: hooks.ownerRequired(async (session, stationId, cb) => {
+        const stationModel = await db.runJob("GET_MODEL", {
+            modelName: "station",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    stationModel.updateOne(
+                        { _id: stationId },
+                        { $set: { locked: !station.locked } },
+                        next
+                    );
+                },
+
+                (res, next) => {
+                    stations
+                        .runJob("UPDATE_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+            ],
+            async (err, station) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_UPDATE_LOCKED_STATUS",
+                        `Toggling the queue lock for station "${stationId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "STATIONS_UPDATE_LOCKED_STATUS",
+                        `Toggled the queue lock for station "${stationId}" successfully to "${station.locked}".`
+                    );
+                    cache.runJob("PUB", {
+                        channel: "station.queueLockToggled",
+                        value: {
+                            stationId,
+                            locked: station.locked,
+                        },
+                    });
+                    return cb({ status: "success", data: station.locked });
+                }
+            }
+        );
+    }),
+
+    /**
+     * Votes to skip a station
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param cb
+     */
+    voteSkip: hooks.loginRequired(async (session, stationId, cb) => {
+        const stationModel = await db.runJob("GET_MODEL", {
+            modelName: "station",
+        });
+
+        let skipVotes = 0;
+        let shouldSkip = false;
+
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    if (!station) return next("Station not found.");
+                    stations
+                        .runJob("CAN_USER_VIEW_STATION", {
+                            station,
+                            userId: session.userId,
+                        })
+                        .then((canView) => {
+                            if (canView) return next(null, station);
+                            return next("Insufficient permissions.");
+                        })
+                        .catch((err) => {
+                            return next(err);
+                        });
+                },
+
+                (station, next) => {
+                    if (!station.currentSong)
+                        return next("There is currently no song to skip.");
+                    if (
+                        station.currentSong.skipVotes.indexOf(
+                            session.userId
+                        ) !== -1
+                    )
+                        return next(
+                            "You have already voted to skip this song."
+                        );
+                    next(null, station);
+                },
+
+                (station, next) => {
+                    stationModel.updateOne(
+                        { _id: stationId },
+                        { $push: { "currentSong.skipVotes": session.userId } },
+                        next
+                    );
+                },
+
+                (res, next) => {
+                    stations
+                        .runJob("UPDATE_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    if (!station) return next("Station not found.");
+                    next(null, station);
+                },
+
+                (station, next) => {
+                    skipVotes = station.currentSong.skipVotes.length;
+                    utils
+                        .runJob("GET_ROOM_SOCKETS", {
+                            room: `station.${stationId}`,
+                        })
+                        .then((sockets) => next(null, sockets))
+                        .catch(next);
+                },
+
+                (sockets, next) => {
+                    if (sockets.length <= skipVotes) shouldSkip = true;
+                    next();
+                },
+            ],
+            async (err, station) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_VOTE_SKIP",
+                        `Vote skipping station "${stationId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "STATIONS_VOTE_SKIP",
+                    `Vote skipping "${stationId}" successful.`
+                );
+                cache.runJob("PUB", {
+                    channel: "station.voteSkipSong",
+                    value: stationId,
+                });
+                cb({
+                    status: "success",
+                    message: "Successfully voted to skip the song.",
+                });
+                if (shouldSkip) stations.runJob("SKIP_STATION", { stationId });
+            }
+        );
+    }),
+
+    /**
+     * Force skips a station
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param cb
+     */
+    forceSkip: hooks.ownerRequired((session, stationId, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    if (!station) return next("Station not found.");
+                    next();
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_FORCE_SKIP",
+                        `Force skipping station "${stationId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                notifications.runJob("UNSCHEDULE", {
+                    name: `stations.nextSong?id=${stationId}`,
+                });
+                stations.runJob("SKIP_STATION", { stationId });
+                console.log(
+                    "SUCCESS",
+                    "STATIONS_FORCE_SKIP",
+                    `Force skipped station "${stationId}" successfully.`
+                );
+                return cb({
+                    status: "success",
+                    message: "Successfully skipped station.",
+                });
+            }
+        );
+    }),
+
+    /**
+     * Leaves the user's current station
+     *
+     * @param session
+     * @param stationId
+     * @param cb
+     * @return {{ status: String, userCount: Integer }}
+     */
+    leave: (session, stationId, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    if (!station) return next("Station not found.");
+                    next();
+                },
+            ],
+            async (err, userCount) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_LEAVE",
+                        `Leaving station "${stationId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "STATIONS_LEAVE",
+                    `Left station "${stationId}" successfully.`
+                );
+                utils.runJob("SOCKET_LEAVE_ROOMS", { socketId: session });
+                delete userList[session.socketId];
+                return cb({
+                    status: "success",
+                    message: "Successfully left station.",
+                    userCount,
+                });
+            }
+        );
+    },
+
+    /**
+     * Updates a station's name
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param newName - the new station name
+     * @param cb
+     */
+    updateName: hooks.ownerRequired(async (session, stationId, newName, cb) => {
+        const stationModel = await db.runJob("GET_MODEL", {
+            modelName: "station",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    stationModel.updateOne(
+                        { _id: stationId },
+                        { $set: { name: newName } },
+                        { runValidators: true },
+                        next
+                    );
+                },
+
+                (res, next) => {
+                    stations
+                        .runJob("UPDATE_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_UPDATE_NAME",
+                        `Updating station "${stationId}" name to "${newName}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "STATIONS_UPDATE_NAME",
+                    `Updated station "${stationId}" name to "${newName}" successfully.`
+                );
+                return cb({
+                    status: "success",
+                    message: "Successfully updated the name.",
+                });
+            }
+        );
+    }),
+
+    /**
+     * Updates a station's display name
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param newDisplayName - the new station display name
+     * @param cb
+     */
+    updateDisplayName: hooks.ownerRequired(
+        async (session, stationId, newDisplayName, cb) => {
+            const stationModel = await db.runJob("GET_MODEL", {
+                modelName: "station",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        stationModel.updateOne(
+                            { _id: stationId },
+                            { $set: { displayName: newDisplayName } },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        stations
+                            .runJob("UPDATE_STATION", { stationId })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+                ],
+                async (err) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "STATIONS_UPDATE_DISPLAY_NAME",
+                            `Updating station "${stationId}" displayName to "${newDisplayName}" failed. "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    }
+                    console.log(
+                        "SUCCESS",
+                        "STATIONS_UPDATE_DISPLAY_NAME",
+                        `Updated station "${stationId}" displayName to "${newDisplayName}" successfully.`
+                    );
+                    return cb({
+                        status: "success",
+                        message: "Successfully updated the display name.",
+                    });
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates a station's description
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param newDescription - the new station description
+     * @param cb
+     */
+    updateDescription: hooks.ownerRequired(
+        async (session, stationId, newDescription, cb) => {
+            const stationModel = await db.runJob("GET_MODEL", {
+                modelName: "station",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        stationModel.updateOne(
+                            { _id: stationId },
+                            { $set: { description: newDescription } },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        stations
+                            .runJob("UPDATE_STATION", { stationId })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+                ],
+                async (err) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "STATIONS_UPDATE_DESCRIPTION",
+                            `Updating station "${stationId}" description to "${newDescription}" failed. "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    }
+                    console.log(
+                        "SUCCESS",
+                        "STATIONS_UPDATE_DESCRIPTION",
+                        `Updated station "${stationId}" description to "${newDescription}" successfully.`
+                    );
+                    return cb({
+                        status: "success",
+                        message: "Successfully updated the description.",
+                    });
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates a station's privacy
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param newPrivacy - the new station privacy
+     * @param cb
+     */
+    updatePrivacy: hooks.ownerRequired(
+        async (session, stationId, newPrivacy, cb) => {
+            const stationModel = await db.runJob("GET_MODEL", {
+                modelName: "station",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        stationModel.updateOne(
+                            { _id: stationId },
+                            { $set: { privacy: newPrivacy } },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        stations
+                            .runJob("UPDATE_STATION", { stationId })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+                ],
+                async (err) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "STATIONS_UPDATE_PRIVACY",
+                            `Updating station "${stationId}" privacy to "${newPrivacy}" failed. "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    }
+                    console.log(
+                        "SUCCESS",
+                        "STATIONS_UPDATE_PRIVACY",
+                        `Updated station "${stationId}" privacy to "${newPrivacy}" successfully.`
+                    );
+                    return cb({
+                        status: "success",
+                        message: "Successfully updated the privacy.",
+                    });
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates a station's genres
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param newGenres - the new station genres
+     * @param cb
+     */
+    updateGenres: hooks.ownerRequired(
+        async (session, stationId, newGenres, cb) => {
+            const stationModel = await db.runJob("GET_MODEL", {
+                modelName: "station",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        stationModel.updateOne(
+                            { _id: stationId },
+                            { $set: { genres: newGenres } },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        stations
+                            .runJob("UPDATE_STATION", { stationId })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+                ],
+                async (err) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "STATIONS_UPDATE_GENRES",
+                            `Updating station "${stationId}" genres to "${newGenres}" failed. "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    }
+                    console.log(
+                        "SUCCESS",
+                        "STATIONS_UPDATE_GENRES",
+                        `Updated station "${stationId}" genres to "${newGenres}" successfully.`
+                    );
+                    return cb({
+                        status: "success",
+                        message: "Successfully updated the genres.",
+                    });
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates a station's blacklisted genres
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param newBlacklistedGenres - the new station blacklisted genres
+     * @param cb
+     */
+    updateBlacklistedGenres: hooks.ownerRequired(
+        async (session, stationId, newBlacklistedGenres, cb) => {
+            const stationModel = await db.runJob("GET_MODEL", {
+                modelName: "station",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        stationModel.updateOne(
+                            { _id: stationId },
+                            {
+                                $set: {
+                                    blacklistedGenres: newBlacklistedGenres,
+                                },
+                            },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        stations
+                            .runJob("UPDATE_STATION", { stationId })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+                ],
+                async (err) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "STATIONS_UPDATE_BLACKLISTED_GENRES",
+                            `Updating station "${stationId}" blacklisted genres to "${newBlacklistedGenres}" failed. "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    }
+                    console.log(
+                        "SUCCESS",
+                        "STATIONS_UPDATE_BLACKLISTED_GENRES",
+                        `Updated station "${stationId}" blacklisted genres to "${newBlacklistedGenres}" successfully.`
+                    );
+                    return cb({
+                        status: "success",
+                        message: "Successfully updated the blacklisted genres.",
+                    });
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates a station's party mode
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param newPartyMode - the new station party mode
+     * @param cb
+     */
+    updatePartyMode: hooks.ownerRequired(
+        async (session, stationId, newPartyMode, cb) => {
+            const stationModel = await db.runJob("GET_MODEL", {
+                modelName: "station",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        stations
+                            .runJob("GET_STATION", { stationId })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+
+                    (station, next) => {
+                        if (!station) return next("Station not found.");
+                        if (station.partyMode === newPartyMode)
+                            return next(
+                                "The party mode was already " +
+                                    (newPartyMode ? "enabled." : "disabled.")
+                            );
+                        stationModel.updateOne(
+                            { _id: stationId },
+                            { $set: { partyMode: newPartyMode } },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        stations
+                            .runJob("UPDATE_STATION", { stationId })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+                ],
+                async (err) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "STATIONS_UPDATE_PARTY_MODE",
+                            `Updating station "${stationId}" party mode to "${newPartyMode}" failed. "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    }
+                    console.log(
+                        "SUCCESS",
+                        "STATIONS_UPDATE_PARTY_MODE",
+                        `Updated station "${stationId}" party mode to "${newPartyMode}" successfully.`
+                    );
+                    cache.runJob("PUB", {
+                        channel: "station.updatePartyMode",
+                        value: {
+                            stationId: stationId,
+                            partyMode: newPartyMode,
+                        },
+                    });
+                    stations.runJob("SKIP_STATION", { stationId });
+                    return cb({
+                        status: "success",
+                        message: "Successfully updated the party mode.",
+                    });
+                }
+            );
+        }
+    ),
+
+    /**
+     * Pauses a station
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param cb
+     */
+    pause: hooks.ownerRequired(async (session, stationId, cb) => {
+        const stationModel = await db.runJob("GET_MODEL", {
+            modelName: "station",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    if (!station) return next("Station not found.");
+                    if (station.paused)
+                        return next("That station was already paused.");
+                    stationModel.updateOne(
+                        { _id: stationId },
+                        { $set: { paused: true, pausedAt: Date.now() } },
+                        next
+                    );
+                },
+
+                (res, next) => {
+                    stations
+                        .runJob("UPDATE_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_PAUSE",
+                        `Pausing station "${stationId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "STATIONS_PAUSE",
+                    `Paused station "${stationId}" successfully.`
+                );
+                cache.runJob("PUB", {
+                    channel: "station.pause",
+                    value: stationId,
+                });
+                notifications.runJob("UNSCHEDULE", {
+                    name: `stations.nextSong?id=${stationId}`,
+                });
+                return cb({
+                    status: "success",
+                    message: "Successfully paused.",
+                });
+            }
+        );
+    }),
+
+    /**
+     * Resumes a station
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param cb
+     */
+    resume: hooks.ownerRequired(async (session, stationId, cb) => {
+        const stationModel = await db.runJob("GET_MODEL", {
+            modelName: "station",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    if (!station) return next("Station not found.");
+                    if (!station.paused)
+                        return next("That station is not paused.");
+                    station.timePaused += Date.now() - station.pausedAt;
+                    stationModel.updateOne(
+                        { _id: stationId },
+                        {
+                            $set: { paused: false },
+                            $inc: { timePaused: Date.now() - station.pausedAt },
+                        },
+                        next
+                    );
+                },
+
+                (res, next) => {
+                    stations
+                        .runJob("UPDATE_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_RESUME",
+                        `Resuming station "${stationId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "STATIONS_RESUME",
+                    `Resuming station "${stationId}" successfully.`
+                );
+                cache.runJob("PUB", {
+                    channel: "station.resume",
+                    value: stationId,
+                });
+                return cb({
+                    status: "success",
+                    message: "Successfully resumed.",
+                });
+            }
+        );
+    }),
+
+    /**
+     * Removes a station
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param cb
+     */
+    remove: hooks.ownerRequired(async (session, stationId, cb) => {
+        const stationModel = await db.runJob("GET_MODEL", {
+            modelName: "station",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    stationModel.deleteOne({ _id: stationId }, (err) =>
+                        next(err)
+                    );
+                },
+
+                (next) => {
+                    cache
+                        .runJob("HDEL", { table: "stations", key: stationId })
+                        .then(next)
+                        .catch(next);
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_REMOVE",
+                        `Removing station "${stationId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "STATIONS_REMOVE",
+                    `Removing station "${stationId}" successfully.`
+                );
+                cache.runJob("PUB", {
+                    channel: "station.remove",
+                    value: stationId,
+                });
+                activities.runJob("ADD_ACTIVITY", {
+                    userId: session.userId,
+                    activityType: "deleted_station",
+                    payload: [stationId],
+                });
+                return cb({
+                    status: "success",
+                    message: "Successfully removed.",
+                });
+            }
+        );
+    }),
+
+    /**
+     * Create a station
+     *
+     * @param session
+     * @param data - the station data
+     * @param cb
+     */
+    create: hooks.loginRequired(async (session, data, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        const stationModel = await db.runJob("GET_MODEL", {
+            modelName: "station",
+        });
+
+        data.name = data.name.toLowerCase();
+        let blacklist = [
+            "country",
+            "edm",
+            "musare",
+            "hip-hop",
+            "rap",
+            "top-hits",
+            "todays-hits",
+            "old-school",
+            "christmas",
+            "about",
+            "support",
+            "staff",
+            "help",
+            "news",
+            "terms",
+            "privacy",
+            "profile",
+            "c",
+            "community",
+            "tos",
+            "login",
+            "register",
+            "p",
+            "official",
+            "o",
+            "trap",
+            "faq",
+            "team",
+            "donate",
+            "buy",
+            "shop",
+            "forums",
+            "explore",
+            "settings",
+            "admin",
+            "auth",
+            "reset_password",
+        ];
+        async.waterfall(
+            [
+                (next) => {
+                    if (!data) return next("Invalid data.");
+                    next();
+                },
+
+                (next) => {
+                    stationModel.findOne(
+                        {
+                            $or: [
+                                { name: data.name },
+                                {
+                                    displayName: new RegExp(
+                                        `^${data.displayName}$`,
+                                        "i"
+                                    ),
+                                },
+                            ],
+                        },
+                        next
+                    );
+                },
+
+                (station, next) => {
+                    if (station)
+                        return next(
+                            "A station with that name or display name already exists."
+                        );
+                    const {
+                        name,
+                        displayName,
+                        description,
+                        genres,
+                        playlist,
+                        type,
+                        blacklistedGenres,
+                    } = data;
+                    if (type === "official") {
+                        userModel.findOne(
+                            { _id: session.userId },
+                            (err, user) => {
+                                if (err) return next(err);
+                                if (!user) return next("User not found.");
+                                if (user.role !== "admin")
+                                    return next("Admin required.");
+                                stationModel.create(
+                                    {
+                                        name,
+                                        displayName,
+                                        description,
+                                        type,
+                                        privacy: "private",
+                                        playlist,
+                                        genres,
+                                        blacklistedGenres,
+                                        currentSong: stations.defaultSong,
+                                    },
+                                    next
+                                );
+                            }
+                        );
+                    } else if (type === "community") {
+                        if (blacklist.indexOf(name) !== -1)
+                            return next(
+                                "That name is blacklisted. Please use a different name."
+                            );
+                        stationModel.create(
+                            {
+                                name,
+                                displayName,
+                                description,
+                                type,
+                                privacy: "private",
+                                owner: session.userId,
+                                queue: [],
+                                currentSong: null,
+                            },
+                            next
+                        );
+                    }
+                },
+            ],
+            async (err, station) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_CREATE",
+                        `Creating station failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "STATIONS_CREATE",
+                    `Created station "${station._id}" successfully.`
+                );
+                cache.runJob("PUB", {
+                    channel: "station.create",
+                    value: station._id,
+                });
+                activities.runJob("ADD_ACTIVITY", {
+                    userId: session.userId,
+                    activityType: "created_station",
+                    payload: [station._id],
+                });
+                return cb({
+                    status: "success",
+                    message: "Successfully created station.",
+                });
+            }
+        );
+    }),
+
+    /**
+     * Adds song to station queue
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param songId - the song id
+     * @param cb
+     */
+    addToQueue: hooks.loginRequired(async (session, stationId, songId, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        const stationModel = await db.runJob("GET_MODEL", {
+            modelName: "station",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    if (!station) return next("Station not found.");
+                    if (station.locked) {
+                        userModel.findOne(
+                            { _id: session.userId },
+                            (err, user) => {
+                                if (
+                                    user.role !== "admin" &&
+                                    station.owner !== session.userId
+                                )
+                                    return next(
+                                        "Only owners and admins can add songs to a locked queue."
+                                    );
+                                else return next(null, station);
+                            }
+                        );
+                    } else {
+                        return next(null, station);
+                    }
+                },
+
+                (station, next) => {
+                    if (station.type !== "community")
+                        return next("That station is not a community station.");
+                    stations
+                        .runJob("CAN_USER_VIEW_STATION", {
+                            station,
+                            userId: session.userId,
+                        })
+                        .then((canView) => {
+                            if (canView) return next(null, station);
+                            return next("Insufficient permissions.");
+                        })
+                        .catch((err) => {
+                            return next(err);
+                        });
+                },
+
+                (station, next) => {
+                    if (
+                        station.currentSong &&
+                        station.currentSong.songId === songId
+                    )
+                        return next("That song is currently playing.");
+                    async.each(
+                        station.queue,
+                        (queueSong, next) => {
+                            if (queueSong.songId === songId)
+                                return next(
+                                    "That song is already in the queue."
+                                );
+                            next();
+                        },
+                        (err) => {
+                            next(err, station);
+                        }
+                    );
+                },
+
+                (station, next) => {
+                    // songs
+                    //     .runJob("GET_SONG", { id: songId })
+                    //     .then((song) => {
+                    //         if (song) return next(null, song, station);
+                    //         else {
+                    utils
+                        .runJob("GET_SONG_FROM_YOUTUBE", { songId })
+                        .then((response) => {
+                            const song = response.song;
+                            song.artists = [];
+                            song.skipDuration = 0;
+                            song.likes = -1;
+                            song.dislikes = -1;
+                            song.thumbnail = "empty";
+                            song.explicit = false;
+                            next(null, song, station);
+                        })
+                        .catch((err) => {
+                            next(err);
+                        });
+                    //     }
+                    // })
+                    // .catch((err) => {
+                    //     next(err);
+                    // });
+                },
+
+                (song, station, next) => {
+                    let queue = station.queue;
+                    song.requestedBy = session.userId;
+                    queue.push(song);
+
+                    let totalDuration = 0;
+                    queue.forEach((song) => {
+                        totalDuration += song.duration;
+                    });
+                    if (totalDuration >= 3600 * 3)
+                        return next("The max length of the queue is 3 hours.");
+                    next(null, song, station);
+                },
+
+                (song, station, next) => {
+                    let queue = station.queue;
+                    if (queue.length === 0) return next(null, song, station);
+                    let totalDuration = 0;
+                    const userId = queue[queue.length - 1].requestedBy;
+                    station.queue.forEach((song) => {
+                        if (userId === song.requestedBy) {
+                            totalDuration += song.duration;
+                        }
+                    });
+
+                    if (totalDuration >= 900)
+                        return next(
+                            "The max length of songs per user is 15 minutes."
+                        );
+                    next(null, song, station);
+                },
+
+                (song, station, next) => {
+                    let queue = station.queue;
+                    if (queue.length === 0) return next(null, song);
+                    let totalSongs = 0;
+                    const userId = queue[queue.length - 1].requestedBy;
+                    queue.forEach((song) => {
+                        if (userId === song.requestedBy) {
+                            totalSongs++;
+                        }
+                    });
+
+                    if (totalSongs <= 2) return next(null, song);
+                    if (totalSongs > 3)
+                        return next(
+                            "The max amount of songs per user is 3, and only 2 in a row is allowed."
+                        );
+                    if (
+                        queue[queue.length - 2].requestedBy !== userId ||
+                        queue[queue.length - 3] !== userId
+                    )
+                        return next(
+                            "The max amount of songs per user is 3, and only 2 in a row is allowed."
+                        );
+                    next(null, song);
+                },
+
+                (song, next) => {
+                    stationModel.updateOne(
+                        { _id: stationId },
+                        { $push: { queue: song } },
+                        { runValidators: true },
+                        next
+                    );
+                },
+
+                (res, next) => {
+                    stations
+                        .runJob("UPDATE_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+            ],
+            async (err, station) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_ADD_SONG_TO_QUEUE",
+                        `Adding song "${songId}" to station "${stationId}" queue failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "STATIONS_ADD_SONG_TO_QUEUE",
+                    `Added song "${songId}" to station "${stationId}" successfully.`
+                );
+                cache.runJob("PUB", {
+                    channel: "station.queueUpdate",
+                    value: stationId,
+                });
+                return cb({
+                    status: "success",
+                    message: "Successfully added song to queue.",
+                });
+            }
+        );
+    }),
+
+    /**
+     * Removes song from station queue
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param songId - the song id
+     * @param cb
+     */
+    removeFromQueue: hooks.ownerRequired(
+        async (session, stationId, songId, cb) => {
+            const stationModel = await db.runJob("GET_MODEL", {
+                modelName: "station",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        if (!songId) return next("Invalid song id.");
+                        stations
+                            .runJob("GET_STATION", { stationId })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+
+                    (station, next) => {
+                        if (!station) return next("Station not found.");
+                        if (station.type !== "community")
+                            return next("Station is not a community station.");
+                        async.each(
+                            station.queue,
+                            (queueSong, next) => {
+                                if (queueSong.songId === songId)
+                                    return next(true);
+                                next();
+                            },
+                            (err) => {
+                                if (err === true) return next();
+                                next("Song is not currently in the queue.");
+                            }
+                        );
+                    },
+
+                    (next) => {
+                        stationModel.updateOne(
+                            { _id: stationId },
+                            { $pull: { queue: { songId: songId } } },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        stations
+                            .runJob("UPDATE_STATION", { stationId })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+                ],
+                async (err, station) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "STATIONS_REMOVE_SONG_TO_QUEUE",
+                            `Removing song "${songId}" from station "${stationId}" queue failed. "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    }
+                    console.log(
+                        "SUCCESS",
+                        "STATIONS_REMOVE_SONG_TO_QUEUE",
+                        `Removed song "${songId}" from station "${stationId}" successfully.`
+                    );
+                    cache.runJob("PUB", {
+                        channel: "station.queueUpdate",
+                        value: stationId,
+                    });
+                    return cb({
+                        status: "success",
+                        message: "Successfully removed song from queue.",
+                    });
+                }
+            );
+        }
+    ),
+
+    /**
+     * Gets the queue from a station
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param cb
+     */
+    getQueue: (session, stationId, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    if (!station) return next("Station not found.");
+                    if (station.type !== "community")
+                        return next("Station is not a community station.");
+                    next(null, station);
+                },
+
+                (station, next) => {
+                    stations
+                        .runJob("CAN_USER_VIEW_STATION", {
+                            station,
+                            userId: session.userId,
+                        })
+                        .then((canView) => {
+                            if (canView) return next(null, station);
+                            return next("Insufficient permissions.");
+                        })
+                        .catch((err) => {
+                            return next(err);
+                        });
+                },
+            ],
+            async (err, station) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "STATIONS_GET_QUEUE",
+                        `Getting queue for station "${stationId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "STATIONS_GET_QUEUE",
+                    `Got queue for station "${stationId}" successfully.`
+                );
+                return cb({
+                    status: "success",
+                    message: "Successfully got queue.",
+                    queue: station.queue,
+                });
+            }
+        );
+    },
+
+    /**
+     * Selects a private playlist for a station
+     *
+     * @param session
+     * @param stationId - the station id
+     * @param playlistId - the private playlist id
+     * @param cb
+     */
+    selectPrivatePlaylist: hooks.ownerRequired(
+        async (session, stationId, playlistId, cb) => {
+            const stationModel = await db.runJob("GET_MODEL", {
+                modelName: "station",
+            });
+            const playlistModel = await db.runJob("GET_MODEL", {
+                modelName: "playlist",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        stations
+                            .runJob("GET_STATION", { stationId })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+
+                    (station, next) => {
+                        if (!station) return next("Station not found.");
+                        if (station.type !== "community")
+                            return next("Station is not a community station.");
+                        if (station.privatePlaylist === playlistId)
+                            return next(
+                                "That private playlist is already selected."
+                            );
+                        playlistModel.findOne({ _id: playlistId }, next);
+                    },
+
+                    (playlist, next) => {
+                        if (!playlist) return next("Playlist not found.");
+                        let currentSongIndex =
+                            playlist.songs.length > 0
+                                ? playlist.songs.length - 1
+                                : 0;
+                        stationModel.updateOne(
+                            { _id: stationId },
+                            {
+                                $set: {
+                                    privatePlaylist: playlistId,
+                                    currentSongIndex: currentSongIndex,
+                                },
+                            },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        stations
+                            .runJob("UPDATE_STATION", { stationId })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+                ],
+                async (err, station) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "STATIONS_SELECT_PRIVATE_PLAYLIST",
+                            `Selecting private playlist "${playlistId}" for station "${stationId}" failed. "${err}"`
+                        );
+                        return cb({ status: "failure", message: err });
+                    }
+                    console.log(
+                        "SUCCESS",
+                        "STATIONS_SELECT_PRIVATE_PLAYLIST",
+                        `Selected private playlist "${playlistId}" for station "${stationId}" successfully.`
+                    );
+                    notifications.runJob("UNSCHEDULE", {
+                        name: `stations.nextSong?id${stationId}`,
+                    });
+                    if (!station.partyMode)
+                        stations.runJob("SKIP_STATION", { stationId });
+                    cache.runJob("PUB", {
+                        channel: "privatePlaylist.selected",
+                        value: {
+                            playlistId,
+                            stationId,
+                        },
+                    });
+                    return cb({
+                        status: "success",
+                        message: "Successfully selected playlist.",
+                    });
+                }
+            );
+        }
+    ),
+
+    favoriteStation: hooks.loginRequired(async (session, stationId, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    stations
+                        .runJob("GET_STATION", { stationId })
+                        .then((station) => next(null, station))
+                        .catch(next);
+                },
+
+                (station, next) => {
+                    if (!station) return next("Station not found.");
+                    stations
+                        .runJob("CAN_USER_VIEW_STATION", {
+                            station,
+                            userId: session.userId,
+                        })
+                        .then((canView) => {
+                            if (canView) return next();
+                            return next("Insufficient permissions.");
+                        })
+                        .catch((err) => {
+                            return next(err);
+                        });
+                },
+
+                (next) => {
+                    userModel.updateOne(
+                        { _id: session.userId },
+                        { $addToSet: { favoriteStations: stationId } },
+                        next
+                    );
+                },
+
+                (res, next) => {
+                    if (res.nModified === 0)
+                        return next("The station was already favorited.");
+                    next();
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "FAVORITE_STATION",
+                        `Favoriting station "${stationId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "FAVORITE_STATION",
+                    `Favorited station "${stationId}" successfully.`
+                );
+                cache.runJob("PUB", {
+                    channel: "user.favoritedStation",
+                    value: {
+                        userId: session.userId,
+                        stationId,
+                    },
+                });
+                return cb({
+                    status: "success",
+                    message: "Succesfully favorited station.",
+                });
+            }
+        );
+    }),
+
+    unfavoriteStation: hooks.loginRequired(async (session, stationId, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    userModel.updateOne(
+                        { _id: session.userId },
+                        { $pull: { favoriteStations: stationId } },
+                        next
+                    );
+                },
+
+                (res, next) => {
+                    if (res.nModified === 0)
+                        return next("The station wasn't favorited.");
+                    next();
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "UNFAVORITE_STATION",
+                        `Unfavoriting station "${stationId}" failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "UNFAVORITE_STATION",
+                    `Unfavorited station "${stationId}" successfully.`
+                );
+                cache.runJob("PUB", {
+                    channel: "user.unfavoritedStation",
+                    value: {
+                        userId: session.userId,
+                        stationId,
+                    },
+                });
+                return cb({
+                    status: "success",
+                    message: "Succesfully unfavorited station.",
+                });
+            }
+        );
+    }),
 };

+ 2072 - 1139
backend/logic/actions/users.js

@@ -1,1159 +1,2092 @@
-'use strict';
-
-const async = require('async');
-const config = require('config');
-const request = require('request');
-const bcrypt = require('bcrypt');
-const sha256 = require('sha256');
-
-const hooks = require('./hooks');
-
-const moduleManager = require("../../index");
-
-const db = moduleManager.modules["db"];
-const mail = moduleManager.modules["mail"];
-const cache = moduleManager.modules["cache"];
-const punishments = moduleManager.modules["punishments"];
-const utils = moduleManager.modules["utils"];
-const logger = moduleManager.modules["logger"];
-
-cache.sub('user.updateUsername', user => {
-	utils.socketsFromUser(user._id, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:user.username.changed', user.username);
-		});
-	});
+"use strict";
+
+const async = require("async");
+const config = require("config");
+const request = require("request");
+const bcrypt = require("bcrypt");
+const sha256 = require("sha256");
+
+const hooks = require("./hooks");
+
+// const moduleManager = require("../../index");
+
+const db = require("../db");
+const mail = require("../mail");
+const cache = require("../cache");
+const punishments = require("../punishments");
+const utils = require("../utils");
+// const logger = require("../logger");
+const activities = require("../activities");
+
+cache.runJob("SUB", {
+    channel: "user.updateUsername",
+    cb: (user) => {
+        utils.runJob("SOCKETS_FROM_USER", {
+            userId: user._id,
+            cb: (response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:user.username.changed", user.username);
+                });
+            },
+        });
+    },
 });
 
-cache.sub('user.removeSessions', userId => {
-	utils.socketsFromUserWithoutCache(userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('keep.event:user.session.removed');
-		});
-	});
+cache.runJob("SUB", {
+    channel: "user.removeSessions",
+    cb: (userId) => {
+        utils.runJob("SOCKETS_FROM_USER_WITHOUT_CACHE", {
+            userId: userId,
+            cb: (response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("keep.event:user.session.removed");
+                });
+            },
+        });
+    },
 });
 
-cache.sub('user.linkPassword', userId => {
-	utils.socketsFromUser(userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:user.linkPassword');
-		});
-	});
+cache.runJob("SUB", {
+    channel: "user.linkPassword",
+    cb: (userId) => {
+        utils.runJob("SOCKETS_FROM_USER", {
+            userId: userId,
+            cb: (response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:user.linkPassword");
+                });
+            },
+        });
+    },
 });
 
-cache.sub('user.linkGitHub', userId => {
-	utils.socketsFromUser(userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:user.linkGitHub');
-		});
-	});
+cache.runJob("SUB", {
+    channel: "user.linkGitHub",
+    cb: (userId) => {
+        utils.runJob("SOCKETS_FROM_USER", {
+            userId: userId,
+            cb: (response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:user.linkGitHub");
+                });
+            },
+        });
+    },
 });
 
-cache.sub('user.unlinkPassword', userId => {
-	utils.socketsFromUser(userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:user.unlinkPassword');
-		});
-	});
+cache.runJob("SUB", {
+    channel: "user.unlinkPassword",
+    cb: (userId) => {
+        utils.runJob("SOCKETS_FROM_USER", {
+            userId: userId,
+            cb: (response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:user.unlinkPassword");
+                });
+            },
+        });
+    },
 });
 
-cache.sub('user.unlinkGitHub', userId => {
-	utils.socketsFromUser(userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:user.unlinkGitHub');
-		});
-	});
+cache.runJob("SUB", {
+    channel: "user.unlinkGitHub",
+    cb: (userId) => {
+        utils.runJob("SOCKETS_FROM_USER", {
+            userId: userId,
+            cb: (response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:user.unlinkGitHub");
+                });
+            },
+        });
+    },
 });
 
-cache.sub('user.ban', data => {
-	utils.socketsFromUser(data.userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('keep.event:banned', data.punishment);
-			socket.disconnect(true);
-		});
-	});
+cache.runJob("SUB", {
+    channel: "user.ban",
+    cb: (data) => {
+        utils.runJob("SOCKETS_FROM_USER", {
+            userId: data.userId,
+            cb: (response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("keep.event:banned", data.punishment);
+                    socket.disconnect(true);
+                });
+            },
+        });
+    },
 });
 
-cache.sub('user.favoritedStation', data => {
-	utils.socketsFromUser(data.userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:user.favoritedStation', data.stationId);
-		});
-	});
+cache.runJob("SUB", {
+    channel: "user.favoritedStation",
+    cb: (data) => {
+        utils.runJob("SOCKETS_FROM_USER", {
+            userId: data.userId,
+            cb: (response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit("event:user.favoritedStation", data.stationId);
+                });
+            },
+        });
+    },
 });
 
-cache.sub('user.unfavoritedStation', data => {
-	utils.socketsFromUser(data.userId, sockets => {
-		sockets.forEach(socket => {
-			socket.emit('event:user.unfavoritedStation', data.stationId);
-		});
-	});
+cache.runJob("SUB", {
+    channel: "user.unfavoritedStation",
+    cb: (data) => {
+        utils.runJob("SOCKETS_FROM_USER", {
+            userId: data.userId,
+            cb: (response) => {
+                response.sockets.forEach((socket) => {
+                    socket.emit(
+                        "event:user.unfavoritedStation",
+                        data.stationId
+                    );
+                });
+            },
+        });
+    },
 });
 
 module.exports = {
-
-	/**
-	 * Lists all Users
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Function} cb - gets called with the result
-	 */
-	index: hooks.adminRequired((session, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.user.find({}).exec(next);
-			}
-		], async (err, users) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("USER_INDEX", `Indexing users failed. "${err}"`);
-				return cb({status: 'failure', message: err});
-			} else {
-				logger.success("USER_INDEX", `Indexing users successful.`);
-				let filteredUsers = [];
-				users.forEach(user => {
-					filteredUsers.push({
-						_id: user._id,
-						username: user.username,
-						role: user.role,
-						liked: user.liked,
-						disliked: user.disliked,
-						songsRequested: user.statistics.songsRequested,
-						email: {
-							address: user.email.address,
-							verified: user.email.verified
-						},
-						hasPassword: !!user.services.password,
-						services: { github: user.services.github }
-					});
-				});
-				return cb({ status: 'success', data: filteredUsers });
-			}
-		});
-	}),
-
-	/**
-	 * Logs user in
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} identifier - the email of the user
-	 * @param {String} password - the plaintext of the user
-	 * @param {Function} cb - gets called with the result
-	 */
-	login: (session, identifier, password, cb) => {
-
-		identifier = identifier.toLowerCase();
-
-		async.waterfall([
-
-			// check if a user with the requested identifier exists
-			(next) => {
-				db.models.user.findOne({
-					$or: [{ 'email.address': identifier }]
-				}, next)
-			},
-
-			// if the user doesn't exist, respond with a failure
-			// otherwise compare the requested password and the actual users password
-			(user, next) => {
-				if (!user) return next('User not found');
-				if (!user.services.password || !user.services.password.password) return next('The account you are trying to access uses GitHub to log in.');
-				bcrypt.compare(sha256(password), user.services.password.password, (err, match) => {
-					if (err) return next(err);
-					if (!match) return next('Incorrect password');
-					next(null, user);
-				});
-			},
-
-			(user, next) => {
-				utils.guid().then((sessionId) => {
-					next(null, user, sessionId);
-				});
-			},
-
-			(user, sessionId, next) => {
-				cache.hset('sessions', sessionId, cache.schemas.session(sessionId, user._id), (err) => {
-					if (err) return next(err);
-					next(null, sessionId);
-				});
-			}
-
-		], async (err, sessionId) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("USER_PASSWORD_LOGIN", `Login failed with password for user "${identifier}". "${err}"`);
-				return cb({status: 'failure', message: err});
-			}
-			logger.success("USER_PASSWORD_LOGIN", `Login successful with password for user "${identifier}"`);
-			cb({ status: 'success', message: 'Login successful', user: {}, SID: sessionId });
-		});
-
-	},
-
-	/**
-	 * Registers a new user
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} username - the username for the new user
-	 * @param {String} email - the email for the new user
-	 * @param {String} password - the plaintext password for the new user
-	 * @param {Object} recaptcha - the recaptcha data
-	 * @param {Function} cb - gets called with the result
-	 */
-	register: async function(session, username, email, password, recaptcha, cb) {
-		email = email.toLowerCase();
-		let verificationToken = await utils.generateRandomString(64);
-		async.waterfall([
-
-			// verify the request with google recaptcha
-			(next) => {
-				if (!db.passwordValid(password)) return next('Invalid password. Check if it meets all the requirements.');
-				return next();
-			},
-
-			(next) => {
-				request({
-					url: 'https://www.google.com/recaptcha/api/siteverify',
-					method: 'POST',
-					form: {
-						'secret': config.get("apis").recaptcha.secret,
-						'response': recaptcha
-					}
-				}, next);
-			},
-
-			// check if the response from Google recaptcha is successful
-			// if it is, we check if a user with the requested username already exists
-			(response, body, next) => {
-				let json = JSON.parse(body);
-				if (json.success !== true) return next('Response from recaptcha was not successful.');
-				db.models.user.findOne({ username: new RegExp(`^${username}$`, 'i') }, next);
-			},
-
-			// if the user already exists, respond with that
-			// otherwise check if a user with the requested email already exists
-			(user, next) => {
-				if (user) return next('A user with that username already exists.');
-				db.models.user.findOne({ 'email.address': email }, next);
-			},
-
-			// if the user already exists, respond with that
-			// otherwise, generate a salt to use with hashing the new users password
-			(user, next) => {
-				if (user) return next('A user with that email already exists.');
-				bcrypt.genSalt(10, next);
-			},
-
-			// hash the password
-			(salt, next) => {
-				bcrypt.hash(sha256(password), salt, next)
-			},
-
-			(hash, next) => {
-				utils.generateRandomString(12).then((_id) => {
-					next(null, hash, _id);
-				});
-			},
-
-			// save the new user to the database
-			(hash, _id, next) => {
-				db.models.user.create({
-					_id,
-					username,
-					email: {
-						address: email,
-						verificationToken
-					},
-					services: {
-						password: {
-							password: hash
-						}
-					}
-				}, next);
-			},
-
-			// respond with the new user
-			(newUser, next) => {
-				//TODO Send verification email
-				mail.schemas.verifyEmail(email, username, verificationToken, () => {
-					next();
-				});
-			}
-
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("USER_PASSWORD_REGISTER", `Register failed with password for user "${username}"."${err}"`);
-				cb({status: 'failure', message: err});
-			} else {
-				module.exports.login(session, email, password, (result) => {
-					let obj = {status: 'success', message: 'Successfully registered.'};
-					if (result.status === 'success') {
-						obj.SID = result.SID;
-					}
-					logger.success("USER_PASSWORD_REGISTER", `Register successful with password for user "${username}".`);
-					cb(obj);
-				});
-			}
-		});
-
-	},
-
-	/**
-	 * Logs out a user
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Function} cb - gets called with the result
-	 */
-	logout: (session, cb) => {
-
-		async.waterfall([
-			(next) => {
-				cache.hget('sessions', session.sessionId, next);
-			},
-
-			(session, next) => {
-				if (!session) return next('Session not found');
-				next(null, session);
-			},
-
-			(session, next) => {
-				cache.hdel('sessions', session.sessionId, next);
-			}
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("USER_LOGOUT", `Logout failed. "${err}" `);
-				cb({ status: 'failure', message: err });
-			} else {
-				logger.success("USER_LOGOUT", `Logout successful.`);
-				cb({ status: 'success', message: 'Successfully logged out.' });
-			}
-		});
-
-	},
-
-	/**
-	 * Removes all sessions for a user
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} userId - the id of the user we are trying to delete the sessions of
-	 * @param {Function} cb - gets called with the result
-	 */
-	removeSessions:  hooks.loginRequired((session, userId, cb) => {
-
-		async.waterfall([
-
-			(next) => {
-				db.models.user.findOne({ _id: session.userId }, (err, user) => {
-					if (err) return next(err);
-					if (user.role !== 'admin' && session.userId !== userId) return next('Only admins and the owner of the account can remove their sessions.');
-					else return next();
-				});
-			},
-
-			(next) => {
-				cache.hgetall('sessions', next);
-			},
-
-			(sessions, next) => {
-				if (!sessions) return next('There are no sessions for this user to remove.');
-				else {
-					let keys = Object.keys(sessions);
-					next(null, keys, sessions);
-				}
-			},
-
-			(keys, sessions, next) => {
-				cache.pub('user.removeSessions', userId);
-				async.each(keys, (sessionId, callback) => {
-					let session = sessions[sessionId];
-					if (session.userId === userId) {
-						cache.hdel('sessions', sessionId, err => {
-							if (err) return callback(err);
-							else callback(null);
-						});
-					}
-				}, err => {
-					next(err);
-				});
-			}
-
-		], async err => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("REMOVE_SESSIONS_FOR_USER", `Couldn't remove all sessions for user "${userId}". "${err}"`);
-				return cb({ status: 'failure', message: err });
-			} else {
-				logger.success("REMOVE_SESSIONS_FOR_USER", `Removed all sessions for user "${userId}".`);
-				return cb({ status: 'success', message: 'Successfully removed all sessions.' });
-			}
-		});
-
-	}),
-
-	/**
-	 * Gets user object from username (only a few properties)
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} username - the username of the user we are trying to find
-	 * @param {Function} cb - gets called with the result
-	 */
-	findByUsername: (session, username, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.user.findOne({ username: new RegExp(`^${username}$`, 'i') }, next);
-			},
-
-			(account, next) => {
-				if (!account) return next('User not found.');
-				next(null, account);
-			}
-		], async (err, account) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("FIND_BY_USERNAME", `User not found for username "${username}". "${err}"`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("FIND_BY_USERNAME", `User found for username "${username}".`);
-				return cb({
-					status: 'success',
-					data: {
-						_id: account._id,
-						username: account.username,
-						role: account.role,
-						email: account.email.address,
-						createdAt: account.createdAt,
-						statistics: account.statistics,
-						liked: account.liked,
-						disliked: account.disliked
-					}
-				});
-			}
-		});
-	},
-
-
-	/**
-	 * Gets a username from an userId
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} userId - the userId of the person we are trying to get the username from
-	 * @param {Function} cb - gets called with the result
-	 */
-	getUsernameFromId: (session, userId, cb) => {
-		db.models.user.findById(userId).then(user => {
-			if (user) {
-				logger.success("GET_USERNAME_FROM_ID", `Found username for userId "${userId}".`);
-				return cb({
-					status: 'success',
-					data: user.username
-				});
-			} else {
-				logger.error("GET_USERNAME_FROM_ID", `Getting the username from userId "${userId}" failed. User not found.`);
-				cb({
-					status: 'failure',
-					message: "Couldn't find the user."
-				});
-			}
-			
-		}).catch(async err => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("GET_USERNAME_FROM_ID", `Getting the username from userId "${userId}" failed. "${err}"`);
-				cb({ status: 'failure', message: err });
-			}
-		});
-	},
-
-	//TODO Fix security issues
-	/**
-	 * Gets user info from session
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Function} cb - gets called with the result
-	 */
-	findBySession: (session, cb) => {
-		async.waterfall([
-			(next) => {
-				cache.hget('sessions', session.sessionId, next);
-			},
-
-			(session, next) => {
-				if (!session) return next('Session not found.');
-				next(null, session);
-			},
-
-			(session, next) => {
-				db.models.user.findOne({ _id: session.userId }, next);
-			},
-
-			(user, next) => {
-				if (!user) return next('User not found.');
-				next(null, user);
-			}
-		], async (err, user) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("FIND_BY_SESSION", `User not found. "${err}"`);
-				cb({status: 'failure', message: err});
-			} else {
-				let data = {
-					email: {
-						address: user.email.address
-					},
-					username: user.username
-				};
-				if (user.services.password && user.services.password.password) data.password = true;
-				if (user.services.github && user.services.github.id) data.github = true;
-				logger.success("FIND_BY_SESSION", `User found. "${user.username}".`);
-				return cb({
-					status: 'success',
-					data
-				});
-			}
-		});
-	},
-
-	/**
-	 * Updates a user's username
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} updatingUserId - the updating user's id
-	 * @param {String} newUsername - the new username
-	 * @param {Function} cb - gets called with the result
-	 */
-	updateUsername: hooks.loginRequired((session, updatingUserId, newUsername, cb) => {
-		async.waterfall([
-			(next) => {
-				if (updatingUserId === session.userId) return next(null, true);
-				db.models.user.findOne({_id: session.userId}, next);
-			},
-
-			(user, next) => {
-				if (user !== true && (!user || user.role !== 'admin')) return next('Invalid permissions.');
-				db.models.user.findOne({ _id: updatingUserId }, next);
-			},
-
-			(user, next) => {
-				if (!user) return next('User not found.');
-				if (user.username === newUsername) return next('New username can\'t be the same as the old username.');
-				next(null);
-			},
-
-			(next) => {
-				db.models.user.findOne({ username: new RegExp(`^${newUsername}$`, 'i') }, next);
-			},
-
-			(user, next) => {
-				if (!user) return next();
-				if (user._id === updatingUserId) return next();
-				next('That username is already in use.');
-			},
-
-			(next) => {
-				db.models.user.updateOne({ _id: updatingUserId }, {$set: {username: newUsername}}, {runValidators: true}, next);
-			}
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("UPDATE_USERNAME", `Couldn't update username for user "${updatingUserId}" to username "${newUsername}". "${err}"`);
-				cb({status: 'failure', message: err});
-			} else {
-				cache.pub('user.updateUsername', {
-					username: newUsername,
-					_id: updatingUserId
-				});
-				logger.success("UPDATE_USERNAME", `Updated username for user "${updatingUserId}" to username "${newUsername}".`);
-				cb({ status: 'success', message: 'Username updated successfully' });
-			}
-		});
-	}),
-
-	/**
-	 * Updates a user's email
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} updatingUserId - the updating user's id
-	 * @param {String} newEmail - the new email
-	 * @param {Function} cb - gets called with the result
-	 */
-	updateEmail: hooks.loginRequired(async (session, updatingUserId, newEmail, cb) => {
-		newEmail = newEmail.toLowerCase();
-		let verificationToken = await utils.generateRandomString(64);
-		async.waterfall([
-			(next) => {
-				if (updatingUserId === session.userId) return next(null, true);
-				db.models.user.findOne({_id: session.userId}, next);
-			},
-
-			(user, next) => {
-				if (user !== true && (!user || user.role !== 'admin')) return next('Invalid permissions.');
-				db.models.user.findOne({ _id: updatingUserId }, next);
-			},
-
-			(user, next) => {
-				if (!user) return next('User not found.');
-				if (user.email.address === newEmail) return next('New email can\'t be the same as your the old email.');
-				next();
-			},
-
-			(next) => {
-				db.models.user.findOne({"email.address": newEmail}, next);
-			},
-
-			(user, next) => {
-				if (!user) return next();
-				if (user._id === updatingUserId) return next();
-				next('That email is already in use.');
-			},
-
-			(next) => {
-				db.models.user.updateOne({_id: updatingUserId}, {$set: {"email.address": newEmail, "email.verified": false, "email.verificationToken": verificationToken}}, {runValidators: true}, next);
-			},
-
-			(res, next) => {
-				db.models.user.findOne({ _id: updatingUserId }, next);
-			},
-
-			(user, next) => {
-				mail.schemas.verifyEmail(newEmail, user.username, verificationToken, () => {
-					next();
-				});
-			}
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("UPDATE_EMAIL", `Couldn't update email for user "${updatingUserId}" to email "${newEmail}". '${err}'`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("UPDATE_EMAIL", `Updated email for user "${updatingUserId}" to email "${newEmail}".`);
-				cb({ status: 'success', message: 'Email updated successfully.' });
-			}
-		});
-	}),
-
-	/**
-	 * Updates a user's role
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} updatingUserId - the updating user's id
-	 * @param {String} newRole - the new role
-	 * @param {Function} cb - gets called with the result
-	 */
-	updateRole: hooks.adminRequired((session, updatingUserId, newRole, cb) => {
-		newRole = newRole.toLowerCase();
-		async.waterfall([
-
-			(next) => {
-				db.models.user.findOne({ _id: updatingUserId }, next);
-			},
-
-			(user, next) => {
-				if (!user) return next('User not found.');
-				else if (user.role === newRole) return next('New role can\'t be the same as the old role.');
-				else return next();
-			},
-			(next) => {
-				db.models.user.updateOne({_id: updatingUserId}, {$set: {role: newRole}}, {runValidators: true}, next);
-			}
-
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("UPDATE_ROLE", `User "${session.userId}" couldn't update role for user "${updatingUserId}" to role "${newRole}". "${err}"`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("UPDATE_ROLE", `User "${session.userId}" updated the role of user "${updatingUserId}" to role "${newRole}".`);
-				cb({
-					status: 'success',
-					message: 'Role successfully updated.'
-				});
-			}
-		});
-	}),
-
-	/**
-	 * Updates a user's password
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} newPassword - the new password
-	 * @param {Function} cb - gets called with the result
-	 */
-	updatePassword: hooks.loginRequired((session, newPassword, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.user.findOne({_id: session.userId}, next);
-			},
-
-			(user, next) => {
-				if (!user.services.password) return next('This account does not have a password set.');
-				next();
-			},
-
-			(next) => {
-				if (!db.passwordValid(newPassword)) return next('Invalid password. Check if it meets all the requirements.');
-				return next();
-			},
-
-			(next) => {
-				bcrypt.genSalt(10, next);
-			},
-
-			// hash the password
-			(salt, next) => {
-				bcrypt.hash(sha256(newPassword), salt, next);
-			},
-
-			(hashedPassword, next) => {
-				db.models.user.updateOne({_id: session.userId}, {$set: {"services.password.password": hashedPassword}}, next);
-			}
-		], async (err) => {
-			if (err) {
-				err = await utils.getError(err);
-				logger.error("UPDATE_PASSWORD", `Failed updating user password of user '${session.userId}'. '${err}'.`);
-				return cb({ status: 'failure', message: err });
-			}
-
-			logger.success("UPDATE_PASSWORD", `User '${session.userId}' updated their password.`);
-			cb({
-				status: 'success',
-				message: 'Password successfully updated.'
-			});
-		});
-	}),
-
-	/**
-	 * Requests a password for a session
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} email - the email of the user that requests a password reset
-	 * @param {Function} cb - gets called with the result
-	 */
-	requestPassword: hooks.loginRequired(async (session, cb) => {
-		let code = await utils.generateRandomString(8);
-		async.waterfall([
-			(next) => {
-				db.models.user.findOne({_id: session.userId}, next);
-			},
-
-			(user, next) => {
-				if (!user) return next('User not found.');
-				if (user.services.password && user.services.password.password) return next('You already have a password set.');
-				next(null, user);
-			},
-
-			(user, next) => {
-				let expires = new Date();
-				expires.setDate(expires.getDate() + 1);
-				db.models.user.findOneAndUpdate({"email.address": user.email.address}, {$set: {"services.password": {set: {code: code, expires}}}}, {runValidators: true}, next);
-			},
-
-			(user, next) => {
-				mail.schemas.passwordRequest(user.email.address, user.username, code, next);
-			}
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("REQUEST_PASSWORD", `UserId '${session.userId}' failed to request password. '${err}'`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("REQUEST_PASSWORD", `UserId '${session.userId}' successfully requested a password.`);
-				cb({
-					status: 'success',
-					message: 'Successfully requested password.'
-				});
-			}
-		});
-	}),
-
-	/**
-	 * Verifies a password code
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} code - the password code
-	 * @param {Function} cb - gets called with the result
-	 */
-	verifyPasswordCode: hooks.loginRequired((session, code, cb) => {
-		async.waterfall([
-			(next) => {
-				if (!code || typeof code !== 'string') return next('Invalid code1.');
-				db.models.user.findOne({"services.password.set.code": code, _id: session.userId}, next);
-			},
-
-			(user, next) => {
-				if (!user) return next('Invalid code2.');
-				if (user.services.password.set.expires < new Date()) return next('That code has expired.');
-				next(null);
-			}
-		], async(err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("VERIFY_PASSWORD_CODE", `Code '${code}' failed to verify. '${err}'`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("VERIFY_PASSWORD_CODE", `Code '${code}' successfully verified.`);
-				cb({
-					status: 'success',
-					message: 'Successfully verified password code.'
-				});
-			}
-		});
-	}),
-
-	/**
-	 * Adds a password to a user with a code
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} code - the password code
-	 * @param {String} newPassword - the new password code
-	 * @param {Function} cb - gets called with the result
-	 */
-	changePasswordWithCode: hooks.loginRequired((session, code, newPassword, cb) => {
-		async.waterfall([
-			(next) => {
-				if (!code || typeof code !== 'string') return next('Invalid code1.');
-				db.models.user.findOne({"services.password.set.code": code}, next);
-			},
-
-			(user, next) => {
-				if (!user) return next('Invalid code2.');
-				if (!user.services.password.set.expires > new Date()) return next('That code has expired.');
-				next();
-			},
-
-			(next) => {
-				if (!db.passwordValid(newPassword)) return next('Invalid password. Check if it meets all the requirements.');
-				return next();
-			},
-
-			(next) => {
-				bcrypt.genSalt(10, next);
-			},
-
-			// hash the password
-			(salt, next) => {
-				bcrypt.hash(sha256(newPassword), salt, next);
-			},
-
-			(hashedPassword, next) => {
-				db.models.user.updateOne({"services.password.set.code": code}, {$set: {"services.password.password": hashedPassword}, $unset: {"services.password.set": ''}}, {runValidators: true}, next);
-			}
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("ADD_PASSWORD_WITH_CODE", `Code '${code}' failed to add password. '${err}'`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("ADD_PASSWORD_WITH_CODE", `Code '${code}' successfully added password.`);
-				cache.pub('user.linkPassword', session.userId);
-				cb({
-					status: 'success',
-					message: 'Successfully added password.'
-				});
-			}
-		});
-	}),
-
-	/**
-	 * Unlinks password from user
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Function} cb - gets called with the result
-	 */
-	unlinkPassword: hooks.loginRequired((session, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.user.findOne({_id: session.userId}, next);
-			},
-
-			(user, next) => {
-				if (!user) return next('Not logged in.');
-				if (!user.services.github || !user.services.github.id) return next('You can\'t remove password login without having GitHub login.');
-				db.models.user.updateOne({_id: session.userId}, {$unset: {"services.password": ''}}, next);
-			}
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("UNLINK_PASSWORD", `Unlinking password failed for userId '${session.userId}'. '${err}'`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("UNLINK_PASSWORD", `Unlinking password successful for userId '${session.userId}'.`);
-				cache.pub('user.unlinkPassword', session.userId);
-				cb({
-					status: 'success',
-					message: 'Successfully unlinked password.'
-				});
-			}
-		});
-	}),
-
-	/**
-	 * Unlinks GitHub from user
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {Function} cb - gets called with the result
-	 */
-	unlinkGitHub: hooks.loginRequired((session, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.user.findOne({_id: session.userId}, next);
-			},
-
-			(user, next) => {
-				if (!user) return next('Not logged in.');
-				if (!user.services.password || !user.services.password.password) return next('You can\'t remove GitHub login without having password login.');
-				db.models.user.updateOne({_id: session.userId}, {$unset: {"services.github": ''}}, next);
-			}
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("UNLINK_GITHUB", `Unlinking GitHub failed for userId '${session.userId}'. '${err}'`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("UNLINK_GITHUB", `Unlinking GitHub successful for userId '${session.userId}'.`);
-				cache.pub('user.unlinkGitHub', session.userId);
-				cb({
-					status: 'success',
-					message: 'Successfully unlinked GitHub.'
-				});
-			}
-		});
-	}),
-
-	/**
-	 * Requests a password reset for an email
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} email - the email of the user that requests a password reset
-	 * @param {Function} cb - gets called with the result
-	 */
-	requestPasswordReset: async (session, email, cb) => {
-		let code = await utils.generateRandomString(8);
-		async.waterfall([
-			(next) => {
-				if (!email || typeof email !== 'string') return next('Invalid email.');
-				email = email.toLowerCase();
-				db.models.user.findOne({"email.address": email}, next);
-			},
-
-			(user, next) => {
-				if (!user) return next('User not found.');
-				if (!user.services.password || !user.services.password.password) return next('User does not have a password set, and probably uses GitHub to log in.');
-				next(null, user);
-			},
-
-			(user, next) => {
-				let expires = new Date();
-				expires.setDate(expires.getDate() + 1);
-				db.models.user.findOneAndUpdate({"email.address": email}, {$set: {"services.password.reset": {code: code, expires}}}, {runValidators: true}, next);
-			},
-
-			(user, next) => {
-				mail.schemas.resetPasswordRequest(user.email.address, user.username, code, next);
-			}
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("REQUEST_PASSWORD_RESET", `Email '${email}' failed to request password reset. '${err}'`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("REQUEST_PASSWORD_RESET", `Email '${email}' successfully requested a password reset.`);
-				cb({
-					status: 'success',
-					message: 'Successfully requested password reset.'
-				});
-			}
-		});
-	},
-
-	/**
-	 * Verifies a reset code
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} code - the password reset code
-	 * @param {Function} cb - gets called with the result
-	 */
-	verifyPasswordResetCode: (session, code, cb) => {
-		async.waterfall([
-			(next) => {
-				if (!code || typeof code !== 'string') return next('Invalid code.');
-				db.models.user.findOne({"services.password.reset.code": code}, next);
-			},
-
-			(user, next) => {
-				if (!user) return next('Invalid code.');
-				if (!user.services.password.reset.expires > new Date()) return next('That code has expired.');
-				next(null);
-			}
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("VERIFY_PASSWORD_RESET_CODE", `Code '${code}' failed to verify. '${err}'`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("VERIFY_PASSWORD_RESET_CODE", `Code '${code}' successfully verified.`);
-				cb({
-					status: 'success',
-					message: 'Successfully verified password reset code.'
-				});
-			}
-		});
-	},
-
-	/**
-	 * Changes a user's password with a reset code
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} code - the password reset code
-	 * @param {String} newPassword - the new password reset code
-	 * @param {Function} cb - gets called with the result
-	 */
-	changePasswordWithResetCode: (session, code, newPassword, cb) => {
-		async.waterfall([
-			(next) => {
-				if (!code || typeof code !== 'string') return next('Invalid code.');
-				db.models.user.findOne({"services.password.reset.code": code}, next);
-			},
-
-			(user, next) => {
-				if (!user) return next('Invalid code.');
-				if (!user.services.password.reset.expires > new Date()) return next('That code has expired.');
-				next();
-			},
-
-			(next) => {
-				if (!db.passwordValid(newPassword)) return next('Invalid password. Check if it meets all the requirements.');
-				return next();
-			},
-
-			(next) => {
-				bcrypt.genSalt(10, next);
-			},
-
-			// hash the password
-			(salt, next) => {
-				bcrypt.hash(sha256(newPassword), salt, next);
-			},
-
-			(hashedPassword, next) => {
-				db.models.user.updateOne({"services.password.reset.code": code}, {$set: {"services.password.password": hashedPassword}, $unset: {"services.password.reset": ''}}, {runValidators: true}, next);
-			}
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("CHANGE_PASSWORD_WITH_RESET_CODE", `Code '${code}' failed to change password. '${err}'`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("CHANGE_PASSWORD_WITH_RESET_CODE", `Code '${code}' successfully changed password.`);
-				cb({
-					status: 'success',
-					message: 'Successfully changed password.'
-				});
-			}
-		});
-	},
-
-	/**
-	 * Bans a user by userId
-	 *
-	 * @param {Object} session - the session object automatically added by socket.io
-	 * @param {String} value - the user id that is going to be banned
-	 * @param {String} reason - the reason for the ban
-	 * @param {String} expiresAt - the time the ban expires
-	 * @param {Function} cb - gets called with the result
-	 */
-	banUserById: hooks.adminRequired((session, userId, reason, expiresAt, cb) => {
-		async.waterfall([
-			(next) => {
-				if (!userId) return next('You must provide a userId to ban.');
-				else if (!reason) return next('You must provide a reason for the ban.');
-				else return next();
-			},
-
-			(next) => {
-				if (!expiresAt || typeof expiresAt !== 'string') return next('Invalid expire date.');
-				let date = new Date();
-				switch(expiresAt) {
-					case '1h':
-						expiresAt = date.setHours(date.getHours() + 1);
-						break;
-					case '12h':
-						expiresAt = date.setHours(date.getHours() + 12);
-						break;
-					case '1d':
-						expiresAt = date.setDate(date.getDate() + 1);
-						break;
-					case '1w':
-						expiresAt = date.setDate(date.getDate() + 7);
-						break;
-					case '1m':
-						expiresAt = date.setMonth(date.getMonth() + 1);
-						break;
-					case '3m':
-						expiresAt = date.setMonth(date.getMonth() + 3);
-						break;
-					case '6m':
-						expiresAt = date.setMonth(date.getMonth() + 6);
-						break;
-					case '1y':
-						expiresAt = date.setFullYear(date.getFullYear() + 1);
-						break;
-					case 'never':
-						expiresAt = new Date(3093527980800000);
-						break;
-					default:
-						return next('Invalid expire date.');
-				}
-
-				next();
-			},
-
-			(next) => {
-				punishments.addPunishment('banUserId', userId, reason, expiresAt, userId, next)
-			},
-
-			(punishment, next) => {
-				cache.pub('user.ban', { userId, punishment });
-				next();
-			},
-		], async (err) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("BAN_USER_BY_ID", `User ${session.userId} failed to ban user ${userId} with the reason ${reason}. '${err}'`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("BAN_USER_BY_ID", `User ${session.userId} has successfully banned user ${userId} with the reason ${reason}.`);
-				cb({
-					status: 'success',
-					message: 'Successfully banned user.'
-				});
-			}
-		});
-	}),
-
-	getFavoriteStations: hooks.loginRequired((session, cb) => {
-		async.waterfall([
-			(next) => {
-				db.models.user.findOne({ _id: session.userId }, next);
-			},
-
-			(user, next) => {
-				if (!user) return next("User not found.");
-				next(null, user);
-			}
-		], async (err, user) => {
-			if (err && err !== true) {
-				err = await utils.getError(err);
-				logger.error("GET_FAVORITE_STATIONS", `User ${session.userId} failed to get favorite stations. '${err}'`);
-				cb({status: 'failure', message: err});
-			} else {
-				logger.success("GET_FAVORITE_STATIONS", `User ${session.userId} got favorite stations.`);
-				cb({
-					status: 'success',
-					favoriteStations: user.favoriteStations
-				});
-			}
-		});
-	})
+    /**
+     * Lists all Users
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Function} cb - gets called with the result
+     */
+    index: hooks.adminRequired(async (session, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+
+        async.waterfall(
+            [
+                (next) => {
+                    userModel.find({}).exec(next);
+                },
+            ],
+            async (err, users) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "USER_INDEX",
+                        `Indexing users failed. "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "USER_INDEX",
+                        `Indexing users successful.`
+                    );
+                    let filteredUsers = [];
+                    users.forEach((user) => {
+                        filteredUsers.push({
+                            _id: user._id,
+                            username: user.username,
+                            role: user.role,
+                            liked: user.liked,
+                            disliked: user.disliked,
+                            songsRequested: user.statistics.songsRequested,
+                            email: {
+                                address: user.email.address,
+                                verified: user.email.verified,
+                            },
+                            hasPassword: !!user.services.password,
+                            services: { github: user.services.github },
+                        });
+                    });
+                    return cb({ status: "success", data: filteredUsers });
+                }
+            }
+        );
+    }),
+
+    /**
+     * Logs user in
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} identifier - the email of the user
+     * @param {String} password - the plaintext of the user
+     * @param {Function} cb - gets called with the result
+     */
+    login: async (session, identifier, password, cb) => {
+        identifier = identifier.toLowerCase();
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        const sessionSchema = await cache.runJob("GET_SCHEMA", {
+            schemaName: "session",
+        });
+
+        async.waterfall(
+            [
+                // check if a user with the requested identifier exists
+                (next) => {
+                    userModel.findOne(
+                        {
+                            $or: [{ "email.address": identifier }],
+                        },
+                        next
+                    );
+                },
+
+                // if the user doesn't exist, respond with a failure
+                // otherwise compare the requested password and the actual users password
+                (user, next) => {
+                    if (!user) return next("User not found");
+                    if (
+                        !user.services.password ||
+                        !user.services.password.password
+                    )
+                        return next(
+                            "The account you are trying to access uses GitHub to log in."
+                        );
+                    bcrypt.compare(
+                        sha256(password),
+                        user.services.password.password,
+                        (err, match) => {
+                            if (err) return next(err);
+                            if (!match) return next("Incorrect password");
+                            next(null, user);
+                        }
+                    );
+                },
+
+                (user, next) => {
+                    utils.runJob("GUID", {}).then((sessionId) => {
+                        next(null, user, sessionId);
+                    });
+                },
+
+                (user, sessionId, next) => {
+                    cache
+                        .runJob("HSET", {
+                            table: "sessions",
+                            key: sessionId,
+                            value: sessionSchema(sessionId, user._id),
+                        })
+                        .then(() => next(null, sessionId))
+                        .catch(next);
+                },
+            ],
+            async (err, sessionId) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "USER_PASSWORD_LOGIN",
+                        `Login failed with password for user "${identifier}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+                console.log(
+                    "SUCCESS",
+                    "USER_PASSWORD_LOGIN",
+                    `Login successful with password for user "${identifier}"`
+                );
+                cb({
+                    status: "success",
+                    message: "Login successful",
+                    user: {},
+                    SID: sessionId,
+                });
+            }
+        );
+    },
+
+    /**
+     * Registers a new user
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} username - the username for the new user
+     * @param {String} email - the email for the new user
+     * @param {String} password - the plaintext password for the new user
+     * @param {Object} recaptcha - the recaptcha data
+     * @param {Function} cb - gets called with the result
+     */
+    register: async function(
+        session,
+        username,
+        email,
+        password,
+        recaptcha,
+        cb
+    ) {
+        email = email.toLowerCase();
+        let verificationToken = await utils.runJob("GENERATE_RANDOM_STRING", {
+            length: 64,
+        });
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        const verifyEmailSchema = await mail.runJob("GET_SCHEMA", {
+            schemaName: "verifyEmail",
+        });
+
+        async.waterfall(
+            [
+                // verify the request with google recaptcha
+                (next) => {
+                    if (!db.passwordValid(password))
+                        return next(
+                            "Invalid password. Check if it meets all the requirements."
+                        );
+                    return next();
+                },
+
+                (next) => {
+                    request(
+                        {
+                            url:
+                                "https://www.google.com/recaptcha/api/siteverify",
+                            method: "POST",
+                            form: {
+                                secret: config.get("apis").recaptcha.secret,
+                                response: recaptcha,
+                            },
+                        },
+                        next
+                    );
+                },
+
+                // check if the response from Google recaptcha is successful
+                // if it is, we check if a user with the requested username already exists
+                (response, body, next) => {
+                    let json = JSON.parse(body);
+                    if (json.success !== true)
+                        return next(
+                            "Response from recaptcha was not successful."
+                        );
+                    userModel.findOne(
+                        { username: new RegExp(`^${username}$`, "i") },
+                        next
+                    );
+                },
+
+                // if the user already exists, respond with that
+                // otherwise check if a user with the requested email already exists
+                (user, next) => {
+                    if (user)
+                        return next(
+                            "A user with that username already exists."
+                        );
+                    userModel.findOne({ "email.address": email }, next);
+                },
+
+                // if the user already exists, respond with that
+                // otherwise, generate a salt to use with hashing the new users password
+                (user, next) => {
+                    if (user)
+                        return next("A user with that email already exists.");
+                    bcrypt.genSalt(10, next);
+                },
+
+                // hash the password
+                (salt, next) => {
+                    bcrypt.hash(sha256(password), salt, next);
+                },
+
+                (hash, next) => {
+                    utils
+                        .runJob("GENERATE_RANDOM_STRING", { length: 12 })
+                        .then((_id) => {
+                            next(null, hash, _id);
+                        });
+                },
+
+                // create the user object
+                (hash, _id, next) => {
+                    next(null, {
+                        _id,
+                        username,
+                        email: {
+                            address: email,
+                            verificationToken,
+                        },
+                        services: {
+                            password: {
+                                password: hash,
+                            },
+                        },
+                    });
+                },
+
+                // generate the url for gravatar avatar
+                (user, next) => {
+                    utils
+                        .runJob("CREATE_GRAVATAR", {
+                            email: user.email.address,
+                        })
+                        .then((url) => {
+                            user.avatar = url;
+                            next(null, user);
+                        });
+                },
+
+                // save the new user to the database
+                (user, next) => {
+                    userModel.create(user, next);
+                },
+
+                // respond with the new user
+                (newUser, next) => {
+                    verifyEmailSchema(
+                        email,
+                        username,
+                        verificationToken,
+                        () => {
+                            next(null, newUser);
+                        }
+                    );
+                },
+            ],
+            async (err, user) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "USER_PASSWORD_REGISTER",
+                        `Register failed with password for user "${username}"."${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    module.exports.login(session, email, password, (result) => {
+                        let obj = {
+                            status: "success",
+                            message: "Successfully registered.",
+                        };
+                        if (result.status === "success") {
+                            obj.SID = result.SID;
+                        }
+                        activities.runJob("ADD_ACTIVITY", {
+                            userId: user._id,
+                            activityType: "created_account",
+                        });
+                        console.log(
+                            "SUCCESS",
+                            "USER_PASSWORD_REGISTER",
+                            `Register successful with password for user "${username}".`
+                        );
+                        return cb(obj);
+                    });
+                }
+            }
+        );
+    },
+
+    /**
+     * Logs out a user
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Function} cb - gets called with the result
+     */
+    logout: (session, cb) => {
+        async.waterfall(
+            [
+                (next) => {
+                    cache
+                        .runJob("HGET", {
+                            table: "sessions",
+                            key: session.sessionId,
+                        })
+                        .then((session) => next(null, session))
+                        .catch(next);
+                },
+
+                (session, next) => {
+                    if (!session) return next("Session not found");
+                    next(null, session);
+                },
+
+                (session, next) => {
+                    cache
+                        .runJob("HDEL", {
+                            table: "sessions",
+                            key: session.sessionId,
+                        })
+                        .then(() => next())
+                        .catch(next);
+                },
+            ],
+            async (err) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "USER_LOGOUT",
+                        `Logout failed. "${err}" `
+                    );
+                    cb({ status: "failure", message: err });
+                } else {
+                    console.log("SUCCESS", "USER_LOGOUT", `Logout successful.`);
+                    cb({
+                        status: "success",
+                        message: "Successfully logged out.",
+                    });
+                }
+            }
+        );
+    },
+
+    /**
+     * Removes all sessions for a user
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} userId - the id of the user we are trying to delete the sessions of
+     * @param {Function} cb - gets called with the result
+     */
+    removeSessions: hooks.loginRequired(async (session, userId, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    userModel.findOne({ _id: session.userId }, (err, user) => {
+                        if (err) return next(err);
+                        if (user.role !== "admin" && session.userId !== userId)
+                            return next(
+                                "Only admins and the owner of the account can remove their sessions."
+                            );
+                        else return next();
+                    });
+                },
+
+                (next) => {
+                    cache
+                        .runJob("HGETALL", { table: "sessions" })
+                        .then((sessions) => next(null, sessions))
+                        .catch(next);
+                },
+
+                (sessions, next) => {
+                    if (!sessions)
+                        return next(
+                            "There are no sessions for this user to remove."
+                        );
+                    else {
+                        let keys = Object.keys(sessions);
+                        next(null, keys, sessions);
+                    }
+                },
+
+                (keys, sessions, next) => {
+                    cache.runJob("PUB", {
+                        channel: "user.removeSessions",
+                        value: userId,
+                    });
+                    async.each(
+                        keys,
+                        (sessionId, callback) => {
+                            let session = sessions[sessionId];
+                            if (session.userId === userId) {
+                                cache
+                                    .runJob("HDEL", {
+                                        channel: "sessions",
+                                        key: sessionId,
+                                    })
+                                    .then(() => callback(null))
+                                    .catch(next);
+                            }
+                        },
+                        (err) => {
+                            next(err);
+                        }
+                    );
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "REMOVE_SESSIONS_FOR_USER",
+                        `Couldn't remove all sessions for user "${userId}". "${err}"`
+                    );
+                    return cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "REMOVE_SESSIONS_FOR_USER",
+                        `Removed all sessions for user "${userId}".`
+                    );
+                    return cb({
+                        status: "success",
+                        message: "Successfully removed all sessions.",
+                    });
+                }
+            }
+        );
+    }),
+
+    /**
+     * Gets user object from username (only a few properties)
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} username - the username of the user we are trying to find
+     * @param {Function} cb - gets called with the result
+     */
+    findByUsername: async (session, username, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    userModel.findOne(
+                        { username: new RegExp(`^${username}$`, "i") },
+                        next
+                    );
+                },
+
+                (account, next) => {
+                    if (!account) return next("User not found.");
+                    next(null, account);
+                },
+            ],
+            async (err, account) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "FIND_BY_USERNAME",
+                        `User not found for username "${username}". "${err}"`
+                    );
+                    cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "FIND_BY_USERNAME",
+                        `User found for username "${username}".`
+                    );
+                    return cb({
+                        status: "success",
+                        data: {
+                            _id: account._id,
+                            name: account.name,
+                            username: account.username,
+                            location: account.location,
+                            bio: account.bio,
+                            role: account.role,
+                            avatar: account.avatar,
+                            createdAt: account.createdAt,
+                        },
+                    });
+                }
+            }
+        );
+    },
+
+    /**
+     * Gets a username from an userId
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} userId - the userId of the person we are trying to get the username from
+     * @param {Function} cb - gets called with the result
+     */
+    getUsernameFromId: async (session, userId, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        userModel
+            .findById(userId)
+            .then((user) => {
+                if (user) {
+                    console.log(
+                        "SUCCESS",
+                        "GET_USERNAME_FROM_ID",
+                        `Found username for userId "${userId}".`
+                    );
+                    return cb({
+                        status: "success",
+                        data: user.username,
+                    });
+                } else {
+                    console.log(
+                        "ERROR",
+                        "GET_USERNAME_FROM_ID",
+                        `Getting the username from userId "${userId}" failed. User not found.`
+                    );
+                    cb({
+                        status: "failure",
+                        message: "Couldn't find the user.",
+                    });
+                }
+            })
+            .catch(async (err) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "GET_USERNAME_FROM_ID",
+                        `Getting the username from userId "${userId}" failed. "${err}"`
+                    );
+                    cb({ status: "failure", message: err });
+                }
+            });
+    },
+
+    //TODO Fix security issues
+    /**
+     * Gets user info from session
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Function} cb - gets called with the result
+     */
+    findBySession: async (session, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    cache
+                        .runJob("HGET", {
+                            table: "sessions",
+                            key: session.sessionId,
+                        })
+                        .then((session) => next(null, session))
+                        .catch(next);
+                },
+
+                (session, next) => {
+                    if (!session) return next("Session not found.");
+                    next(null, session);
+                },
+
+                (session, next) => {
+                    userModel.findOne({ _id: session.userId }, next);
+                },
+
+                (user, next) => {
+                    if (!user) return next("User not found.");
+                    next(null, user);
+                },
+            ],
+            async (err, user) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "FIND_BY_SESSION",
+                        `User not found. "${err}"`
+                    );
+                    cb({ status: "failure", message: err });
+                } else {
+                    let data = {
+                        email: {
+                            address: user.email.address,
+                        },
+                        avatar: user.avatar,
+                        username: user.username,
+                        name: user.name,
+                        location: user.location,
+                        bio: user.bio,
+                    };
+                    if (
+                        user.services.password &&
+                        user.services.password.password
+                    )
+                        data.password = true;
+                    if (user.services.github && user.services.github.id)
+                        data.github = true;
+                    console.log(
+                        "SUCCESS",
+                        "FIND_BY_SESSION",
+                        `User found. "${user.username}".`
+                    );
+                    return cb({
+                        status: "success",
+                        data,
+                    });
+                }
+            }
+        );
+    },
+
+    /**
+     * Updates a user's username
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} updatingUserId - the updating user's id
+     * @param {String} newUsername - the new username
+     * @param {Function} cb - gets called with the result
+     */
+    updateUsername: hooks.loginRequired(
+        async (session, updatingUserId, newUsername, cb) => {
+            const userModel = await db.runJob("GET_MODEL", {
+                modelName: "user",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        if (updatingUserId === session.userId)
+                            return next(null, true);
+                        userModel.findOne({ _id: session.userId }, next);
+                    },
+
+                    (user, next) => {
+                        if (user !== true && (!user || user.role !== "admin"))
+                            return next("Invalid permissions.");
+                        userModel.findOne({ _id: updatingUserId }, next);
+                    },
+
+                    (user, next) => {
+                        if (!user) return next("User not found.");
+                        if (user.username === newUsername)
+                            return next(
+                                "New username can't be the same as the old username."
+                            );
+                        next(null);
+                    },
+
+                    (next) => {
+                        userModel.findOne(
+                            { username: new RegExp(`^${newUsername}$`, "i") },
+                            next
+                        );
+                    },
+
+                    (user, next) => {
+                        if (!user) return next();
+                        if (user._id === updatingUserId) return next();
+                        next("That username is already in use.");
+                    },
+
+                    (next) => {
+                        userModel.updateOne(
+                            { _id: updatingUserId },
+                            { $set: { username: newUsername } },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+                ],
+                async (err) => {
+                    if (err && err !== true) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "UPDATE_USERNAME",
+                            `Couldn't update username for user "${updatingUserId}" to username "${newUsername}". "${err}"`
+                        );
+                        cb({ status: "failure", message: err });
+                    } else {
+                        cache.runJob("PUB", {
+                            channel: "user.updateUsername",
+                            value: {
+                                username: newUsername,
+                                _id: updatingUserId,
+                            },
+                        });
+                        console.log(
+                            "SUCCESS",
+                            "UPDATE_USERNAME",
+                            `Updated username for user "${updatingUserId}" to username "${newUsername}".`
+                        );
+                        cb({
+                            status: "success",
+                            message: "Username updated successfully",
+                        });
+                    }
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates a user's email
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} updatingUserId - the updating user's id
+     * @param {String} newEmail - the new email
+     * @param {Function} cb - gets called with the result
+     */
+    updateEmail: hooks.loginRequired(
+        async (session, updatingUserId, newEmail, cb) => {
+            newEmail = newEmail.toLowerCase();
+            let verificationToken = await utils.runJob(
+                "GENERATE_RANDOM_STRING",
+                { length: 64 }
+            );
+            const userModel = await db.runJob("GET_MODEL", {
+                modelName: "user",
+            });
+            const verifyEmailSchema = await mail.runJob("GET_SCHEMA", {
+                schemaName: "verifyEmail",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        if (updatingUserId === session.userId)
+                            return next(null, true);
+                        userModel.findOne({ _id: session.userId }, next);
+                    },
+
+                    (user, next) => {
+                        if (user !== true && (!user || user.role !== "admin"))
+                            return next("Invalid permissions.");
+                        userModel.findOne({ _id: updatingUserId }, next);
+                    },
+
+                    (user, next) => {
+                        if (!user) return next("User not found.");
+                        if (user.email.address === newEmail)
+                            return next(
+                                "New email can't be the same as your the old email."
+                            );
+                        next();
+                    },
+
+                    (next) => {
+                        userModel.findOne({ "email.address": newEmail }, next);
+                    },
+
+                    (user, next) => {
+                        if (!user) return next();
+                        if (user._id === updatingUserId) return next();
+                        next("That email is already in use.");
+                    },
+
+                    // regenerate the url for gravatar avatar
+                    (next) => {
+                        utils
+                            .runJob("CREATE_GRAVATAR", { email: newEmail })
+                            .then((url) => next(null, url));
+                    },
+
+                    (avatar, next) => {
+                        userModel.updateOne(
+                            { _id: updatingUserId },
+                            {
+                                $set: {
+                                    avatar: avatar,
+                                    "email.address": newEmail,
+                                    "email.verified": false,
+                                    "email.verificationToken": verificationToken,
+                                },
+                            },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        userModel.findOne({ _id: updatingUserId }, next);
+                    },
+
+                    (user, next) => {
+                        verifyEmailSchema(
+                            newEmail,
+                            user.username,
+                            verificationToken,
+                            () => {
+                                next();
+                            }
+                        );
+                    },
+                ],
+                async (err) => {
+                    if (err && err !== true) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "UPDATE_EMAIL",
+                            `Couldn't update email for user "${updatingUserId}" to email "${newEmail}". '${err}'`
+                        );
+                        cb({ status: "failure", message: err });
+                    } else {
+                        console.log(
+                            "SUCCESS",
+                            "UPDATE_EMAIL",
+                            `Updated email for user "${updatingUserId}" to email "${newEmail}".`
+                        );
+                        cb({
+                            status: "success",
+                            message: "Email updated successfully.",
+                        });
+                    }
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates a user's name
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} updatingUserId - the updating user's id
+     * @param {String} newBio - the new name
+     * @param {Function} cb - gets called with the result
+     */
+    updateName: hooks.loginRequired(
+        async (session, updatingUserId, newName, cb) => {
+            const userModel = await db.runJob("GET_MODEL", {
+                modelName: "user",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        if (updatingUserId === session.userId)
+                            return next(null, true);
+                        userModel.findOne({ _id: session.userId }, next);
+                    },
+
+                    (user, next) => {
+                        if (user !== true && (!user || user.role !== "admin"))
+                            return next("Invalid permissions.");
+                        userModel.findOne({ _id: updatingUserId }, next);
+                    },
+
+                    (user, next) => {
+                        if (!user) return next("User not found.");
+                        userModel.updateOne(
+                            { _id: updatingUserId },
+                            { $set: { name: newName } },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+                ],
+                async (err) => {
+                    if (err && err !== true) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "UPDATE_NAME",
+                            `Couldn't update name for user "${updatingUserId}" to name "${newName}". "${err}"`
+                        );
+                        cb({ status: "failure", message: err });
+                    } else {
+                        console.log(
+                            "SUCCESS",
+                            "UPDATE_NAME",
+                            `Updated name for user "${updatingUserId}" to name "${newName}".`
+                        );
+                        cb({
+                            status: "success",
+                            message: "Name updated successfully",
+                        });
+                    }
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates a user's location
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} updatingUserId - the updating user's id
+     * @param {String} newLocation - the new location
+     * @param {Function} cb - gets called with the result
+     */
+    updateLocation: hooks.loginRequired(
+        async (session, updatingUserId, newLocation, cb) => {
+            const userModel = await db.runJob("GET_MODEL", {
+                modelName: "user",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        if (updatingUserId === session.userId)
+                            return next(null, true);
+                        userModel.findOne({ _id: session.userId }, next);
+                    },
+
+                    (user, next) => {
+                        if (user !== true && (!user || user.role !== "admin"))
+                            return next("Invalid permissions.");
+                        userModel.findOne({ _id: updatingUserId }, next);
+                    },
+
+                    (user, next) => {
+                        if (!user) return next("User not found.");
+                        userModel.updateOne(
+                            { _id: updatingUserId },
+                            { $set: { location: newLocation } },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+                ],
+                async (err) => {
+                    if (err && err !== true) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "UPDATE_LOCATION",
+                            `Couldn't update location for user "${updatingUserId}" to location "${newLocation}". "${err}"`
+                        );
+                        cb({ status: "failure", message: err });
+                    } else {
+                        console.log(
+                            "SUCCESS",
+                            "UPDATE_LOCATION",
+                            `Updated location for user "${updatingUserId}" to location "${newLocation}".`
+                        );
+                        cb({
+                            status: "success",
+                            message: "Location updated successfully",
+                        });
+                    }
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates a user's bio
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} updatingUserId - the updating user's id
+     * @param {String} newBio - the new bio
+     * @param {Function} cb - gets called with the result
+     */
+    updateBio: hooks.loginRequired(
+        async (session, updatingUserId, newBio, cb) => {
+            const userModel = await db.runJob("GET_MODEL", {
+                modelName: "user",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        if (updatingUserId === session.userId)
+                            return next(null, true);
+                        userModel.findOne({ _id: session.userId }, next);
+                    },
+
+                    (user, next) => {
+                        if (user !== true && (!user || user.role !== "admin"))
+                            return next("Invalid permissions.");
+                        userModel.findOne({ _id: updatingUserId }, next);
+                    },
+
+                    (user, next) => {
+                        if (!user) return next("User not found.");
+                        userModel.updateOne(
+                            { _id: updatingUserId },
+                            { $set: { bio: newBio } },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+                ],
+                async (err) => {
+                    if (err && err !== true) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "UPDATE_BIO",
+                            `Couldn't update bio for user "${updatingUserId}" to bio "${newBio}". "${err}"`
+                        );
+                        cb({ status: "failure", message: err });
+                    } else {
+                        console.log(
+                            "SUCCESS",
+                            "UPDATE_BIO",
+                            `Updated bio for user "${updatingUserId}" to bio "${newBio}".`
+                        );
+                        cb({
+                            status: "success",
+                            message: "Bio updated successfully",
+                        });
+                    }
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates the type of a user's avatar
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} updatingUserId - the updating user's id
+     * @param {String} newType - the new type
+     * @param {Function} cb - gets called with the result
+     */
+    updateAvatarType: hooks.loginRequired(
+        async (session, updatingUserId, newType, cb) => {
+            const userModel = await db.runJob("GET_MODEL", {
+                modelName: "user",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        if (updatingUserId === session.userId)
+                            return next(null, true);
+                        userModel.findOne({ _id: session.userId }, next);
+                    },
+
+                    (user, next) => {
+                        if (user !== true && (!user || user.role !== "admin"))
+                            return next("Invalid permissions.");
+                        userModel.findOne({ _id: updatingUserId }, next);
+                    },
+
+                    (user, next) => {
+                        if (!user) return next("User not found.");
+                        userModel.updateOne(
+                            { _id: updatingUserId },
+                            { $set: { "avatar.type": newType } },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+                ],
+                async (err) => {
+                    if (err && err !== true) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "UPDATE_AVATAR_TYPE",
+                            `Couldn't update avatar type for user "${updatingUserId}" to type "${newType}". "${err}"`
+                        );
+                        cb({ status: "failure", message: err });
+                    } else {
+                        console.log(
+                            "SUCCESS",
+                            "UPDATE_AVATAR_TYPE",
+                            `Updated avatar type for user "${updatingUserId}" to type "${newType}".`
+                        );
+                        cb({
+                            status: "success",
+                            message: "Avatar type updated successfully",
+                        });
+                    }
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates a user's role
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} updatingUserId - the updating user's id
+     * @param {String} newRole - the new role
+     * @param {Function} cb - gets called with the result
+     */
+    updateRole: hooks.adminRequired(
+        async (session, updatingUserId, newRole, cb) => {
+            newRole = newRole.toLowerCase();
+            const userModel = await db.runJob("GET_MODEL", {
+                modelName: "user",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        userModel.findOne({ _id: updatingUserId }, next);
+                    },
+
+                    (user, next) => {
+                        if (!user) return next("User not found.");
+                        else if (user.role === newRole)
+                            return next(
+                                "New role can't be the same as the old role."
+                            );
+                        else return next();
+                    },
+                    (next) => {
+                        userModel.updateOne(
+                            { _id: updatingUserId },
+                            { $set: { role: newRole } },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+                ],
+                async (err) => {
+                    if (err && err !== true) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "UPDATE_ROLE",
+                            `User "${session.userId}" couldn't update role for user "${updatingUserId}" to role "${newRole}". "${err}"`
+                        );
+                        cb({ status: "failure", message: err });
+                    } else {
+                        console.log(
+                            "SUCCESS",
+                            "UPDATE_ROLE",
+                            `User "${session.userId}" updated the role of user "${updatingUserId}" to role "${newRole}".`
+                        );
+                        cb({
+                            status: "success",
+                            message: "Role successfully updated.",
+                        });
+                    }
+                }
+            );
+        }
+    ),
+
+    /**
+     * Updates a user's password
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} newPassword - the new password
+     * @param {Function} cb - gets called with the result
+     */
+    updatePassword: hooks.loginRequired(async (session, newPassword, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    userModel.findOne({ _id: session.userId }, next);
+                },
+
+                (user, next) => {
+                    if (!user.services.password)
+                        return next(
+                            "This account does not have a password set."
+                        );
+                    next();
+                },
+
+                (next) => {
+                    if (!db.passwordValid(newPassword))
+                        return next(
+                            "Invalid password. Check if it meets all the requirements."
+                        );
+                    return next();
+                },
+
+                (next) => {
+                    bcrypt.genSalt(10, next);
+                },
+
+                // hash the password
+                (salt, next) => {
+                    bcrypt.hash(sha256(newPassword), salt, next);
+                },
+
+                (hashedPassword, next) => {
+                    userModel.updateOne(
+                        { _id: session.userId },
+                        {
+                            $set: {
+                                "services.password.password": hashedPassword,
+                            },
+                        },
+                        next
+                    );
+                },
+            ],
+            async (err) => {
+                if (err) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "UPDATE_PASSWORD",
+                        `Failed updating user password of user '${session.userId}'. '${err}'.`
+                    );
+                    return cb({ status: "failure", message: err });
+                }
+
+                console.log(
+                    "SUCCESS",
+                    "UPDATE_PASSWORD",
+                    `User '${session.userId}' updated their password.`
+                );
+                cb({
+                    status: "success",
+                    message: "Password successfully updated.",
+                });
+            }
+        );
+    }),
+
+    /**
+     * Requests a password for a session
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} email - the email of the user that requests a password reset
+     * @param {Function} cb - gets called with the result
+     */
+    requestPassword: hooks.loginRequired(async (session, cb) => {
+        let code = await utils.runJob("GENERATE_RANDOM_STRING", { length: 8 });
+        const passwordRequestSchema = await mail.runJob("GET_SCHEMA", {
+            schemaName: "passwordRequest",
+        });
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    userModel.findOne({ _id: session.userId }, next);
+                },
+
+                (user, next) => {
+                    if (!user) return next("User not found.");
+                    if (
+                        user.services.password &&
+                        user.services.password.password
+                    )
+                        return next("You already have a password set.");
+                    next(null, user);
+                },
+
+                (user, next) => {
+                    let expires = new Date();
+                    expires.setDate(expires.getDate() + 1);
+                    userModel.findOneAndUpdate(
+                        { "email.address": user.email.address },
+                        {
+                            $set: {
+                                "services.password": {
+                                    set: { code: code, expires },
+                                },
+                            },
+                        },
+                        { runValidators: true },
+                        next
+                    );
+                },
+
+                (user, next) => {
+                    passwordRequestSchema(
+                        user.email.address,
+                        user.username,
+                        code,
+                        next
+                    );
+                },
+            ],
+            async (err) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "REQUEST_PASSWORD",
+                        `UserId '${session.userId}' failed to request password. '${err}'`
+                    );
+                    cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "REQUEST_PASSWORD",
+                        `UserId '${session.userId}' successfully requested a password.`
+                    );
+                    cb({
+                        status: "success",
+                        message: "Successfully requested password.",
+                    });
+                }
+            }
+        );
+    }),
+
+    /**
+     * Verifies a password code
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} code - the password code
+     * @param {Function} cb - gets called with the result
+     */
+    verifyPasswordCode: hooks.loginRequired(async (session, code, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    if (!code || typeof code !== "string")
+                        return next("Invalid code1.");
+                    userModel.findOne(
+                        {
+                            "services.password.set.code": code,
+                            _id: session.userId,
+                        },
+                        next
+                    );
+                },
+
+                (user, next) => {
+                    if (!user) return next("Invalid code2.");
+                    if (user.services.password.set.expires < new Date())
+                        return next("That code has expired.");
+                    next(null);
+                },
+            ],
+            async (err) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "VERIFY_PASSWORD_CODE",
+                        `Code '${code}' failed to verify. '${err}'`
+                    );
+                    cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "VERIFY_PASSWORD_CODE",
+                        `Code '${code}' successfully verified.`
+                    );
+                    cb({
+                        status: "success",
+                        message: "Successfully verified password code.",
+                    });
+                }
+            }
+        );
+    }),
+
+    /**
+     * Adds a password to a user with a code
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} code - the password code
+     * @param {String} newPassword - the new password code
+     * @param {Function} cb - gets called with the result
+     */
+    changePasswordWithCode: hooks.loginRequired(
+        async (session, code, newPassword, cb) => {
+            const userModel = await db.runJob("GET_MODEL", {
+                modelName: "user",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        if (!code || typeof code !== "string")
+                            return next("Invalid code1.");
+                        userModel.findOne(
+                            { "services.password.set.code": code },
+                            next
+                        );
+                    },
+
+                    (user, next) => {
+                        if (!user) return next("Invalid code2.");
+                        if (!user.services.password.set.expires > new Date())
+                            return next("That code has expired.");
+                        next();
+                    },
+
+                    (next) => {
+                        if (!db.passwordValid(newPassword))
+                            return next(
+                                "Invalid password. Check if it meets all the requirements."
+                            );
+                        return next();
+                    },
+
+                    (next) => {
+                        bcrypt.genSalt(10, next);
+                    },
+
+                    // hash the password
+                    (salt, next) => {
+                        bcrypt.hash(sha256(newPassword), salt, next);
+                    },
+
+                    (hashedPassword, next) => {
+                        userModel.updateOne(
+                            { "services.password.set.code": code },
+                            {
+                                $set: {
+                                    "services.password.password": hashedPassword,
+                                },
+                                $unset: { "services.password.set": "" },
+                            },
+                            { runValidators: true },
+                            next
+                        );
+                    },
+                ],
+                async (err) => {
+                    if (err && err !== true) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "ADD_PASSWORD_WITH_CODE",
+                            `Code '${code}' failed to add password. '${err}'`
+                        );
+                        cb({ status: "failure", message: err });
+                    } else {
+                        console.log(
+                            "SUCCESS",
+                            "ADD_PASSWORD_WITH_CODE",
+                            `Code '${code}' successfully added password.`
+                        );
+                        cache.runJob("PUB", {
+                            channel: "user.linkPassword",
+                            value: session.userId,
+                        });
+                        cb({
+                            status: "success",
+                            message: "Successfully added password.",
+                        });
+                    }
+                }
+            );
+        }
+    ),
+
+    /**
+     * Unlinks password from user
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Function} cb - gets called with the result
+     */
+    unlinkPassword: hooks.loginRequired(async (session, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    userModel.findOne({ _id: session.userId }, next);
+                },
+
+                (user, next) => {
+                    if (!user) return next("Not logged in.");
+                    if (!user.services.github || !user.services.github.id)
+                        return next(
+                            "You can't remove password login without having GitHub login."
+                        );
+                    userModel.updateOne(
+                        { _id: session.userId },
+                        { $unset: { "services.password": "" } },
+                        next
+                    );
+                },
+            ],
+            async (err) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "UNLINK_PASSWORD",
+                        `Unlinking password failed for userId '${session.userId}'. '${err}'`
+                    );
+                    cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "UNLINK_PASSWORD",
+                        `Unlinking password successful for userId '${session.userId}'.`
+                    );
+                    cache.runJob("PUB", {
+                        channel: "user.unlinkPassword",
+                        value: session.userId,
+                    });
+                    cb({
+                        status: "success",
+                        message: "Successfully unlinked password.",
+                    });
+                }
+            }
+        );
+    }),
+
+    /**
+     * Unlinks GitHub from user
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {Function} cb - gets called with the result
+     */
+    unlinkGitHub: hooks.loginRequired(async (session, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    userModel.findOne({ _id: session.userId }, next);
+                },
+
+                (user, next) => {
+                    if (!user) return next("Not logged in.");
+                    if (
+                        !user.services.password ||
+                        !user.services.password.password
+                    )
+                        return next(
+                            "You can't remove GitHub login without having password login."
+                        );
+                    userModel.updateOne(
+                        { _id: session.userId },
+                        { $unset: { "services.github": "" } },
+                        next
+                    );
+                },
+            ],
+            async (err) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "UNLINK_GITHUB",
+                        `Unlinking GitHub failed for userId '${session.userId}'. '${err}'`
+                    );
+                    cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "UNLINK_GITHUB",
+                        `Unlinking GitHub successful for userId '${session.userId}'.`
+                    );
+                    cache.runJob("PUB", {
+                        channel: "user.unlinkGithub",
+                        value: session.userId,
+                    });
+                    cb({
+                        status: "success",
+                        message: "Successfully unlinked GitHub.",
+                    });
+                }
+            }
+        );
+    }),
+
+    /**
+     * Requests a password reset for an email
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} email - the email of the user that requests a password reset
+     * @param {Function} cb - gets called with the result
+     */
+    requestPasswordReset: async (session, email, cb) => {
+        let code = await utils.runJob("GENERATE_RANDOM_STRING", { length: 8 });
+        console.log(111, code);
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        const resetPasswordRequestSchema = await mail.runJob("GET_SCHEMA", {
+            schemaName: "resetPasswordRequest",
+        });
+        async.waterfall(
+            [
+                (next) => {
+                    if (!email || typeof email !== "string")
+                        return next("Invalid email.");
+                    email = email.toLowerCase();
+                    userModel.findOne({ "email.address": email }, next);
+                },
+
+                (user, next) => {
+                    if (!user) return next("User not found.");
+                    if (
+                        !user.services.password ||
+                        !user.services.password.password
+                    )
+                        return next(
+                            "User does not have a password set, and probably uses GitHub to log in."
+                        );
+                    next(null, user);
+                },
+
+                (user, next) => {
+                    let expires = new Date();
+                    expires.setDate(expires.getDate() + 1);
+                    userModel.findOneAndUpdate(
+                        { "email.address": email },
+                        {
+                            $set: {
+                                "services.password.reset": {
+                                    code: code,
+                                    expires,
+                                },
+                            },
+                        },
+                        { runValidators: true },
+                        next
+                    );
+                },
+
+                (user, next) => {
+                    resetPasswordRequestSchema(
+                        user.email.address,
+                        user.username,
+                        code,
+                        next
+                    );
+                },
+            ],
+            async (err) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "REQUEST_PASSWORD_RESET",
+                        `Email '${email}' failed to request password reset. '${err}'`
+                    );
+                    cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "REQUEST_PASSWORD_RESET",
+                        `Email '${email}' successfully requested a password reset.`
+                    );
+                    cb({
+                        status: "success",
+                        message: "Successfully requested password reset.",
+                    });
+                }
+            }
+        );
+    },
+
+    /**
+     * Verifies a reset code
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} code - the password reset code
+     * @param {Function} cb - gets called with the result
+     */
+    verifyPasswordResetCode: async (session, code, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    if (!code || typeof code !== "string")
+                        return next("Invalid code.");
+                    userModel.findOne(
+                        { "services.password.reset.code": code },
+                        next
+                    );
+                },
+
+                (user, next) => {
+                    if (!user) return next("Invalid code.");
+                    if (!user.services.password.reset.expires > new Date())
+                        return next("That code has expired.");
+                    next(null);
+                },
+            ],
+            async (err) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "VERIFY_PASSWORD_RESET_CODE",
+                        `Code '${code}' failed to verify. '${err}'`
+                    );
+                    cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "VERIFY_PASSWORD_RESET_CODE",
+                        `Code '${code}' successfully verified.`
+                    );
+                    cb({
+                        status: "success",
+                        message: "Successfully verified password reset code.",
+                    });
+                }
+            }
+        );
+    },
+
+    /**
+     * Changes a user's password with a reset code
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} code - the password reset code
+     * @param {String} newPassword - the new password reset code
+     * @param {Function} cb - gets called with the result
+     */
+    changePasswordWithResetCode: async (session, code, newPassword, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    if (!code || typeof code !== "string")
+                        return next("Invalid code.");
+                    userModel.findOne(
+                        { "services.password.reset.code": code },
+                        next
+                    );
+                },
+
+                (user, next) => {
+                    if (!user) return next("Invalid code.");
+                    if (!user.services.password.reset.expires > new Date())
+                        return next("That code has expired.");
+                    next();
+                },
+
+                (next) => {
+                    if (!db.passwordValid(newPassword))
+                        return next(
+                            "Invalid password. Check if it meets all the requirements."
+                        );
+                    return next();
+                },
+
+                (next) => {
+                    bcrypt.genSalt(10, next);
+                },
+
+                // hash the password
+                (salt, next) => {
+                    bcrypt.hash(sha256(newPassword), salt, next);
+                },
+
+                (hashedPassword, next) => {
+                    userModel.updateOne(
+                        { "services.password.reset.code": code },
+                        {
+                            $set: {
+                                "services.password.password": hashedPassword,
+                            },
+                            $unset: { "services.password.reset": "" },
+                        },
+                        { runValidators: true },
+                        next
+                    );
+                },
+            ],
+            async (err) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "CHANGE_PASSWORD_WITH_RESET_CODE",
+                        `Code '${code}' failed to change password. '${err}'`
+                    );
+                    cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "CHANGE_PASSWORD_WITH_RESET_CODE",
+                        `Code '${code}' successfully changed password.`
+                    );
+                    cb({
+                        status: "success",
+                        message: "Successfully changed password.",
+                    });
+                }
+            }
+        );
+    },
+
+    /**
+     * Bans a user by userId
+     *
+     * @param {Object} session - the session object automatically added by socket.io
+     * @param {String} value - the user id that is going to be banned
+     * @param {String} reason - the reason for the ban
+     * @param {String} expiresAt - the time the ban expires
+     * @param {Function} cb - gets called with the result
+     */
+    banUserById: hooks.adminRequired(
+        (session, userId, reason, expiresAt, cb) => {
+            async.waterfall(
+                [
+                    (next) => {
+                        if (!userId)
+                            return next("You must provide a userId to ban.");
+                        else if (!reason)
+                            return next(
+                                "You must provide a reason for the ban."
+                            );
+                        else return next();
+                    },
+
+                    (next) => {
+                        if (!expiresAt || typeof expiresAt !== "string")
+                            return next("Invalid expire date.");
+                        let date = new Date();
+                        switch (expiresAt) {
+                            case "1h":
+                                expiresAt = date.setHours(date.getHours() + 1);
+                                break;
+                            case "12h":
+                                expiresAt = date.setHours(date.getHours() + 12);
+                                break;
+                            case "1d":
+                                expiresAt = date.setDate(date.getDate() + 1);
+                                break;
+                            case "1w":
+                                expiresAt = date.setDate(date.getDate() + 7);
+                                break;
+                            case "1m":
+                                expiresAt = date.setMonth(date.getMonth() + 1);
+                                break;
+                            case "3m":
+                                expiresAt = date.setMonth(date.getMonth() + 3);
+                                break;
+                            case "6m":
+                                expiresAt = date.setMonth(date.getMonth() + 6);
+                                break;
+                            case "1y":
+                                expiresAt = date.setFullYear(
+                                    date.getFullYear() + 1
+                                );
+                                break;
+                            case "never":
+                                expiresAt = new Date(3093527980800000);
+                                break;
+                            default:
+                                return next("Invalid expire date.");
+                        }
+
+                        next();
+                    },
+
+                    (next) => {
+                        punishments
+                            .runJob("ADD_PUNISHMENT", {
+                                type: "banUserId",
+                                value: userId,
+                                reason,
+                                expiresAt,
+                                punishedBy,
+                            })
+                            .then((punishment) => next(null, punishment))
+                            .catch(next);
+                    },
+
+                    (punishment, next) => {
+                        cache.runJob("PUB", {
+                            channel: "user.ban",
+                            value: { userId, punishment },
+                        });
+                        next();
+                    },
+                ],
+                async (err) => {
+                    if (err && err !== true) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        console.log(
+                            "ERROR",
+                            "BAN_USER_BY_ID",
+                            `User ${session.userId} failed to ban user ${userId} with the reason ${reason}. '${err}'`
+                        );
+                        cb({ status: "failure", message: err });
+                    } else {
+                        console.log(
+                            "SUCCESS",
+                            "BAN_USER_BY_ID",
+                            `User ${session.userId} has successfully banned user ${userId} with the reason ${reason}.`
+                        );
+                        cb({
+                            status: "success",
+                            message: "Successfully banned user.",
+                        });
+                    }
+                }
+            );
+        }
+    ),
+
+    getFavoriteStations: hooks.loginRequired(async (session, cb) => {
+        const userModel = await db.runJob("GET_MODEL", { modelName: "user" });
+        async.waterfall(
+            [
+                (next) => {
+                    userModel.findOne({ _id: session.userId }, next);
+                },
+
+                (user, next) => {
+                    if (!user) return next("User not found.");
+                    next(null, user);
+                },
+            ],
+            async (err, user) => {
+                if (err && err !== true) {
+                    err = await utils.runJob("GET_ERROR", { error: err });
+                    console.log(
+                        "ERROR",
+                        "GET_FAVORITE_STATIONS",
+                        `User ${session.userId} failed to get favorite stations. '${err}'`
+                    );
+                    cb({ status: "failure", message: err });
+                } else {
+                    console.log(
+                        "SUCCESS",
+                        "GET_FAVORITE_STATIONS",
+                        `User ${session.userId} got favorite stations.`
+                    );
+                    cb({
+                        status: "success",
+                        favoriteStations: user.favoriteStations,
+                    });
+                }
+            }
+        );
+    }),
 };

+ 71 - 0
backend/logic/activities.js

@@ -0,0 +1,71 @@
+const CoreClass = require("../core.js");
+
+const async = require("async");
+const mongoose = require("mongoose");
+
+class ActivitiesModule extends CoreClass {
+    constructor() {
+        super("activities");
+    }
+
+    initialize() {
+        return new Promise((resolve, reject) => {
+            this.db = this.moduleManager.modules["db"];
+            this.io = this.moduleManager.modules["io"];
+            this.utils = this.moduleManager.modules["utils"];
+
+            resolve();
+        });
+    }
+
+    // TODO: Migrate
+    ADD_ACTIVITY(payload) {
+        //userId, activityType, payload
+        return new Promise((resolve, reject) => {
+            async.waterfall(
+                [
+                    (next) => {
+                        this.db
+                            .runJob("GET_MODEL", { modelName: "activity" })
+                            .then((res) => {
+                                next(null, res);
+                            })
+                            .catch(next);
+                    },
+                    (activityModel, next) => {
+                        const activity = new activityModel({
+                            userId: payload.userId,
+                            activityType: payload.activityType,
+                            payload: payload.payload,
+                        });
+
+                        activity.save((err, activity) => {
+                            if (err) return next(err);
+                            next(null, activity);
+                        });
+                    },
+
+                    (activity, next) => {
+                        this.utils
+                            .runJob("SOCKETS_FROM_USER", {
+                                userId: activity.userId,
+                            })
+                            .then((response) =>
+                                response.sockets.forEach((socket) => {
+                                    socket.emit(
+                                        "event:activity.create",
+                                        activity
+                                    );
+                                })
+                            );
+                    },
+                ],
+                (err, activity) => {
+                    // cb(err, activity);
+                }
+            );
+        });
+    }
+}
+
+module.exports = new ActivitiesModule();

+ 44 - 39
backend/logic/api.js

@@ -1,40 +1,45 @@
-const coreClass = require("../core");
-
-module.exports = class extends coreClass {
-	constructor(name, moduleManager) {
-		super(name, moduleManager);
-
-		this.dependsOn = ["app", "db", "cache"];
-	}
-
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
-
-			this.app = this.moduleManager.modules["app"];
-
-			this.app.app.get('/', (req, res) => {
-				res.json({
-					status: 'success',
-					message: 'Coming Soon'
-				});
-			});
-
-			const actions = require("../logic/actions");
-	
-			Object.keys(actions).forEach((namespace) => {
-				Object.keys(actions[namespace]).forEach((action) => {
-					let name = `/${namespace}/${action}`;
-	
-					this.app.app.get(name, (req, res) => {
-						actions[namespace][action](null, (result) => {
-							if (typeof cb === 'function') return res.json(result);
-						});
-					});
-				})
-			});
-
-			resolve();
-		});
-	}
+const CoreClass = require("../core.js");
+
+class APIModule extends CoreClass {
+    constructor() {
+        super("api");
+    }
+
+    initialize() {
+        return new Promise((resolve, reject) => {
+            const app = this.moduleManager.modules["app"];
+
+            const actions = require("./actions");
+
+            app.runJob("GET_APP", {})
+                .then((response) => {
+                    response.app.get("/", (req, res) => {
+                        res.json({
+                            status: "success",
+                            message: "Coming Soon",
+                        });
+                    });
+
+                    // Object.keys(actions).forEach(namespace => {
+                    //     Object.keys(actions[namespace]).forEach(action => {
+                    //         let name = `/${namespace}/${action}`;
+
+                    //         response.app.get(name, (req, res) => {
+                    //             actions[namespace][action](null, result => {
+                    //                 if (typeof cb === "function")
+                    //                     return res.json(result);
+                    //             });
+                    //         });
+                    //     });
+                    // });
+
+                    resolve();
+                })
+                .catch((err) => {
+                    reject(err);
+                });
+        });
+    }
 }
+
+module.exports = new APIModule();

+ 511 - 246
backend/logic/app.js

@@ -1,247 +1,512 @@
-'use strict';
-
-const coreClass = require("../core");
-
-const express = require('express');
-const bodyParser = require('body-parser');
-const cookieParser = require('cookie-parser');
-const cors = require('cors');
-const config = require('config');
-const async = require('async');
-const request = require('request');
-const OAuth2 = require('oauth').OAuth2;
-
-module.exports = class extends coreClass {
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
-
-			const 	logger 	= this.logger,
-					mail	= this.moduleManager.modules["mail"],
-					cache	= this.moduleManager.modules["cache"],
-					db		= this.moduleManager.modules["db"];
-			
-			this.utils = this.moduleManager.modules["utils"];
-
-			let app = this.app = express();
-			const SIDname = config.get("cookie.SIDname");
-			this.server = app.listen(config.get('serverPort'));
-
-			app.use(cookieParser());
-
-			app.use(bodyParser.json());
-			app.use(bodyParser.urlencoded({ extended: true }));
-
-			let corsOptions = Object.assign({}, config.get('cors'));
-
-			app.use(cors(corsOptions));
-			app.options('*', cors(corsOptions));
-
-			let oauth2 = new OAuth2(
-				config.get('apis.github.client'),
-				config.get('apis.github.secret'),
-				'https://github.com/',
-				'login/oauth/authorize',
-				'login/oauth/access_token',
-				null
-			);
-
-			let redirect_uri = config.get('serverDomain') + '/auth/github/authorize/callback';
-
-			app.get('/auth/github/authorize', async (req, res) => {
-				try { await this._validateHook(); } catch { return; }
-				let params = [
-					`client_id=${config.get('apis.github.client')}`,
-					`redirect_uri=${config.get('serverDomain')}/auth/github/authorize/callback`,
-					`scope=user:email`
-				].join('&');
-				res.redirect(`https://github.com/login/oauth/authorize?${params}`);
-			});
-
-			app.get('/auth/github/link', async (req, res) => {
-				try { await this._validateHook(); } catch { return; }
-				let params = [
-					`client_id=${config.get('apis.github.client')}`,
-					`redirect_uri=${config.get('serverDomain')}/auth/github/authorize/callback`,
-					`scope=user:email`,
-					`state=${req.cookies[SIDname]}`
-				].join('&');
-				res.redirect(`https://github.com/login/oauth/authorize?${params}`);
-			});
-
-			function redirectOnErr (res, err){
-				return res.redirect(`${config.get('domain')}/?err=${encodeURIComponent(err)}`);
-			}
-
-			app.get('/auth/github/authorize/callback', async (req, res) => {
-				try { await this._validateHook(); } catch { return; }
-				let code = req.query.code;
-				let access_token;
-				let body;
-				let address;
-				const state = req.query.state;
-
-				async.waterfall([
-					(next) => {
-						if (req.query.error) return next(req.query.error_description);
-						next();
-					},
-
-					(next) => {
-						oauth2.getOAuthAccessToken(code, {redirect_uri}, next);
-					},
-
-					(_access_token, refresh_token, results, next) => {
-						if (results.error) return next(results.error_description);
-						access_token = _access_token;
-						request.get({
-							url: `https://api.github.com/user?access_token=${access_token}`,
-							headers: {'User-Agent': 'request'}
-						}, next);
-					},
-
-					(httpResponse, _body, next) => {
-						body = _body = JSON.parse(_body);
-						if (httpResponse.statusCode !== 200) return next(body.message);
-						if (state) {
-							return async.waterfall([
-								(next) => {
-									cache.hget('sessions', state, next);
-								},
-
-								(session, next) => {
-									if (!session) return next('Invalid session.');
-									db.models.user.findOne({_id: session.userId}, next);
-								},
-
-								(user, next) => {
-									if (!user) return next('User not found.');
-									if (user.services.github && user.services.github.id) return next('Account already has GitHub linked.');
-									db.models.user.updateOne({_id: user._id}, {$set: {"services.github": {id: body.id, access_token}}}, {runValidators: true}, (err) => {
-										if (err) return next(err);
-										next(null, user, body);
-									});
-								},
-
-								(user) => {
-									cache.pub('user.linkGitHub', user._id);
-									res.redirect(`${config.get('domain')}/settings`);
-								}
-							], next);
-						}
-						if (!body.id) return next("Something went wrong, no id.");
-						db.models.user.findOne({'services.github.id': body.id}, (err, user) => {
-							next(err, user, body);
-						});
-					},
-
-					(user, body, next) => {
-						if (user) {
-							user.services.github.access_token = access_token;
-							return user.save(() => {
-								next(true, user._id);
-							});
-						}
-						db.models.user.findOne({ username: new RegExp(`^${body.login}$`, 'i' )}, (err, user) => {
-							next(err, user);
-						});
-					},
-
-					(user, next) => {
-						if (user) return next('An account with that username already exists.');
-						request.get({
-							url: `https://api.github.com/user/emails?access_token=${access_token}`,
-							headers: {'User-Agent': 'request'}
-						}, next);
-					},
-
-					(httpResponse, body2, next) => {
-						body2 = JSON.parse(body2);
-						if (!Array.isArray(body2)) return next(body2.message);
-						body2.forEach(email => {
-							if (email.primary) address = email.email.toLowerCase();
-						});
-						db.models.user.findOne({'email.address': address}, next);
-					},
-
-					async (user, next) => {
-						const verificationToken = await this.utils.generateRandomString(64);
-						if (user) return next('An account with that email address already exists.');
-						db.models.user.create({
-							_id: await this.utils.generateRandomString(12),//TODO Check if exists
-							username: body.login,
-							email: {
-								address,
-								verificationToken: verificationToken
-							},
-							services: {
-								github: {id: body.id, access_token}
-							}
-						}, next);
-					},
-
-					(user, next) => {
-						mail.schemas.verifyEmail(address, body.login, user.email.verificationToken);
-						next(null, user._id);
-					}
-				], async (err, userId) => {
-					if (err && err !== true) {
-						err = await this.utils.getError(err);
-						logger.error('AUTH_GITHUB_AUTHORIZE_CALLBACK', `Failed to authorize with GitHub. "${err}"`);
-						return redirectOnErr(res, err);
-					}
-
-					const sessionId = await this.utils.guid();
-					cache.hset('sessions', sessionId, cache.schemas.session(sessionId, userId), err => {
-						if (err) return redirectOnErr(res, err.message);
-						let date = new Date();
-						date.setTime(new Date().getTime() + (2 * 365 * 24 * 60 * 60 * 1000));
-						res.cookie(SIDname, sessionId, {
-							expires: date,
-							secure: config.get("cookie.secure"),
-							path: "/",
-							domain: config.get("cookie.domain")
-						});
-						logger.success('AUTH_GITHUB_AUTHORIZE_CALLBACK', `User "${userId}" successfully authorized with GitHub.`);
-						res.redirect(`${config.get('domain')}/`);
-					});
-				});
-			});
-
-			app.get('/auth/verify_email', async (req, res) => {
-				try { await this._validateHook(); } catch { return; }
-
-				let code = req.query.code;
-
-				async.waterfall([
-					(next) => {
-						if (!code) return next('Invalid code.');
-						next();
-					},
-
-					(next) => {
-						db.models.user.findOne({"email.verificationToken": code}, next);
-					},
-
-					(user, next) => {
-						if (!user) return next('User not found.');
-						if (user.email.verified) return next('This email is already verified.');
-						db.models.user.updateOne({"email.verificationToken": code}, {$set: {"email.verified": true}, $unset: {"email.verificationToken": ''}}, {runValidators: true}, next);
-					}
-				], (err) => {
-					if (err) {
-						let error = 'An error occurred.';
-						if (typeof err === "string") error = err;
-						else if (err.message) error = err.message;
-						logger.error("VERIFY_EMAIL", `Verifying email failed. "${error}"`);
-						return res.json({ status: 'failure', message: error});
-					}
-					logger.success("VERIFY_EMAIL", `Successfully verified email.`);
-					res.redirect(`${config.get("domain")}?msg=Thank you for verifying your email`);
-				});
-			});
-
-			resolve();
-		});
-	}
+const CoreClass = require("../core.js");
+
+const express = require("express");
+const bodyParser = require("body-parser");
+const cookieParser = require("cookie-parser");
+const cors = require("cors");
+const config = require("config");
+const async = require("async");
+const request = require("request");
+const OAuth2 = require("oauth").OAuth2;
+
+class AppModule extends CoreClass {
+    constructor() {
+        super("app");
+    }
+
+    initialize() {
+        return new Promise(async (resolve, reject) => {
+            const mail = this.moduleManager.modules["mail"],
+                cache = this.moduleManager.modules["cache"],
+                db = this.moduleManager.modules["db"],
+                activities = this.moduleManager.modules["activities"];
+
+            this.utils = this.moduleManager.modules["utils"];
+
+            let app = (this.app = express());
+            const SIDname = config.get("cookie.SIDname");
+            this.server = app.listen(config.get("serverPort"));
+
+            app.use(cookieParser());
+
+            app.use(bodyParser.json());
+            app.use(bodyParser.urlencoded({ extended: true }));
+
+            const userModel = await db.runJob("GET_MODEL", {
+                modelName: "user",
+            });
+
+            let corsOptions = Object.assign({}, config.get("cors"));
+
+            app.use(cors(corsOptions));
+            app.options("*", cors(corsOptions));
+
+            let oauth2 = new OAuth2(
+                config.get("apis.github.client"),
+                config.get("apis.github.secret"),
+                "https://github.com/",
+                "login/oauth/authorize",
+                "login/oauth/access_token",
+                null
+            );
+
+            let redirect_uri =
+                config.get("serverDomain") + "/auth/github/authorize/callback";
+
+            app.get("/auth/github/authorize", async (req, res) => {
+                if (this.getStatus() !== "READY") {
+                    this.log(
+                        "INFO",
+                        "APP_REJECTED_GITHUB_AUTHORIZE",
+                        `A user tried to use github authorize, but the APP module is currently not ready.`
+                    );
+                    return redirectOnErr(
+                        res,
+                        "Something went wrong on our end. Please try again later."
+                    );
+                }
+
+                let params = [
+                    `client_id=${config.get("apis.github.client")}`,
+                    `redirect_uri=${config.get(
+                        "serverDomain"
+                    )}/auth/github/authorize/callback`,
+                    `scope=user:email`,
+                ].join("&");
+                res.redirect(
+                    `https://github.com/login/oauth/authorize?${params}`
+                );
+            });
+
+            app.get("/auth/github/link", async (req, res) => {
+                if (this.getStatus() !== "READY") {
+                    this.log(
+                        "INFO",
+                        "APP_REJECTED_GITHUB_AUTHORIZE",
+                        `A user tried to use github authorize, but the APP module is currently not ready.`
+                    );
+                    return redirectOnErr(
+                        res,
+                        "Something went wrong on our end. Please try again later."
+                    );
+                }
+                let params = [
+                    `client_id=${config.get("apis.github.client")}`,
+                    `redirect_uri=${config.get(
+                        "serverDomain"
+                    )}/auth/github/authorize/callback`,
+                    `scope=user:email`,
+                    `state=${req.cookies[SIDname]}`,
+                ].join("&");
+                res.redirect(
+                    `https://github.com/login/oauth/authorize?${params}`
+                );
+            });
+
+            function redirectOnErr(res, err) {
+                return res.redirect(
+                    `${config.get("domain")}/?err=${encodeURIComponent(err)}`
+                );
+            }
+
+            app.get("/auth/github/authorize/callback", async (req, res) => {
+                if (this.getStatus() !== "READY") {
+                    this.log(
+                        "INFO",
+                        "APP_REJECTED_GITHUB_AUTHORIZE",
+                        `A user tried to use github authorize, but the APP module is currently not ready.`
+                    );
+                    return redirectOnErr(
+                        res,
+                        "Something went wrong on our end. Please try again later."
+                    );
+                }
+
+                let code = req.query.code;
+                let access_token;
+                let body;
+                let address;
+
+                const state = req.query.state;
+
+                const verificationToken = await this.utils.runJob(
+                    "GENERATE_RANDOM_STRING",
+                    { length: 64 }
+                );
+
+                async.waterfall(
+                    [
+                        (next) => {
+                            if (req.query.error)
+                                return next(req.query.error_description);
+                            next();
+                        },
+
+                        (next) => {
+                            oauth2.getOAuthAccessToken(
+                                code,
+                                { redirect_uri },
+                                next
+                            );
+                        },
+
+                        (_access_token, refresh_token, results, next) => {
+                            if (results.error)
+                                return next(results.error_description);
+                            access_token = _access_token;
+                            request.get(
+                                {
+                                    url: `https://api.github.com/user?access_token=${access_token}`,
+                                    headers: { "User-Agent": "request" },
+                                },
+                                next
+                            );
+                        },
+
+                        (httpResponse, _body, next) => {
+                            body = _body = JSON.parse(_body);
+                            if (httpResponse.statusCode !== 200)
+                                return next(body.message);
+                            if (state) {
+                                return async.waterfall(
+                                    [
+                                        (next) => {
+                                            cache
+                                                .runJob("HGET", {
+                                                    table: "sessions",
+                                                    key: state,
+                                                })
+                                                .then((session) =>
+                                                    next(null, session)
+                                                )
+                                                .catch(next);
+                                        },
+
+                                        (session, next) => {
+                                            if (!session)
+                                                return next("Invalid session.");
+                                            userModel.findOne(
+                                                { _id: session.userId },
+                                                next
+                                            );
+                                        },
+
+                                        (user, next) => {
+                                            if (!user)
+                                                return next("User not found.");
+                                            if (
+                                                user.services.github &&
+                                                user.services.github.id
+                                            )
+                                                return next(
+                                                    "Account already has GitHub linked."
+                                                );
+                                            userModel.updateOne(
+                                                { _id: user._id },
+                                                {
+                                                    $set: {
+                                                        "services.github": {
+                                                            id: body.id,
+                                                            access_token,
+                                                        },
+                                                    },
+                                                },
+                                                { runValidators: true },
+                                                (err) => {
+                                                    if (err) return next(err);
+                                                    next(null, user, body);
+                                                }
+                                            );
+                                        },
+
+                                        (user) => {
+                                            cache.runJob("PUB", {
+                                                channel: "user.linkGithub",
+                                                value: user._id,
+                                            });
+                                            res.redirect(
+                                                `${config.get(
+                                                    "domain"
+                                                )}/settings`
+                                            );
+                                        },
+                                    ],
+                                    next
+                                );
+                            }
+
+                            if (!body.id)
+                                return next("Something went wrong, no id.");
+                            userModel.findOne(
+                                { "services.github.id": body.id },
+                                (err, user) => {
+                                    next(err, user, body);
+                                }
+                            );
+                        },
+
+                        (user, body, next) => {
+                            if (user) {
+                                user.services.github.access_token = access_token;
+                                return user.save(() => {
+                                    next(true, user._id);
+                                });
+                            }
+                            userModel.findOne(
+                                {
+                                    username: new RegExp(
+                                        `^${body.login}$`,
+                                        "i"
+                                    ),
+                                },
+                                (err, user) => {
+                                    next(err, user);
+                                }
+                            );
+                        },
+
+                        (user, next) => {
+                            if (user)
+                                return next(
+                                    "An account with that username already exists."
+                                );
+                            request.get(
+                                {
+                                    url: `https://api.github.com/user/emails?access_token=${access_token}`,
+                                    headers: { "User-Agent": "request" },
+                                },
+                                next
+                            );
+                        },
+
+                        (httpResponse, body2, next) => {
+                            body2 = JSON.parse(body2);
+                            if (!Array.isArray(body2))
+                                return next(body2.message);
+
+                            body2.forEach((email) => {
+                                if (email.primary)
+                                    address = email.email.toLowerCase();
+                            });
+
+                            userModel.findOne(
+                                { "email.address": address },
+                                next
+                            );
+                        },
+
+                        (user, next) => {
+                            this.utils
+                                .runJob("GENERATE_RANDOM_STRING", {
+                                    length: 12,
+                                })
+                                .then((_id) => {
+                                    next(null, user, _id);
+                                });
+                        },
+
+                        (user, _id, next) => {
+                            if (user)
+                                return next(
+                                    "An account with that email address already exists."
+                                );
+
+                            next(null, {
+                                _id, //TODO Check if exists
+                                username: body.login,
+                                name: body.name,
+                                location: body.location,
+                                bio: body.bio,
+                                email: {
+                                    address,
+                                    verificationToken,
+                                },
+                                services: {
+                                    github: { id: body.id, access_token },
+                                },
+                            });
+                        },
+
+                        // generate the url for gravatar avatar
+                        (user, next) => {
+                            this.utils
+                                .runJob("CREATE_GRAVATAR", {
+                                    email: user.email.address,
+                                })
+                                .then((url) => {
+                                    user.avatar = { type: "gravatar", url };
+                                    next(null, user);
+                                });
+                        },
+
+                        // save the new user to the database
+                        (user, next) => {
+                            userModel.create(user, next);
+                        },
+
+                        // add the activity of account creation
+                        (user, next) => {
+                            activities.runJob("ADD_ACTIVITY", {
+                                userId: user._id,
+                                activityType: "created_account",
+                            });
+                            next(null, user);
+                        },
+
+                        (user, next) => {
+                            mail.runJob("GET_SCHEMA", {
+                                schemaName: "verifyEmail",
+                            }).then((verifyEmailSchema) => {
+                                verifyEmailSchema(
+                                    address,
+                                    body.login,
+                                    user.email.verificationToken
+                                );
+                                next(null, user._id);
+                            });
+                        },
+                    ],
+                    async (err, userId) => {
+                        if (err && err !== true) {
+                            err = await this.utils.getError(err);
+                            logger.error(
+                                "AUTH_GITHUB_AUTHORIZE_CALLBACK",
+                                `Failed to authorize with GitHub. "${err}"`
+                            );
+                            return redirectOnErr(res, err);
+                        }
+
+                        const sessionId = await this.utils.runJob("GUID", {});
+                        const sessionSchema = await cache.runJob("GET_SCHEMA", {
+                            schemaName: "session",
+                        });
+                        cache
+                            .runJob("HSET", {
+                                table: "sessions",
+                                key: sessionId,
+                                value: sessionSchema(sessionId, userId),
+                            })
+                            .then(() => {
+                                let date = new Date();
+                                date.setTime(
+                                    new Date().getTime() +
+                                        2 * 365 * 24 * 60 * 60 * 1000
+                                );
+                                res.cookie(SIDname, sessionId, {
+                                    expires: date,
+                                    secure: config.get("cookie.secure"),
+                                    path: "/",
+                                    domain: config.get("cookie.domain"),
+                                });
+                                logger.success(
+                                    "AUTH_GITHUB_AUTHORIZE_CALLBACK",
+                                    `User "${userId}" successfully authorized with GitHub.`
+                                );
+                                res.redirect(`${config.get("domain")}/`);
+                            })
+                            .catch((err) => {
+                                return redirectOnErr(res, err.message);
+                            });
+                    }
+                );
+            });
+
+            app.get("/auth/verify_email", async (req, res) => {
+                if (this.getStatus() !== "READY") {
+                    this.log(
+                        "INFO",
+                        "APP_REJECTED_GITHUB_AUTHORIZE",
+                        `A user tried to use github authorize, but the APP module is currently not ready.`
+                    );
+                    return redirectOnErr(
+                        res,
+                        "Something went wrong on our end. Please try again later."
+                    );
+                }
+
+                let code = req.query.code;
+
+                async.waterfall(
+                    [
+                        (next) => {
+                            if (!code) return next("Invalid code.");
+                            next();
+                        },
+
+                        (next) => {
+                            userModel.findOne(
+                                { "email.verificationToken": code },
+                                next
+                            );
+                        },
+
+                        (user, next) => {
+                            if (!user) return next("User not found.");
+                            if (user.email.verified)
+                                return next("This email is already verified.");
+                            userModel.updateOne(
+                                { "email.verificationToken": code },
+                                {
+                                    $set: { "email.verified": true },
+                                    $unset: { "email.verificationToken": "" },
+                                },
+                                { runValidators: true },
+                                next
+                            );
+                        },
+                    ],
+                    (err) => {
+                        if (err) {
+                            let error = "An error occurred.";
+                            if (typeof err === "string") error = err;
+                            else if (err.message) error = err.message;
+                            logger.error(
+                                "VERIFY_EMAIL",
+                                `Verifying email failed. "${error}"`
+                            );
+                            return res.json({
+                                status: "failure",
+                                message: error,
+                            });
+                        }
+                        logger.success(
+                            "VERIFY_EMAIL",
+                            `Successfully verified email.`
+                        );
+                        res.redirect(
+                            `${config.get(
+                                "domain"
+                            )}?msg=Thank you for verifying your email`
+                        );
+                    }
+                );
+            });
+
+            resolve();
+        });
+    }
+
+    SERVER(payload) {
+        return new Promise((resolve, reject) => {
+            resolve(this.server);
+        });
+    }
+
+    GET_APP(payload) {
+        return new Promise((resolve, reject) => {
+            resolve({ app: this.app });
+        });
+    }
+
+    EXAMPLE_JOB(payload) {
+        return new Promise((resolve, reject) => {
+            if (true) {
+                resolve({});
+            } else {
+                reject(new Error("Nothing changed."));
+            }
+        });
+    }
 }
+
+module.exports = new AppModule();

+ 261 - 206
backend/logic/cache/index.js

@@ -1,211 +1,266 @@
-'use strict';
+const CoreClass = require("../../core.js");
 
-const coreClass = require("../../core");
-
-const redis = require('redis');
-const config = require('config');
-const mongoose = require('mongoose');
+const redis = require("redis");
+const config = require("config");
+const mongoose = require("mongoose");
 
 // Lightweight / convenience wrapper around redis module for our needs
 
-const pubs = {}, subs = {};
-
-module.exports = class extends coreClass {
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
-
-			this.schemas = {
-				session: require('./schemas/session'),
-				station: require('./schemas/station'),
-				playlist: require('./schemas/playlist'),
-				officialPlaylist: require('./schemas/officialPlaylist'),
-				song: require('./schemas/song'),
-				punishment: require('./schemas/punishment')
-			}
-
-			this.url = config.get("redis").url;
-			this.password = config.get("redis").password;
-
-			this.logger.info("REDIS", "Connecting...");
-
-			this.client = redis.createClient({
-				url: this.url,
-				password: this.password,
-				retry_strategy: (options) => {
-					if (this.state === "LOCKDOWN") return;
-					if (this.state !== "RECONNECTING") this.setState("RECONNECTING");
-
-					this.logger.info("CACHE_MODULE", `Attempting to reconnect.`);
-
-					if (options.attempt >= 10) {
-						this.logger.error("CACHE_MODULE", `Stopped trying to reconnect.`);
-
-						this.failed = true;
-						this._lockdown();
-
-						return undefined;
-					}
-
-					return 3000;
-				}
-			});
-
-			this.client.on('error', err => {
-				if (this.state === "INITIALIZING") reject(err);
-				if(this.state === "LOCKDOWN") return;
-
-				this.logger.error("CACHE_MODULE", `Error ${err.message}.`);
-			});
-
-			this.client.on("connect", () => {
-				this.logger.info("CACHE_MODULE", "Connected succesfully.");
-
-				if (this.state === "INITIALIZING") resolve();
-				else if (this.state === "LOCKDOWN" || this.state === "RECONNECTING") this.setState("INITIALIZED");
-			});
-		});
-	}
-
-	/**
-	 * Gracefully closes all the Redis client connections
-	 */
-	async quit() {
-		try { await this._validateHook(); } catch { return; }
-
-		if (this.client.connected) {
-			this.client.quit();
-			Object.keys(pubs).forEach((channel) => pubs[channel].quit());
-			Object.keys(subs).forEach((channel) => subs[channel].client.quit());
-		}
-	}
-
-	/**
-	 * Sets a single value in a table
-	 *
-	 * @param {String} table - name of the table we want to set a key of (table === redis hash)
-	 * @param {String} key -  name of the key to set
-	 * @param {*} value - the value we want to set
-	 * @param {Function} cb - gets called when the value has been set in Redis
-	 * @param {Boolean} [stringifyJson=true] - stringify 'value' if it's an Object or Array
-	 */
-	async hset(table, key, value, cb, stringifyJson = true) {
-		try { await this._validateHook(); } catch { return; }
-
-		if (mongoose.Types.ObjectId.isValid(key)) key = key.toString();
-		// automatically stringify objects and arrays into JSON
-		if (stringifyJson && ['object', 'array'].includes(typeof value)) value = JSON.stringify(value);
-
-		this.client.hset(table, key, value, err => {
-			if (cb !== undefined) {
-				if (err) return cb(err);
-				cb(null, JSON.parse(value));
-			}
-		});
-	}
-
-	/**
-	 * Gets a single value from a table
-	 *
-	 * @param {String} table - name of the table to get the value from (table === redis hash)
-	 * @param {String} key - name of the key to fetch
-	 * @param {Function} cb - gets called when the value is returned from Redis
-	 * @param {Boolean} [parseJson=true] - attempt to parse returned data as JSON
-	 */
-	async hget(table, key, cb, parseJson = true) {
-		try { await this._validateHook(); } catch { return; }
-
-		if (!key || !table) return typeof cb === 'function' ? cb(null, null) : null;
-		if (mongoose.Types.ObjectId.isValid(key)) key = key.toString();
-
-		this.client.hget(table, key, (err, value) => {
-			if (err) return typeof cb === 'function' ? cb(err) : null;
-			if (parseJson) try {
-				value = JSON.parse(value);
-			} catch (e) {
-			}
-			if (typeof cb === 'function') cb(null, value);
-		});
-	}
-
-	/**
-	 * Deletes a single value from a table
-	 *
-	 * @param {String} table - name of the table to delete the value from (table === redis hash)
-	 * @param {String} key - name of the key to delete
-	 * @param {Function} cb - gets called when the value has been deleted from Redis or when it returned an error
-	 */
-	async hdel(table, key, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		if (!key || !table || typeof key !== "string") return cb(null, null);
-		if (mongoose.Types.ObjectId.isValid(key)) key = key.toString();
-
-		this.client.hdel(table, key, (err) => {
-			if (err) return cb(err);
-			else return cb(null);
-		});
-	}
-
-	/**
-	 * Returns all the keys for a table
-	 *
-	 * @param {String} table - name of the table to get the values from (table === redis hash)
-	 * @param {Function} cb - gets called when the values are returned from Redis
-	 * @param {Boolean} [parseJson=true] - attempts to parse all values as JSON by default
-	 */
-	async hgetall(table, cb, parseJson = true) {
-		try { await this._validateHook(); } catch { return; }
-
-		if (!table) return cb(null, null);
-
-		this.client.hgetall(table, (err, obj) => {
-			if (err) return typeof cb === 'function' ? cb(err) : null;
-			if (parseJson && obj) Object.keys(obj).forEach((key) => { try { obj[key] = JSON.parse(obj[key]); } catch (e) {} });
-			if (parseJson && !obj) obj = [];
-			cb(null, obj);
-		});
-	}
-
-	/**
-	 * Publish a message to a channel, caches the redis client connection
-	 *
-	 * @param {String} channel - the name of the channel we want to publish a message to
-	 * @param {*} value - the value we want to send
-	 * @param {Boolean} [stringifyJson=true] - stringify 'value' if it's an Object or Array
-	 */
-	async pub(channel, value, stringifyJson = true) {
-		try { await this._validateHook(); } catch { return; }
-		/*if (pubs[channel] === undefined) {
-		 pubs[channel] = redis.createClient({ url: this.url });
-		 pubs[channel].on('error', (err) => console.error);
-		 }*/
-
-		if (stringifyJson && ['object', 'array'].includes(typeof value)) value = JSON.stringify(value);
-
-		//pubs[channel].publish(channel, value);
-		this.client.publish(channel, value);
-	}
-
-	/**
-	 * Subscribe to a channel, caches the redis client connection
-	 *
-	 * @param {String} channel - name of the channel to subscribe to
-	 * @param {Function} cb - gets called when a message is received
-	 * @param {Boolean} [parseJson=true] - parse the message as JSON
-	 */
-	async sub(channel, cb, parseJson = true) {
-		try { await this._validateHook(); } catch { return; }
-
-		if (subs[channel] === undefined) {
-			subs[channel] = { client: redis.createClient({ url: this.url, password: this.password }), cbs: [] };
-			subs[channel].client.on('message', (channel, message) => {
-				if (parseJson) try { message = JSON.parse(message); } catch (e) {}
-				subs[channel].cbs.forEach((cb) => cb(message));
-			});
-			subs[channel].client.subscribe(channel);
-		}
-
-		subs[channel].cbs.push(cb);
-	}
+const pubs = {},
+    subs = {};
+
+class CacheModule extends CoreClass {
+    constructor() {
+        super("cache");
+    }
+
+    initialize() {
+        return new Promise((resolve, reject) => {
+            this.schemas = {
+                session: require("./schemas/session"),
+                station: require("./schemas/station"),
+                playlist: require("./schemas/playlist"),
+                officialPlaylist: require("./schemas/officialPlaylist"),
+                song: require("./schemas/song"),
+                punishment: require("./schemas/punishment"),
+            };
+
+            this.url = config.get("redis").url;
+            this.password = config.get("redis").password;
+
+            this.log("INFO", "Connecting...");
+
+            this.client = redis.createClient({
+                url: this.url,
+                password: this.password,
+                retry_strategy: (options) => {
+                    if (this.getStatus() === "LOCKDOWN") return;
+                    if (this.getStatus() !== "RECONNECTING")
+                        this.setStatus("RECONNECTING");
+
+                    this.log("INFO", `Attempting to reconnect.`);
+
+                    if (options.attempt >= 10) {
+                        this.log("ERROR", `Stopped trying to reconnect.`);
+
+                        this.setStatus("FAILED");
+
+                        // this.failed = true;
+                        // this._lockdown();
+
+                        return undefined;
+                    }
+
+                    return 3000;
+                },
+            });
+
+            this.client.on("error", (err) => {
+                if (this.getStatus() === "INITIALIZING") reject(err);
+                if (this.getStatus() === "LOCKDOWN") return;
+
+                this.log("ERROR", `Error ${err.message}.`);
+            });
+
+            this.client.on("connect", () => {
+                this.log("INFO", "Connected succesfully.");
+
+                if (this.getStatus() === "INITIALIZING") resolve();
+                else if (
+                    this.getStatus() === "FAILED" ||
+                    this.getStatus() === "RECONNECTING"
+                )
+                    this.setStatus("READY");
+            });
+        });
+    }
+
+    /**
+     * Gracefully closes all the Redis client connections
+     */
+    QUIT(payload) {
+        return new Promise((resolve, reject) => {
+            if (this.client.connected) {
+                this.client.quit();
+                Object.keys(pubs).forEach((channel) => pubs[channel].quit());
+                Object.keys(subs).forEach((channel) =>
+                    subs[channel].client.quit()
+                );
+            }
+            resolve();
+        });
+    }
+
+    /**
+     * Sets a single value in a table
+     *
+     * @param {String} table - name of the table we want to set a key of (table === redis hash)
+     * @param {String} key -  name of the key to set
+     * @param {*} value - the value we want to set
+     * @param {Function} cb - gets called when the value has been set in Redis
+     * @param {Boolean} [stringifyJson=true] - stringify 'value' if it's an Object or Array
+     */
+    HSET(payload) {
+        //table, key, value, cb, stringifyJson = true
+        return new Promise((resolve, reject) => {
+            let key = payload.key;
+            let value = payload.value;
+
+            if (mongoose.Types.ObjectId.isValid(key)) key = key.toString();
+            // automatically stringify objects and arrays into JSON
+            if (["object", "array"].includes(typeof value))
+                value = JSON.stringify(value);
+
+            this.client.hset(payload.table, key, value, (err) => {
+                if (err) return reject(new Error(err));
+                else resolve(JSON.parse(value));
+            });
+        });
+    }
+
+    /**
+     * Gets a single value from a table
+     *
+     * @param {String} table - name of the table to get the value from (table === redis hash)
+     * @param {String} key - name of the key to fetch
+     * @param {Function} cb - gets called when the value is returned from Redis
+     * @param {Boolean} [parseJson=true] - attempt to parse returned data as JSON
+     */
+    HGET(payload) {
+        //table, key, cb, parseJson = true
+        return new Promise((resolve, reject) => {
+            // if (!key || !table)
+            // return typeof cb === "function" ? cb(null, null) : null;
+            let key = payload.key;
+
+            if (mongoose.Types.ObjectId.isValid(key)) key = key.toString();
+
+            this.client.hget(payload.table, key, (err, value) => {
+                if (err) return reject(new Error(err));
+                try {
+                    value = JSON.parse(value);
+                } catch (e) {}
+                resolve(value);
+            });
+        });
+    }
+
+    /**
+     * Deletes a single value from a table
+     *
+     * @param {String} table - name of the table to delete the value from (table === redis hash)
+     * @param {String} key - name of the key to delete
+     * @param {Function} cb - gets called when the value has been deleted from Redis or when it returned an error
+     */
+    HDEL(payload) {
+        //table, key, cb
+        return new Promise((resolve, reject) => {
+            // if (!payload.key || !table || typeof key !== "string")
+            // return cb(null, null);
+
+            let key = payload.key;
+
+            if (mongoose.Types.ObjectId.isValid(key)) key = key.toString();
+
+            this.client.hdel(payload.table, key, (err) => {
+                if (err) return reject(new Error(err));
+                else return resolve();
+            });
+        });
+    }
+
+    /**
+     * Returns all the keys for a table
+     *
+     * @param {String} table - name of the table to get the values from (table === redis hash)
+     * @param {Function} cb - gets called when the values are returned from Redis
+     * @param {Boolean} [parseJson=true] - attempts to parse all values as JSON by default
+     */
+    HGETALL(payload) {
+        //table, cb, parseJson = true
+        return new Promise((resolve, reject) => {
+            this.client.hgetall(payload.table, (err, obj) => {
+                if (err) return reject(new Error(err));
+                if (obj)
+                    Object.keys(obj).forEach((key) => {
+                        try {
+                            obj[key] = JSON.parse(obj[key]);
+                        } catch (e) {}
+                    });
+                else if (!obj) obj = [];
+                resolve(obj);
+            });
+        });
+    }
+
+    /**
+     * Publish a message to a channel, caches the redis client connection
+     *
+     * @param {String} channel - the name of the channel we want to publish a message to
+     * @param {*} value - the value we want to send
+     * @param {Boolean} [stringifyJson=true] - stringify 'value' if it's an Object or Array
+     */
+    PUB(payload) {
+        //channel, value, stringifyJson = true
+        return new Promise((resolve, reject) => {
+            /*if (pubs[channel] === undefined) {
+            pubs[channel] = redis.createClient({ url: this.url });
+            pubs[channel].on('error', (err) => console.error);
+            }*/
+
+            let value = payload.value;
+
+            if (["object", "array"].includes(typeof value))
+                value = JSON.stringify(value);
+
+            //pubs[channel].publish(channel, value);
+            this.client.publish(payload.channel, value);
+
+            resolve();
+        });
+    }
+
+    /**
+     * Subscribe to a channel, caches the redis client connection
+     *
+     * @param {String} channel - name of the channel to subscribe to
+     * @param {Function} cb - gets called when a message is received
+     * @param {Boolean} [parseJson=true] - parse the message as JSON
+     */
+    SUB(payload) {
+        //channel, cb, parseJson = true
+        return new Promise((resolve, reject) => {
+            if (subs[payload.channel] === undefined) {
+                subs[payload.channel] = {
+                    client: redis.createClient({
+                        url: this.url,
+                        password: this.password,
+                    }),
+                    cbs: [],
+                };
+                subs[payload.channel].client.on(
+                    "message",
+                    (channel, message) => {
+                        try {
+                            message = JSON.parse(message);
+                        } catch (e) {}
+                        subs[channel].cbs.forEach((cb) => cb(message));
+                    }
+                );
+                subs[payload.channel].client.subscribe(payload.channel);
+            }
+
+            subs[payload.channel].cbs.push(payload.cb);
+
+            resolve();
+        });
+    }
+
+    GET_SCHEMA(payload) {
+        return new Promise((resolve, reject) => {
+            resolve(this.schemas[payload.schemaName]);
+        });
+    }
 }
+
+module.exports = new CacheModule();

+ 324 - 190
backend/logic/db/index.js

@@ -1,127 +1,199 @@
-'use strict';
+const CoreClass = require("../../core.js");
 
-const coreClass = require("../../core");
-
-const mongoose = require('mongoose');
-const config = require('config');
+const mongoose = require("mongoose");
+const config = require("config");
 
 const regex = {
-	azAZ09_: /^[A-Za-z0-9_]+$/,
-	az09_: /^[a-z0-9_]+$/,
-	emailSimple: /^[\x00-\x7F]+@[a-z0-9]+\.[a-z0-9]+(\.[a-z0-9]+)?$/,
-	ascii: /^[\x00-\x7F]+$/,
-	custom: regex => new RegExp(`^[${regex}]+$`)
+    azAZ09_: /^[A-Za-z0-9_]+$/,
+    az09_: /^[a-z0-9_]+$/,
+    emailSimple: /^[\x00-\x7F]+@[a-z0-9]+\.[a-z0-9]+(\.[a-z0-9]+)?$/,
+    ascii: /^[\x00-\x7F]+$/,
+    custom: (regex) => new RegExp(`^[${regex}]+$`),
 };
 
 const isLength = (string, min, max) => {
-	return !(typeof string !== 'string' || string.length < min || string.length > max);
-}
+    return !(
+        typeof string !== "string" ||
+        string.length < min ||
+        string.length > max
+    );
+};
 
-const bluebird = require('bluebird');
+const bluebird = require("bluebird");
 
 mongoose.Promise = bluebird;
 
-module.exports = class extends coreClass {
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
-
-			this.schemas = {};
-			this.models = {};
-
-			const mongoUrl = config.get("mongo").url;
-
-			mongoose.connect(mongoUrl, {
-				useNewUrlParser: true,
-				useCreateIndex: true,
-				reconnectInterval: 3000,
-				reconnectTries: 10
-			})
-				.then(() => {
-					this.schemas = {
-						song: new mongoose.Schema(require(`./schemas/song`)),
-						queueSong: new mongoose.Schema(require(`./schemas/queueSong`)),
-						station: new mongoose.Schema(require(`./schemas/station`)),
-						user: new mongoose.Schema(require(`./schemas/user`)),
-						playlist: new mongoose.Schema(require(`./schemas/playlist`)),
-						news: new mongoose.Schema(require(`./schemas/news`)),
-						report: new mongoose.Schema(require(`./schemas/report`)),
-						punishment: new mongoose.Schema(require(`./schemas/punishment`))
-					};
-		
-					this.models = {
-						song: mongoose.model('song', this.schemas.song),
-						queueSong: mongoose.model('queueSong', this.schemas.queueSong),
-						station: mongoose.model('station', this.schemas.station),
-						user: mongoose.model('user', this.schemas.user),
-						playlist: mongoose.model('playlist', this.schemas.playlist),
-						news: mongoose.model('news', this.schemas.news),
-						report: mongoose.model('report', this.schemas.report),
-						punishment: mongoose.model('punishment', this.schemas.punishment)
-					};
-
-					mongoose.connection.on('error', err => {
-						this.logger.error("DB_MODULE", err);
-					});
-
-					mongoose.connection.on('disconnected', () => {
-						this.logger.error("DB_MODULE", "Disconnected, going to try to reconnect...");
-						this.setState("RECONNECTING");
-					});
-
-					mongoose.connection.on('reconnected', () => {
-						this.logger.success("DB_MODULE", "Reconnected.");
-						this.setState("INITIALIZED");
-					});
-
-					mongoose.connection.on('reconnectFailed', () => {
-						this.logger.error("DB_MODULE", "Reconnect failed, stopping reconnecting.");
-						this.failed = true;
-						this._lockdown();
-					});
-		
-					// User
-					this.schemas.user.path('username').validate((username) => {
-						return (isLength(username, 2, 32) && regex.custom("a-zA-Z0-9_-").test(username));
-					}, 'Invalid username.');
-		
-					this.schemas.user.path('email.address').validate((email) => {
-						if (!isLength(email, 3, 254)) return false;
-						if (email.indexOf('@') !== email.lastIndexOf('@')) return false;
-						return regex.emailSimple.test(email) && regex.ascii.test(email);
-					}, 'Invalid email.');
-
-					// Station
-					this.schemas.station.path('name').validate((id) => {
-						return (isLength(id, 2, 16) && regex.az09_.test(id));
-					}, 'Invalid station name.');
-		
-					this.schemas.station.path('displayName').validate((displayName) => {
-						return (isLength(displayName, 2, 32) && regex.ascii.test(displayName));
-					}, 'Invalid display name.');
-		
-					this.schemas.station.path('description').validate((description) => {
-						if (!isLength(description, 2, 200)) return false;
-						let characters = description.split("");
-						return characters.filter((character) => {
-							return character.charCodeAt(0) === 21328;
-						}).length === 0;
-					}, 'Invalid display name.');
-		
-					this.schemas.station.path('owner').validate({
-						validator: (owner) => {
-							return new Promise((resolve, reject) => {
-								this.models.station.countDocuments({ owner: owner }, (err, c) => {
-									if (err) reject(new Error("A mongo error happened."));
-									else if (c >= 3) reject(new Error("User already has 3 stations."));
-									else resolve();
-								});
-							});
-						},
-						message: 'User already has 3 stations.'
-					});
-		
-					/*
+class DBModule extends CoreClass {
+    constructor() {
+        super("db");
+    }
+
+    initialize() {
+        return new Promise((resolve, reject) => {
+            this.schemas = {};
+            this.models = {};
+
+            const mongoUrl = config.get("mongo").url;
+
+            mongoose
+                .connect(mongoUrl, {
+                    useNewUrlParser: true,
+                    useCreateIndex: true,
+                    reconnectInterval: 3000,
+                    reconnectTries: 10,
+                })
+                .then(() => {
+                    this.schemas = {
+                        song: new mongoose.Schema(require(`./schemas/song`)),
+                        queueSong: new mongoose.Schema(
+                            require(`./schemas/queueSong`)
+                        ),
+                        station: new mongoose.Schema(
+                            require(`./schemas/station`)
+                        ),
+                        user: new mongoose.Schema(require(`./schemas/user`)),
+                        activity: new mongoose.Schema(
+                            require(`./schemas/activity`)
+                        ),
+                        playlist: new mongoose.Schema(
+                            require(`./schemas/playlist`)
+                        ),
+                        news: new mongoose.Schema(require(`./schemas/news`)),
+                        report: new mongoose.Schema(
+                            require(`./schemas/report`)
+                        ),
+                        punishment: new mongoose.Schema(
+                            require(`./schemas/punishment`)
+                        ),
+                    };
+
+                    this.models = {
+                        song: mongoose.model("song", this.schemas.song),
+                        queueSong: mongoose.model(
+                            "queueSong",
+                            this.schemas.queueSong
+                        ),
+                        station: mongoose.model(
+                            "station",
+                            this.schemas.station
+                        ),
+                        user: mongoose.model("user", this.schemas.user),
+                        activity: mongoose.model(
+                            "activity",
+                            this.schemas.activity
+                        ),
+                        playlist: mongoose.model(
+                            "playlist",
+                            this.schemas.playlist
+                        ),
+                        news: mongoose.model("news", this.schemas.news),
+                        report: mongoose.model("report", this.schemas.report),
+                        punishment: mongoose.model(
+                            "punishment",
+                            this.schemas.punishment
+                        ),
+                    };
+
+                    mongoose.connection.on("error", (err) => {
+                        this.log("ERROR", err);
+                    });
+
+                    mongoose.connection.on("disconnected", () => {
+                        this.log(
+                            "ERROR",
+                            "Disconnected, going to try to reconnect..."
+                        );
+                        this.setStatus("RECONNECTING");
+                    });
+
+                    mongoose.connection.on("reconnected", () => {
+                        this.log("INFO", "Reconnected.");
+                        this.setStatus("READY");
+                    });
+
+                    mongoose.connection.on("reconnectFailed", () => {
+                        this.log(
+                            "INFO",
+                            "Reconnect failed, stopping reconnecting."
+                        );
+                        // this.failed = true;
+                        // this._lockdown();
+                        this.setStatus("FAILED");
+                    });
+
+                    // User
+                    this.schemas.user.path("username").validate((username) => {
+                        return (
+                            isLength(username, 2, 32) &&
+                            regex.custom("a-zA-Z0-9_-").test(username)
+                        );
+                    }, "Invalid username.");
+
+                    this.schemas.user
+                        .path("email.address")
+                        .validate((email) => {
+                            if (!isLength(email, 3, 254)) return false;
+                            if (email.indexOf("@") !== email.lastIndexOf("@"))
+                                return false;
+                            return (
+                                regex.emailSimple.test(email) &&
+                                regex.ascii.test(email)
+                            );
+                        }, "Invalid email.");
+
+                    // Station
+                    this.schemas.station.path("name").validate((id) => {
+                        return isLength(id, 2, 16) && regex.az09_.test(id);
+                    }, "Invalid station name.");
+
+                    this.schemas.station
+                        .path("displayName")
+                        .validate((displayName) => {
+                            return (
+                                isLength(displayName, 2, 32) &&
+                                regex.ascii.test(displayName)
+                            );
+                        }, "Invalid display name.");
+
+                    this.schemas.station
+                        .path("description")
+                        .validate((description) => {
+                            if (!isLength(description, 2, 200)) return false;
+                            let characters = description.split("");
+                            return (
+                                characters.filter((character) => {
+                                    return character.charCodeAt(0) === 21328;
+                                }).length === 0
+                            );
+                        }, "Invalid display name.");
+
+                    this.schemas.station.path("owner").validate({
+                        validator: (owner) => {
+                            return new Promise((resolve, reject) => {
+                                this.models.station.countDocuments(
+                                    { owner: owner },
+                                    (err, c) => {
+                                        if (err)
+                                            reject(
+                                                new Error(
+                                                    "A mongo error happened."
+                                                )
+                                            );
+                                        else if (c >= 3)
+                                            reject(
+                                                new Error(
+                                                    "User already has 3 stations."
+                                                )
+                                            );
+                                        else resolve();
+                                    }
+                                );
+                            });
+                        },
+                        message: "User already has 3 stations.",
+                    });
+
+                    /*
 					this.schemas.station.path('queue').validate((queue, callback) => { //Callback no longer works, see station max count
 						let totalDuration = 0;
 						queue.forEach((song) => {
@@ -158,81 +230,143 @@ module.exports = class extends coreClass {
 					}, 'The max amount of songs per user is 3, and only 2 in a row is allowed.');
 					*/
 
+                    // Song
+                    let songTitle = (title) => {
+                        return isLength(title, 1, 100);
+                    };
+                    this.schemas.song
+                        .path("title")
+                        .validate(songTitle, "Invalid title.");
+                    this.schemas.queueSong
+                        .path("title")
+                        .validate(songTitle, "Invalid title.");
 
-					// Song
-					let songTitle = (title) => {
-						return isLength(title, 1, 100);
-					};
-					this.schemas.song.path('title').validate(songTitle, 'Invalid title.');
-					this.schemas.queueSong.path('title').validate(songTitle, 'Invalid title.');
-		
-					this.schemas.song.path('artists').validate((artists) => {
-						return !(artists.length < 1 || artists.length > 10);
-					}, 'Invalid artists.');
-					this.schemas.queueSong.path('artists').validate((artists) => {
-						return !(artists.length < 0 || artists.length > 10);
-					}, 'Invalid artists.');
-		
-					let songArtists = (artists) => {
-						return artists.filter((artist) => {
-								return (isLength(artist, 1, 64) && artist !== "NONE");
-							}).length === artists.length;
-					};
-					this.schemas.song.path('artists').validate(songArtists, 'Invalid artists.');
-					this.schemas.queueSong.path('artists').validate(songArtists, 'Invalid artists.');
-		
-					let songGenres = (genres) => {
-						if (genres.length < 1 || genres.length > 16) return false;
-						return genres.filter((genre) => {
-								return (isLength(genre, 1, 32) && regex.ascii.test(genre));
-							}).length === genres.length;
-					};
-					this.schemas.song.path('genres').validate(songGenres, 'Invalid genres.');
-					this.schemas.queueSong.path('genres').validate(songGenres, 'Invalid genres.');
-		
-					let songThumbnail = (thumbnail) => {
-						if (!isLength(thumbnail, 1, 256)) return false;
-						if (config.get("cookie.secure") === true) return thumbnail.startsWith("https://");
-						else return thumbnail.startsWith("http://") || thumbnail.startsWith("https://");
-					};
-					this.schemas.song.path('thumbnail').validate(songThumbnail, 'Invalid thumbnail.');
-					this.schemas.queueSong.path('thumbnail').validate(songThumbnail, 'Invalid thumbnail.');
-
-					// Playlist
-					this.schemas.playlist.path('displayName').validate((displayName) => {
-						return (isLength(displayName, 1, 32) && regex.ascii.test(displayName));
-					}, 'Invalid display name.');
-		
-					this.schemas.playlist.path('createdBy').validate((createdBy) => {
-						this.models.playlist.countDocuments({ createdBy: createdBy }, (err, c) => {
-							return !(err || c >= 10);
-						});
-					}, 'Max 10 playlists per user.');
-		
-					this.schemas.playlist.path('songs').validate((songs) => {
-						return songs.length <= 5000;
-					}, 'Max 5000 songs per playlist.');
-		
-					this.schemas.playlist.path('songs').validate((songs) => {
-						if (songs.length === 0) return true;
-						return songs[0].duration <= 10800;
-					}, 'Max 3 hours per song.');
-		
-					// Report
-					this.schemas.report.path('description').validate((description) => {
-						return (!description || (isLength(description, 0, 400) && regex.ascii.test(description)));
-					}, 'Invalid description.');
-
-					resolve();
-				})
-				.catch(err => {
-					this.logger.error("DB_MODULE", err);
-					reject(err);
-				});
-		})
-	}
-
-	passwordValid(password) {
-		return isLength(password, 6, 200);
-	}
+                    this.schemas.song.path("artists").validate((artists) => {
+                        return !(artists.length < 1 || artists.length > 10);
+                    }, "Invalid artists.");
+                    this.schemas.queueSong
+                        .path("artists")
+                        .validate((artists) => {
+                            return !(artists.length < 0 || artists.length > 10);
+                        }, "Invalid artists.");
+
+                    let songArtists = (artists) => {
+                        return (
+                            artists.filter((artist) => {
+                                return (
+                                    isLength(artist, 1, 64) && artist !== "NONE"
+                                );
+                            }).length === artists.length
+                        );
+                    };
+                    this.schemas.song
+                        .path("artists")
+                        .validate(songArtists, "Invalid artists.");
+                    this.schemas.queueSong
+                        .path("artists")
+                        .validate(songArtists, "Invalid artists.");
+
+                    let songGenres = (genres) => {
+                        if (genres.length < 1 || genres.length > 16)
+                            return false;
+                        return (
+                            genres.filter((genre) => {
+                                return (
+                                    isLength(genre, 1, 32) &&
+                                    regex.ascii.test(genre)
+                                );
+                            }).length === genres.length
+                        );
+                    };
+                    this.schemas.song
+                        .path("genres")
+                        .validate(songGenres, "Invalid genres.");
+                    this.schemas.queueSong
+                        .path("genres")
+                        .validate(songGenres, "Invalid genres.");
+
+                    let songThumbnail = (thumbnail) => {
+                        if (!isLength(thumbnail, 1, 256)) return false;
+                        if (config.get("cookie.secure") === true)
+                            return thumbnail.startsWith("https://");
+                        else
+                            return (
+                                thumbnail.startsWith("http://") ||
+                                thumbnail.startsWith("https://")
+                            );
+                    };
+                    this.schemas.song
+                        .path("thumbnail")
+                        .validate(songThumbnail, "Invalid thumbnail.");
+                    this.schemas.queueSong
+                        .path("thumbnail")
+                        .validate(songThumbnail, "Invalid thumbnail.");
+
+                    // Playlist
+                    this.schemas.playlist
+                        .path("displayName")
+                        .validate((displayName) => {
+                            return (
+                                isLength(displayName, 1, 32) &&
+                                regex.ascii.test(displayName)
+                            );
+                        }, "Invalid display name.");
+
+                    this.schemas.playlist
+                        .path("createdBy")
+                        .validate((createdBy) => {
+                            this.models.playlist.countDocuments(
+                                { createdBy: createdBy },
+                                (err, c) => {
+                                    return !(err || c >= 10);
+                                }
+                            );
+                        }, "Max 10 playlists per user.");
+
+                    this.schemas.playlist.path("songs").validate((songs) => {
+                        return songs.length <= 5000;
+                    }, "Max 5000 songs per playlist.");
+
+                    this.schemas.playlist.path("songs").validate((songs) => {
+                        if (songs.length === 0) return true;
+                        return songs[0].duration <= 10800;
+                    }, "Max 3 hours per song.");
+
+                    // Report
+                    this.schemas.report
+                        .path("description")
+                        .validate((description) => {
+                            return (
+                                !description ||
+                                (isLength(description, 0, 400) &&
+                                    regex.ascii.test(description))
+                            );
+                        }, "Invalid description.");
+
+                    resolve();
+                })
+                .catch((err) => {
+                    this.log("ERROR", err);
+                    reject(err);
+                });
+        });
+    }
+
+    GET_MODEL(payload) {
+        return new Promise((resolve, reject) => {
+            resolve(this.models[payload.modelName]);
+        });
+    }
+
+    GET_SCHEMA(payload) {
+        return new Promise((resolve, reject) => {
+            resolve(this.schemas[payload.schemaName]);
+        });
+    }
+
+    passwordValid(password) {
+        return isLength(password, 6, 200);
+    }
 }
+
+module.exports = new DBModule();

+ 16 - 0
backend/logic/db/schemas/activity.js

@@ -0,0 +1,16 @@
+module.exports = {
+	createdAt: { type: Date, default: Date.now, required: true },
+	hidden: { type: Boolean, default: false, required: true },
+	userId: { type: String, required: true },
+	activityType: { type: String, enum: [
+		"created_account",
+		"created_station",
+		"deleted_station",
+		"created_playlist",
+		"deleted_playlist",
+		"liked_song",
+		"added_song_to_playlist",
+		"added_songs_to_playlist"
+	], required: true },
+	payload: { type: Array, required: true }
+}

+ 1 - 1
backend/logic/db/schemas/news.js

@@ -6,5 +6,5 @@ module.exports = {
 	improvements: [{ type: String }],
 	upcoming: [{ type: String }],
 	createdBy: { type: String, required: true },
-	createdAt: { type: Number, default: Date.now(), required: true }
+	createdAt: { type: Number, default: Date.now, required: true }
 };

+ 1 - 1
backend/logic/db/schemas/playlist.js

@@ -2,5 +2,5 @@ module.exports = {
 	displayName: { type: String, min: 2, max: 32, required: true },
 	songs: { type: Array },
 	createdBy: { type: String, required: true },
-	createdAt: { type: Date, default: Date.now(), required: true }
+	createdAt: { type: Date, default: Date.now, required: true }
 };

+ 1 - 1
backend/logic/db/schemas/punishment.js

@@ -4,6 +4,6 @@ module.exports = {
 	reason: { type: String, required: true, default: 'Unknown' },
 	active: { type: Boolean, required: true, default: true },
 	expiresAt: { type: Date, required: true },
-	punishedAt: { type: Date, default: Date.now(), required: true },
+	punishedAt: { type: Date, default: Date.now, required: true },
 	punishedBy: { type: String, required: true }
 };

+ 1 - 1
backend/logic/db/schemas/queueSong.js

@@ -8,6 +8,6 @@ module.exports = {
 	thumbnail: { type: String, required: true },
 	explicit: { type: Boolean, required: true },
 	requestedBy: { type: String, required: true },
-	requestedAt: { type: Date, default: Date.now(), required: true },
+	requestedAt: { type: Date, default: Date.now, required: true },
 	discogs: { type: Object }
 };

+ 1 - 1
backend/logic/db/schemas/report.js

@@ -10,5 +10,5 @@ module.exports = {
 		reasons: Array
 	}],
 	createdBy: { type: String, required: true },
-	createdAt: { type: Date, default: Date.now(), required: true }
+	createdAt: { type: Date, default: Date.now, required: true }
 };

+ 1 - 1
backend/logic/db/schemas/song.js

@@ -12,6 +12,6 @@ module.exports = {
 	requestedBy: { type: String, required: true },
 	requestedAt: { type: Date, required: true },
 	acceptedBy: { type: String, required: true },
-	acceptedAt: { type: Date, default: Date.now(), required: true },
+	acceptedAt: { type: Date, default: Date.now, required: true },
 	discogs: { type: Object }
 };

+ 8 - 1
backend/logic/db/schemas/user.js

@@ -6,6 +6,10 @@ module.exports = {
 		verificationToken: String,
 		address: String
 	},
+	avatar: {
+		type: { type: String, enum: ["gravatar", "initials"] },
+		url: { type: String, required: false }
+	},
 	services: {
 		password: {
 			password: String,
@@ -29,5 +33,8 @@ module.exports = {
 	liked: [{ type: String }],
 	disliked: [{ type: String }],
 	favoriteStations: [{ type: String }],
-	createdAt: { type: Date, default: Date.now() }
+	name: { type: String, default: "" },
+	location: { type: String, default: "" },
+	bio: { type: String, default: "" },
+	createdAt: { type: Date, default: Date.now }
 };

+ 99 - 77
backend/logic/discord.js

@@ -1,91 +1,113 @@
-const coreClass = require("../core");
+const CoreClass = require("../core.js");
 
-const EventEmitter = require('events');
 const Discord = require("discord.js");
 const config = require("config");
 
-const bus = new EventEmitter();
+class DiscordModule extends CoreClass {
+    constructor() {
+        super("discord");
+    }
 
-module.exports = class extends coreClass {
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
+    initialize() {
+        return new Promise((resolve, reject) => {
+            this.log("INFO", "Discord initialize");
 
-			this.client = new Discord.Client();
-			this.adminAlertChannelId = config.get("apis.discord").loggingChannel;
-			
-			this.client.on("ready", () => {
-				this.logger.info("DISCORD_MODULE", `Logged in as ${this.client.user.tag}!`);
+            this.client = new Discord.Client();
+            this.adminAlertChannelId = config.get(
+                "apis.discord"
+            ).loggingChannel;
 
-				if (this.state === "INITIALIZING") resolve();
-				else {
-					this.logger.info("DISCORD_MODULE", `Discord client reconnected.`);
-					this.setState("INITIALIZED");
-				}
-			});
-		  
-			this.client.on("disconnect", () => {
-				this.logger.info("DISCORD_MODULE", `Discord client disconnected.`);
+            this.client.on("ready", () => {
+                this.log("INFO", `Logged in as ${this.client.user.tag}!`);
 
-				if (this.state === "INITIALIZING") reject();
-				else {
-					this.failed = true;
-					this._lockdown;
-				} 
-			});
+                if (this.getStatus() === "INITIALIZING") {
+                    resolve();
+                } else if (this.getStatus() === "RECONNECTING") {
+                    this.log("INFO", `Discord client reconnected.`);
+                    this.setStatus("READY");
+                }
+            });
 
-			this.client.on("reconnecting", () => {
-				this.logger.info("DISCORD_MODULE", `Discord client reconnecting.`);
-				this.setState("RECONNECTING");
-			});
-		
-			this.client.on("error", err => {
-				this.logger.info("DISCORD_MODULE", `Discord client encountered an error: ${err.message}.`);
-			});
+            this.client.on("disconnect", () => {
+                this.log("INFO", `Discord client disconnected.`);
 
-			this.client.login(config.get("apis.discord").token);
-		});
-	}
+                if (this.getStatus() === "INITIALIZING") reject();
+                else {
+                    this.setStatus("DISCONNECTED");
+                }
+            });
 
-	async sendAdminAlertMessage(message, color, type, critical, extraFields) {
-		try { await this._validateHook(); } catch { return; }
+            this.client.on("reconnecting", () => {
+                this.log("INFO", `Discord client reconnecting.`);
+                this.setStatus("RECONNECTING");
+            });
 
-		const channel = this.client.channels.find(channel => channel.id === this.adminAlertChannelId);
-		if (channel !== null) {
-			let richEmbed = new Discord.RichEmbed();
-			richEmbed.setAuthor(
-				"Musare Logger",
-				`${config.get("domain")}/favicon-194x194.png`,
-				config.get("domain")
-			);
-			richEmbed.setColor(color);
-			richEmbed.setDescription(message);
-			//richEmbed.setFooter("Footer", "https://musare.com/favicon-194x194.png");
-			//richEmbed.setImage("https://musare.com/favicon-194x194.png");
-			//richEmbed.setThumbnail("https://musare.com/favicon-194x194.png");
-			richEmbed.setTimestamp(new Date());
-			richEmbed.setTitle("MUSARE ALERT");
-			richEmbed.setURL(config.get("domain"));
-			richEmbed.addField("Type:", type, true);
-			richEmbed.addField("Critical:", critical ? "True" : "False", true);
-			extraFields.forEach(extraField => {
-				richEmbed.addField(
-					extraField.name,
-					extraField.value,
-					extraField.inline
-				);
-			});
+            this.client.on("error", (err) => {
+                this.log(
+                    "INFO",
+                    `Discord client encountered an error: ${err.message}.`
+                );
+            });
 
-			channel
-			.send(message, { embed: richEmbed })
-			.then(message =>
-				this.logger.success("SEND_ADMIN_ALERT_MESSAGE", `Sent admin alert message: ${message}`)
-			)
-			.catch(() =>
-				this.logger.error("SEND_ADMIN_ALERT_MESSAGE", "Couldn't send admin alert message")
-			);
-		} else {
-			this.logger.error("SEND_ADMIN_ALERT_MESSAGE", "Couldn't send admin alert message, channel was not found.");
-		}
-	}
+            this.client.login(config.get("apis.discord").token);
+        });
+    }
+
+    SEND_ADMIN_ALERT_MESSAGE(payload) {
+        return new Promise((resolve, reject) => {
+            const channel = this.client.channels.find(
+                (channel) => channel.id === this.adminAlertChannelId
+            );
+            if (channel !== null) {
+                let richEmbed = new Discord.RichEmbed();
+                richEmbed.setAuthor(
+                    "Musare Logger",
+                    `${config.get("domain")}/favicon-194x194.png`,
+                    config.get("domain")
+                );
+                richEmbed.setColor(payload.color);
+                richEmbed.setDescription(payload.message);
+                //richEmbed.setFooter("Footer", "https://musare.com/favicon-194x194.png");
+                //richEmbed.setImage("https://musare.com/favicon-194x194.png");
+                //richEmbed.setThumbnail("https://musare.com/favicon-194x194.png");
+                richEmbed.setTimestamp(new Date());
+                richEmbed.setTitle("MUSARE ALERT");
+                richEmbed.setURL(config.get("domain"));
+                richEmbed.addField("Type:", payload.type, true);
+                richEmbed.addField(
+                    "Critical:",
+                    payload.critical ? "True" : "False",
+                    true
+                );
+                payload.extraFields.forEach((extraField) => {
+                    richEmbed.addField(
+                        extraField.name,
+                        extraField.value,
+                        extraField.inline
+                    );
+                });
+
+                channel
+                    .send(payload.message, { embed: richEmbed })
+                    .then((message) =>
+                        resolve({
+                            status: "success",
+                            message: `Successfully sent admin alert message: ${message}`,
+                        })
+                    )
+                    .catch(() =>
+                        reject(new Error("Couldn't send admin alert message"))
+                    );
+            } else {
+                reject(new Error("Channel was not found"));
+            }
+            // if (true) {
+            //     resolve({});
+            // } else {
+            //     reject(new Error("Nothing changed."));
+            // }
+        });
+    }
 }
+
+module.exports = new DiscordModule();

+ 376 - 188
backend/logic/io.js

@@ -1,194 +1,382 @@
-'use strict';
-
-// This file contains all the logic for Socket.IO
-
-const coreClass = require("../core");
+const CoreClass = require("../core.js");
 
 const socketio = require("socket.io");
 const async = require("async");
 const config = require("config");
 
-module.exports = class extends coreClass {
-	constructor(name, moduleManager) {
-		super(name, moduleManager);
-
-		this.dependsOn = ["app", "db", "cache", "utils"];
-	}
-
-	initialize() {
-		return new Promise(resolve => {
-			this.setStage(1);
-
-			const 	logger		= this.logger,
-					app			= this.moduleManager.modules["app"],
-					cache		= this.moduleManager.modules["cache"],
-					utils		= this.moduleManager.modules["utils"],
-					db			= this.moduleManager.modules["db"],
-					punishments	= this.moduleManager.modules["punishments"];
-			
-			const actions = require('../logic/actions');
-
-			const SIDname = config.get("cookie.SIDname");
-
-			// TODO: Check every 30s/60s, for all sockets, if they are still allowed to be in the rooms they are in, and on socket at all (permission changing/banning)
-			this._io = socketio(app.server);
-
-			this._io.use(async (socket, next) => {
-				try { await this._validateHook(); } catch { return; }
-
-				let SID;
-
-				socket.ip = socket.request.headers['x-forwarded-for'] || '0.0.0.0';
-
-				async.waterfall([
-					(next) => {
-						utils.parseCookies(
-							socket.request.headers.cookie
-						).then(res => {
-							SID = res[SIDname];
-							next(null);
-						});
-					},
-
-					(next) => {
-						if (!SID) return next('No SID.');
-						next();
-					},
-
-					(next) => {
-						cache.hget('sessions', SID, next);
-					},
-
-					(session, next) => {
-						if (!session) return next('No session found.');
-
-						session.refreshDate = Date.now();
-						
-						socket.session = session;
-						cache.hset('sessions', SID, session, next);
-					},
-
-					(res, next) => {
-						// check if a session's user / IP is banned
-						punishments.getPunishments((err, punishments) => {
-							const isLoggedIn = !!(socket.session && socket.session.refreshDate);
-							const userId = (isLoggedIn) ? socket.session.userId : null;
-
-							let banishment = { banned: false, ban: 0 };
-
-							punishments.forEach(punishment => {
-								if (punishment.expiresAt > banishment.ban) banishment.ban = punishment;
-								if (punishment.type === 'banUserId' && isLoggedIn && punishment.value === userId) banishment.banned = true;
-								if (punishment.type === 'banUserIp' && punishment.value === socket.ip) banishment.banned = true;
-							});
-							
-							socket.banishment = banishment;
-
-							next();
-						});
-					}
-				], () => {
-					if (!socket.session) socket.session = { socketId: socket.id };
-					else socket.session.socketId = socket.id;
-
-					next();
-				});
-			});
-
-			this._io.on('connection', async socket => {
-				try { await this._validateHook(); } catch { return; }
-
-				let sessionInfo = '';
-				
-				if (socket.session.sessionId) sessionInfo = ` UserID: ${socket.session.userId}.`;
-
-				// if session is banned
-				if (socket.banishment && socket.banishment.banned) {
-					logger.info('IO_BANNED_CONNECTION', `A user tried to connect, but is currently banned. IP: ${socket.ip}.${sessionInfo}`);
-					socket.emit('keep.event:banned', socket.banishment.ban);
-					socket.disconnect(true);
-				} else {
-					logger.info('IO_CONNECTION', `User connected. IP: ${socket.ip}.${sessionInfo}`);
-
-					// catch when the socket has been disconnected
-					socket.on('disconnect', () => {
-						if (socket.session.sessionId) sessionInfo = ` UserID: ${socket.session.userId}.`;
-						logger.info('IO_DISCONNECTION', `User disconnected. IP: ${socket.ip}.${sessionInfo}`);
-					});
-
-					// catch errors on the socket (internal to socket.io)
-					socket.on('error', console.error);
-
-					// have the socket listen for each action
-					Object.keys(actions).forEach(namespace => {
-						Object.keys(actions[namespace]).forEach(action => {
-
-							// the full name of the action
-							let name = `${namespace}.${action}`;
-
-							// listen for this action to be called
-							socket.on(name, async (...args) => {
-								let cb = args[args.length - 1];
-								if (typeof cb !== "function")
-									cb = () => {
-										this.logger.info("IO_MODULE", `There was no callback provided for ${name}.`);
-									}
-								else args.pop();
-
-								try { await this._validateHook(); } catch { return cb({status: 'failure', message: 'Lockdown'}); } 
-
-								// load the session from the cache
-								cache.hget('sessions', socket.session.sessionId, (err, session) => {
-									if (err && err !== true) {
-										if (typeof cb === 'function') return cb({
-											status: 'error',
-											message: 'An error occurred while obtaining your session'
-										});
-									}
-
-									// make sure the sockets sessionId isn't set if there is no session
-									if (socket.session.sessionId && session === null) delete socket.session.sessionId;
-
-									// call the action, passing it the session, and the arguments socket.io passed us
-									actions[namespace][action].apply(null, [socket.session].concat(args).concat([
-										(result) => {
-											// respond to the socket with our message
-											if (typeof cb === 'function') return cb(result);
-										}
-									]));
-								});
-							})
-						})
-					});
-
-					if (socket.session.sessionId) {
-						cache.hget('sessions', socket.session.sessionId, (err, session) => {
-							if (err && err !== true) socket.emit('ready', false);
-							else if (session && session.userId) {
-								db.models.user.findOne({ _id: session.userId }, (err, user) => {
-									if (err || !user) return socket.emit('ready', false);
-									let role = '';
-									let username = '';
-									let userId = '';
-									if (user) {
-										role = user.role;
-										username = user.username;
-										userId = session.userId;
-									}
-									socket.emit('ready', true, role, username, userId);
-								});
-							} else socket.emit('ready', false);
-						})
-					} else socket.emit('ready', false);
-				}
-			});
-
-			resolve();
-		});
-	}
-
-	async io () {
-		try { await this._validateHook(); } catch { return; }
-		return this._io;
-	}
+class IOModule extends CoreClass {
+    constructor() {
+        super("io");
+    }
+
+    initialize() {
+        return new Promise(async (resolve, reject) => {
+            this.setStage(1);
+
+            const app = this.moduleManager.modules["app"],
+                cache = this.moduleManager.modules["cache"],
+                utils = this.moduleManager.modules["utils"],
+                db = this.moduleManager.modules["db"],
+                punishments = this.moduleManager.modules["punishments"];
+
+            const actions = require("./actions");
+
+            this.setStage(2);
+
+            const SIDname = config.get("cookie.SIDname");
+
+            // TODO: Check every 30s/60s, for all sockets, if they are still allowed to be in the rooms they are in, and on socket at all (permission changing/banning)
+            this._io = socketio(await app.runJob("SERVER", {}));
+
+            this.setStage(3);
+
+            this._io.use(async (socket, next) => {
+                if (this.getStatus() !== "READY") {
+                    this.log(
+                        "INFO",
+                        "IO_REJECTED_CONNECTION",
+                        `A user tried to connect, but the IO module is currently not ready. IP: ${socket.ip}.${sessionInfo}`
+                    );
+                    return socket.disconnect(true);
+                }
+
+                let SID;
+
+                socket.ip =
+                    socket.request.headers["x-forwarded-for"] || "0.0.0.0";
+
+                async.waterfall(
+                    [
+                        (next) => {
+                            utils
+                                .runJob("PARSE_COOKIES", {
+                                    cookieString: socket.request.headers.cookie,
+                                })
+                                .then((res) => {
+                                    SID = res[SIDname];
+                                    next(null);
+                                });
+                        },
+
+                        (next) => {
+                            if (!SID) return next("No SID.");
+                            next();
+                        },
+
+                        (next) => {
+                            cache
+                                .runJob("HGET", { table: "sessions", key: SID })
+                                .then((session) => {
+                                    next(null, session);
+                                });
+                        },
+
+                        (session, next) => {
+                            if (!session) return next("No session found.");
+
+                            session.refreshDate = Date.now();
+
+                            socket.session = session;
+                            cache
+                                .runJob("HSET", {
+                                    table: "sessions",
+                                    key: SID,
+                                    value: session,
+                                })
+                                .then((session) => {
+                                    next(null, session);
+                                });
+                        },
+
+                        (res, next) => {
+                            // check if a session's user / IP is banned
+                            punishments
+                                .runJob("GET_PUNISHMENTS", {})
+                                .then((punishments) => {
+                                    const isLoggedIn = !!(
+                                        socket.session &&
+                                        socket.session.refreshDate
+                                    );
+                                    const userId = isLoggedIn
+                                        ? socket.session.userId
+                                        : null;
+
+                                    let banishment = { banned: false, ban: 0 };
+
+                                    punishments.forEach((punishment) => {
+                                        if (
+                                            punishment.expiresAt >
+                                            banishment.ban
+                                        )
+                                            banishment.ban = punishment;
+                                        if (
+                                            punishment.type === "banUserId" &&
+                                            isLoggedIn &&
+                                            punishment.value === userId
+                                        )
+                                            banishment.banned = true;
+                                        if (
+                                            punishment.type === "banUserIp" &&
+                                            punishment.value === socket.ip
+                                        )
+                                            banishment.banned = true;
+                                    });
+
+                                    socket.banishment = banishment;
+
+                                    next();
+                                })
+                                .catch(() => {
+                                    next();
+                                });
+                        },
+                    ],
+                    () => {
+                        if (!socket.session)
+                            socket.session = { socketId: socket.id };
+                        else socket.session.socketId = socket.id;
+
+                        next();
+                    }
+                );
+            });
+
+            this.setStage(4);
+
+            this._io.on("connection", async (socket) => {
+                if (this.getStatus() !== "READY") {
+                    this.log(
+                        "INFO",
+                        "IO_REJECTED_CONNECTION",
+                        `A user tried to connect, but the IO module is currently not ready. IP: ${socket.ip}.${sessionInfo}`
+                    );
+                    return socket.disconnect(true);
+                }
+
+                let sessionInfo = "";
+
+                if (socket.session.sessionId)
+                    sessionInfo = ` UserID: ${socket.session.userId}.`;
+
+                // if session is banned
+                if (socket.banishment && socket.banishment.banned) {
+                    this.log(
+                        "INFO",
+                        "IO_BANNED_CONNECTION",
+                        `A user tried to connect, but is currently banned. IP: ${socket.ip}.${sessionInfo}`
+                    );
+                    socket.emit("keep.event:banned", socket.banishment.ban);
+                    socket.disconnect(true);
+                } else {
+                    this.log(
+                        "INFO",
+                        "IO_CONNECTION",
+                        `User connected. IP: ${socket.ip}.${sessionInfo}`
+                    );
+
+                    // catch when the socket has been disconnected
+                    socket.on("disconnect", () => {
+                        if (socket.session.sessionId)
+                            sessionInfo = ` UserID: ${socket.session.userId}.`;
+                        this.log(
+                            "INFO",
+                            "IO_DISCONNECTION",
+                            `User disconnected. IP: ${socket.ip}.${sessionInfo}`
+                        );
+                    });
+
+                    socket.use((data, next) => {
+                        if (data.length === 0)
+                            return next(
+                                new Error("Not enough arguments specified.")
+                            );
+                        else if (typeof data[0] !== "string")
+                            return next(
+                                new Error("First argument must be a string.")
+                            );
+                        else {
+                            const namespaceAction = data[0];
+                            if (
+                                !namespaceAction ||
+                                namespaceAction.indexOf(".") === -1 ||
+                                namespaceAction.indexOf(".") !==
+                                    namespaceAction.lastIndexOf(".")
+                            )
+                                return next(
+                                    new Error("Invalid first argument")
+                                );
+                            const namespace = data[0].split(".")[0];
+                            const action = data[0].split(".")[1];
+                            if (!namespace)
+                                return next(new Error("Invalid namespace."));
+                            else if (!action)
+                                return next(new Error("Invalid action."));
+                            else if (!actions[namespace])
+                                return next(new Error("Namespace not found."));
+                            else if (!actions[namespace][action])
+                                return next(new Error("Action not found."));
+                            else return next();
+                        }
+                    });
+
+                    // catch errors on the socket (internal to socket.io)
+                    socket.on("error", console.error);
+
+                    // have the socket listen for each action
+                    Object.keys(actions).forEach((namespace) => {
+                        Object.keys(actions[namespace]).forEach((action) => {
+                            // the full name of the action
+                            let name = `${namespace}.${action}`;
+
+                            // listen for this action to be called
+                            socket.on(name, async (...args) => {
+                                let cb = args[args.length - 1];
+                                if (typeof cb !== "function")
+                                    cb = () => {
+                                        this.this.log(
+                                            "INFO",
+                                            "IO_MODULE",
+                                            `There was no callback provided for ${name}.`
+                                        );
+                                    };
+                                else args.pop();
+
+                                if (this.getStatus() !== "READY") {
+                                    this.log(
+                                        "INFO",
+                                        "IO_REJECTED_ACTION",
+                                        `A user tried to execute an action, but the IO module is currently not ready. Action: ${namespace}.${action}.`
+                                    );
+                                    return;
+                                } else {
+                                    this.log(
+                                        "INFO",
+                                        "IO_ACTION",
+                                        `A user executed an action. Action: ${namespace}.${action}.`
+                                    );
+                                }
+
+                                // load the session from the cache
+                                cache
+                                    .runJob("HGET", {
+                                        table: "sessions",
+                                        key: socket.session.sessionId,
+                                    })
+                                    .then((session) => {
+                                        // make sure the sockets sessionId isn't set if there is no session
+                                        if (
+                                            socket.session.sessionId &&
+                                            session === null
+                                        )
+                                            delete socket.session.sessionId;
+
+                                        try {
+                                            // call the action, passing it the session, and the arguments socket.io passed us
+                                            actions[namespace][action].apply(
+                                                null,
+                                                [socket.session]
+                                                    .concat(args)
+                                                    .concat([
+                                                        (result) => {
+                                                            this.log(
+                                                                "INFO",
+                                                                "IO_ACTION",
+                                                                `Response to action. Action: ${namespace}.${action}. Response status: ${result.status}`
+                                                            );
+                                                            // respond to the socket with our message
+                                                            if (
+                                                                typeof cb ===
+                                                                "function"
+                                                            )
+                                                                return cb(
+                                                                    result
+                                                                );
+                                                        },
+                                                    ])
+                                            );
+                                        } catch (err) {
+                                            this.log(
+                                                "ERROR",
+                                                "IO_ACTION_ERROR",
+                                                `Some type of exception occurred in the action ${namespace}.${action}. Error message: ${err.message}`
+                                            );
+                                            if (typeof cb === "function")
+                                                return cb({
+                                                    status: "error",
+                                                    message:
+                                                        "An error occurred while executing the specified action.",
+                                                });
+                                        }
+                                    })
+                                    .catch((err) => {
+                                        if (typeof cb === "function")
+                                            return cb({
+                                                status: "error",
+                                                message:
+                                                    "An error occurred while obtaining your session",
+                                            });
+                                    });
+                            });
+                        });
+                    });
+
+                    if (socket.session.sessionId) {
+                        cache
+                            .runJob("HGET", {
+                                table: "sessions",
+                                key: socket.session.sessionId,
+                            })
+                            .then((session) => {
+                                if (session && session.userId) {
+                                    db.runJob("GET_MODEL", {
+                                        modelName: "user",
+                                    }).then((userModel) => {
+                                        userModel.findOne(
+                                            { _id: session.userId },
+                                            (err, user) => {
+                                                if (err || !user)
+                                                    return socket.emit(
+                                                        "ready",
+                                                        false
+                                                    );
+                                                let role = "";
+                                                let username = "";
+                                                let userId = "";
+                                                if (user) {
+                                                    role = user.role;
+                                                    username = user.username;
+                                                    userId = session.userId;
+                                                }
+                                                socket.emit(
+                                                    "ready",
+                                                    true,
+                                                    role,
+                                                    username,
+                                                    userId
+                                                );
+                                            }
+                                        );
+                                    });
+                                } else socket.emit("ready", false);
+                            })
+                            .catch((err) => {
+                                socket.emit("ready", false);
+                            });
+                    } else socket.emit("ready", false);
+                }
+            });
+
+            this.setStage(5);
+
+            resolve();
+        });
+    }
+
+    IO() {
+        return new Promise((resolve, reject) => {
+            resolve(this._io);
+        });
+    }
 }
+
+module.exports = new IOModule();

+ 0 - 177
backend/logic/logger.js

@@ -1,177 +0,0 @@
-'use strict';
-
-const coreClass = require("../core");
-
-const config = require('config');
-const fs = require('fs');
-
-const twoDigits = (num) => {
-	return (num < 10) ? '0' + num : num;
-};
-
-const getTime = () => {
-	let time = new Date();
-	return {
-		year: time.getFullYear(),
-		month: time.getMonth() + 1,
-		day: time.getDate(),
-		hour: time.getHours(),
-		minute: time.getMinutes(),
-		second: time.getSeconds()
-	}
-};
-
-const getTimeFormatted = () => {
-	let time = getTime();
-	return `${time.year}-${twoDigits(time.month)}-${twoDigits(time.day)} ${twoDigits(time.hour)}:${twoDigits(time.minute)}:${twoDigits(time.second)}`;
-}
-
-module.exports = class extends coreClass {
-	constructor(name, moduleManager) {
-		super(name, moduleManager);
-		this.lockdownImmune = true;
-	}
-
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
-
-			this.configDirectory = `${__dirname}/../../log`;
-
-			if (!config.isDocker && !fs.existsSync(`${this.configDirectory}`))
-				fs.mkdirSync(this.configDirectory);
-
-			let time = getTimeFormatted();
-
-			this.logCbs = [];
-
-			this.colors = {
-				Reset: "\x1b[0m",
-				Bright: "\x1b[1m",
-				Dim: "\x1b[2m",
-				Underscore: "\x1b[4m",
-				Blink: "\x1b[5m",
-				Reverse: "\x1b[7m",
-				Hidden: "\x1b[8m",
-
-				FgBlack: "\x1b[30m",
-				FgRed: "\x1b[31m",
-				FgGreen: "\x1b[32m",
-				FgYellow: "\x1b[33m",
-				FgBlue: "\x1b[34m",
-				FgMagenta: "\x1b[35m",
-				FgCyan: "\x1b[36m",
-				FgWhite: "\x1b[37m",
-
-				BgBlack: "\x1b[40m",
-				BgRed: "\x1b[41m",
-				BgGreen: "\x1b[42m",
-				BgYellow: "\x1b[43m",
-				BgBlue: "\x1b[44m",
-				BgMagenta: "\x1b[45m",
-				BgCyan: "\x1b[46m",
-				BgWhite: "\x1b[47m"
-			};
-
-			fs.appendFile(this.configDirectory + '/all.log', `${time} BACKEND_RESTARTED\n`, ()=>{});
-			fs.appendFile(this.configDirectory + '/success.log', `${time} BACKEND_RESTARTED\n`, ()=>{});
-			fs.appendFile(this.configDirectory + '/error.log', `${time} BACKEND_RESTARTED\n`, ()=>{});
-			fs.appendFile(this.configDirectory + '/info.log', `${time} BACKEND_RESTARTED\n`, ()=>{});
-			fs.appendFile(this.configDirectory + '/debugStation.log', `${time} BACKEND_RESTARTED\n`, ()=>{});
-
-			if (this.moduleManager.fancyConsole) {
-				process.stdout.write(Array(this.reservedLines).fill(`\n`).join(""));
-			}
-
-			resolve();
-		});
-	}
-
-	async success(type, text, display = true) {
-		try { await this._validateHook(); } catch { return; }
-
-		const time = getTimeFormatted();
-		const message = `${time} SUCCESS - ${type} - ${text}`;
-
-		this.writeFile('all.log', message);
-		this.writeFile('success.log', message);
-
-		if (display) this.log(this.colors.FgGreen, message);
-	}
-
-	async error(type, text, display = true) {
-		try { await this._validateHook(); } catch { return; }
-
-		const time = getTimeFormatted();
-		const message = `${time} ERROR - ${type} - ${text}`;
-
-		this.writeFile('all.log', message);
-		this.writeFile('error.log', message);
-
-		if (display) this.log(this.colors.FgRed, message);
-	}
-
-	async info(type, text, display = true) {
-		try { await this._validateHook(); } catch { return; }
-
-		const time = getTimeFormatted();
-		const message = `${time} INFO - ${type} - ${text}`;
-
-		this.writeFile('all.log', message);
-		this.writeFile('info.log', message);
-		if (display) this.log(this.colors.FgCyan, message);
-	}
-
-	async debug(text, display = true) {
-		try { await this._validateHook(); } catch { return; }
-
-		const time = getTimeFormatted();
-		const message = `${time} DEBUG - ${text}`;
-
-		if (display) this.log(this.colors.FgMagenta, message);
-	}
-
-	async stationIssue(text, display = false) {
-		try { await this._validateHook(); } catch { return; }
-
-		const time = getTimeFormatted();
-		const message = `${time} DEBUG_STATION - ${text}`;
-
-		this.writeFile('debugStation.log', message);
-
-		if (display) this.log(this.colors.FgMagenta, message);
-	}
-
-	log(color, message) {
-		if (this.moduleManager.fancyConsole) {
-			const rows = process.stdout.rows;
-			const columns = process.stdout.columns;
-			const lineNumber = rows - this.reservedLines;
-
-			
-			let lines = 0;
-			
-			message.split("\n").forEach((line) => {
-				lines += Math.floor(line.replace("\t", "    ").length / columns) + 1;
-			});
-
-			if (lines > this.logger.reservedLines)
-				lines = this.logger.reservedLines;
-
-			process.stdout.cursorTo(0, rows - this.logger.reservedLines);
-			process.stdout.clearScreenDown();
-
-			process.stdout.cursorTo(0, lineNumber);
-			process.stdout.write(`${color}${message}${this.colors.Reset}\n`);
-
-			process.stdout.cursorTo(0, process.stdout.rows);
-			process.stdout.write(Array(lines).fill(`\n!`).join(""));
-
-			this.moduleManager.printStatus();
-		} else console.log(`${color}${message}${this.colors.Reset}`);
-	}
-
-	writeFile(fileName, message) {
-		fs.appendFile(`${this.configDirectory}/${fileName}`, `${message}\n`, ()=>{});
-	}
-}

+ 45 - 35
backend/logic/mail/index.js

@@ -1,40 +1,50 @@
-'use strict';
+const CoreClass = require("../../core.js");
 
-const coreClass = require("../../core");
-
-const config = require('config');
+const config = require("config");
 
 let mailgun = null;
 
-module.exports = class extends coreClass {
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
-
-			this.schemas = {
-				verifyEmail: require('./schemas/verifyEmail'),
-				resetPasswordRequest: require('./schemas/resetPasswordRequest'),
-				passwordRequest: require('./schemas/passwordRequest')
-			};
-
-			this.enabled = config.get('apis.mailgun.enabled');
-
-			if (this.enabled)
-				mailgun = require('mailgun-js')({
-					apiKey: config.get("apis.mailgun.key"),
-					domain: config.get("apis.mailgun.domain")
-				});
-			
-			resolve();
-		});
-	}
-
-	async sendMail(data, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		if (!cb) cb = ()=>{};
-
-		if (this.enabled) mailgun.messages().send(data, cb);
-		else cb();
-	}
+class MailModule extends CoreClass {
+    constructor() {
+        super("mail");
+    }
+
+    initialize() {
+        return new Promise((resolve, reject) => {
+            this.schemas = {
+                verifyEmail: require("./schemas/verifyEmail"),
+                resetPasswordRequest: require("./schemas/resetPasswordRequest"),
+                passwordRequest: require("./schemas/passwordRequest"),
+            };
+
+            this.enabled = config.get("apis.mailgun.enabled");
+
+            if (this.enabled)
+                mailgun = require("mailgun-js")({
+                    apiKey: config.get("apis.mailgun.key"),
+                    domain: config.get("apis.mailgun.domain"),
+                });
+
+            resolve();
+        });
+    }
+
+    SEND_MAIL(payload) {
+        //data, cb
+        return new Promise((resolve, reject) => {
+            if (this.enabled)
+                mailgun.messages().send(payload.data, () => {
+                    resolve();
+                });
+            else resolve();
+        });
+    }
+
+    GET_SCHEMA(payload) {
+        return new Promise((resolve, reject) => {
+            resolve(this.schemas[payload.schemaName]);
+        });
+    }
 }
+
+module.exports = new MailModule();

+ 17 - 12
backend/logic/mail/schemas/passwordRequest.js

@@ -1,8 +1,8 @@
-const config = require('config');
+const config = require("config");
 
-const moduleManager = require('../../../index');
+// const moduleManager = require('../../../index');
 
-const mail = moduleManager.modules["mail"];
+const mail = require("../index");
 
 /**
  * Sends a request password email
@@ -13,12 +13,11 @@ const mail = moduleManager.modules["mail"];
  * @param {Function} cb - gets called when an error occurred or when the operation was successful
  */
 module.exports = function(to, username, code, cb) {
-	let data = {
-		from: 'Musare <noreply@musare.com>',
-		to: to,
-		subject: 'Password request',
-		html:
-			`
+    let data = {
+        from: "Musare <noreply@musare.com>",
+        to: to,
+        subject: "Password request",
+        html: `
 				Hello there ${username},
 				<br>
 				<br>
@@ -27,7 +26,13 @@ module.exports = function(to, username, code, cb) {
 				<br>
 				The code is <b>${code}</b>. You can enter this code on the page you requested the password on. This code will expire in 24 hours.
 			`
-	};
+    };
 
-	mail.sendMail(data, cb);
-};
+    mail.runJob("SEND_MAIL", { data })
+        .then(() => {
+            cb();
+        })
+        .catch(err => {
+            cb(err);
+        });
+};

+ 17 - 12
backend/logic/mail/schemas/resetPasswordRequest.js

@@ -1,8 +1,8 @@
-const config = require('config');
+const config = require("config");
 
-const moduleManager = require('../../../index');
+// const moduleManager = require('../../../index');
 
-const mail = moduleManager.modules["mail"];
+const mail = require("../index");
 
 /**
  * Sends a request password reset email
@@ -13,12 +13,11 @@ const mail = moduleManager.modules["mail"];
  * @param {Function} cb - gets called when an error occurred or when the operation was successful
  */
 module.exports = function(to, username, code, cb) {
-	let data = {
-		from: 'Musare <noreply@musare.com>',
-		to: to,
-		subject: 'Password reset request',
-		html:
-			`
+    let data = {
+        from: "Musare <noreply@musare.com>",
+        to: to,
+        subject: "Password reset request",
+        html: `
 				Hello there ${username},
 				<br>
 				<br>
@@ -27,7 +26,13 @@ module.exports = function(to, username, code, cb) {
 				<br>
 				The reset code is <b>${code}</b>. You can enter this code on the page you requested the password reset. This code will expire in 24 hours.
 			`
-	};
+    };
 
-	mail.sendMail(data, cb);
-};
+    mail.runJob("SEND_MAIL", { data })
+        .then(() => {
+            cb();
+        })
+        .catch(err => {
+            cb(err);
+        });
+};

+ 22 - 13
backend/logic/mail/schemas/verifyEmail.js

@@ -1,8 +1,8 @@
-const config = require('config');
+const config = require("config");
 
-const moduleManager = require('../../../index');
+// const moduleManager = require('../../../index');
 
-const mail = moduleManager.modules["mail"];
+const mail = require("../index");
 
 /**
  * Sends a verify email email
@@ -13,18 +13,27 @@ const mail = moduleManager.modules["mail"];
  * @param {Function} cb - gets called when an error occurred or when the operation was successful
  */
 module.exports = function(to, username, code, cb) {
-	let data = {
-		from: 'Musare <noreply@musare.com>',
-		to: to,
-		subject: 'Please verify your email',
-		html:
-			`
+    let data = {
+        from: "Musare <noreply@musare.com>",
+        to: to,
+        subject: "Please verify your email",
+        html: `
 				Hello there ${username},
 				<br>
 				<br>
-				To verify your email, please visit <a href="${config.get('serverDomain')}/auth/verify_email?code=${code}">${config.get('serverDomain')}/auth/verify_email?code=${code}</a>.
+				To verify your email, please visit <a href="${config.get(
+                    "serverDomain"
+                )}/auth/verify_email?code=${code}">${config.get(
+            "serverDomain"
+        )}/auth/verify_email?code=${code}</a>.
 			`
-	};
+    };
 
-	mail.sendMail(data, cb);
-};
+    mail.runJob("SEND_MAIL", { data })
+        .then(() => {
+            cb();
+        })
+        .catch(err => {
+            cb(err);
+        });
+};

+ 238 - 154
backend/logic/notifications.js

@@ -1,159 +1,243 @@
-'use strict';
+const CoreClass = require("../core.js");
 
-const coreClass = require("../core");
-
-const crypto = require('crypto');
-const redis = require('redis');
-const config = require('config');
+const crypto = require("crypto");
+const redis = require("redis");
+const config = require("config");
 
 const subscriptions = [];
 
-module.exports = class extends coreClass {
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
-
-			const url = this.url = config.get("redis").url;
-			const password = this.password = config.get("redis").password;
-
-			this.pub = redis.createClient({
-				url,
-				password,
-				retry_strategy: (options) => {
-					if (this.state === "LOCKDOWN") return;
-					if (this.state !== "RECONNECTING") this.setState("RECONNECTING");
-
-					this.logger.info("NOTIFICATIONS_MODULE", `Attempting to reconnect pub.`);
-
-					if (options.attempt >= 10) {
-						this.logger.error("NOTIFICATIONS_MODULE", `Stopped trying to reconnect pub.`);
-
-						this.failed = true;
-						this._lockdown();
-
-						return undefined;
-					}
-
-					return 3000;
-				}
-			});
-			this.sub = redis.createClient({
-				url,
-				password,
-				retry_strategy: (options) => {
-					if (this.state === "LOCKDOWN") return;
-					if (this.state !== "RECONNECTING") this.setState("RECONNECTING");
-
-					this.logger.info("NOTIFICATIONS_MODULE", `Attempting to reconnect sub.`);
-
-					if (options.attempt >= 10) {
-						this.logger.error("NOTIFICATIONS_MODULE", `Stopped trying to reconnect sub.`);
-
-						this.failed = true;
-						this._lockdown();
-
-						return undefined;
-					}
-
-					return 3000;
-				}
-			});
-
-			this.sub.on('error', (err) => {
-				if (this.state === "INITIALIZING") reject(err);
-				if(this.state === "LOCKDOWN") return;
-
-				this.logger.error("NOTIFICATIONS_MODULE", `Sub error ${err.message}.`);
-			});
-
-			this.pub.on('error', (err) => {
-				if (this.state === "INITIALIZING") reject(err);
-				if(this.state === "LOCKDOWN") return; 
-
-				this.logger.error("NOTIFICATIONS_MODULE", `Pub error ${err.message}.`);
-			});
-
-			this.sub.on("connect", () => {
-				this.logger.info("NOTIFICATIONS_MODULE", "Sub connected succesfully.");
-
-				if (this.state === "INITIALIZING") resolve();
-				else if (this.state === "LOCKDOWN" || this.state === "RECONNECTING") this.setState("INITIALIZED");
-				
-			});
-
-			this.pub.on("connect", () => {
-				this.logger.info("NOTIFICATIONS_MODULE", "Pub connected succesfully.");
-
-				if (this.state === "INITIALIZING") resolve();
-				else if (this.state === "LOCKDOWN" || this.state === "RECONNECTING") this.setState("INITIALIZED");
-			});
-
-			this.sub.on('pmessage', (pattern, channel, expiredKey) => {
-				this.logger.stationIssue(`PMESSAGE1 - Pattern: ${pattern}; Channel: ${channel}; ExpiredKey: ${expiredKey}`);
-				subscriptions.forEach((sub) => {
-					this.logger.stationIssue(`PMESSAGE2 - Sub name: ${sub.name}; Calls cb: ${!(sub.name !== expiredKey)}`);
-					if (sub.name !== expiredKey) return;
-					sub.cb();
-				});
-			});
-
-			this.sub.psubscribe('__keyevent@0__:expired');
-		});
-	}
-
-	/**
-	 * Schedules a notification to be dispatched in a specific amount of milliseconds,
-	 * notifications are unique by name, and the first one is always kept, as in
-	 * attempting to schedule a notification that already exists won't do anything
-	 *
-	 * @param {String} name - the name of the notification we want to schedule
-	 * @param {Integer} time - how long in milliseconds until the notification should be fired
-	 * @param {Function} cb - gets called when the notification has been scheduled
-	 */
-	async schedule(name, time, cb, station) {
-		try { await this._validateHook(); } catch { return; }
-
-		if (!cb) cb = ()=>{};
-
-		time = Math.round(time);
-		this.logger.stationIssue(`SCHEDULE - Time: ${time}; Name: ${name}; Key: ${crypto.createHash('md5').update(`_notification:${name}_`).digest('hex')}; StationId: ${station._id}; StationName: ${station.name}`);
-		this.pub.set(crypto.createHash('md5').update(`_notification:${name}_`).digest('hex'), '', 'PX', time, 'NX', cb);
-	}
-
-	/**
-	 * Subscribes a callback function to be called when a notification gets called
-	 *
-	 * @param {String} name - the name of the notification we want to subscribe to
-	 * @param {Function} cb - gets called when the subscribed notification gets called
-	 * @param {Boolean} unique - only subscribe if another subscription with the same name doesn't already exist
-	 * @return {Object} - the subscription object
-	 */
-	async subscribe(name, cb, unique = false, station) {
-		try { await this._validateHook(); } catch { return; }
-
-		this.logger.stationIssue(`SUBSCRIBE - Name: ${name}; Key: ${crypto.createHash('md5').update(`_notification:${name}_`).digest('hex')}, StationId: ${station._id}; StationName: ${station.name}; Unique: ${unique}; SubscriptionExists: ${!!subscriptions.find((subscription) => subscription.originalName == name)};`);
-		if (unique && !!subscriptions.find((subscription) => subscription.originalName == name)) return;
-		let subscription = { originalName: name, name: crypto.createHash('md5').update(`_notification:${name}_`).digest('hex'), cb };
-		subscriptions.push(subscription);
-		return subscription;
-	}
-
-	/**
-	 * Remove a notification subscription
-	 *
-	 * @param {Object} subscription - the subscription object returned by {@link subscribe}
-	 */
-	async remove(subscription) {
-		try { await this._validateHook(); } catch { return; }
-
-		let index = subscriptions.indexOf(subscription);
-		if (index) subscriptions.splice(index, 1);
-	}
-
-	async unschedule(name) {
-		try { await this._validateHook(); } catch { return; }
-
-		this.logger.stationIssue(`UNSCHEDULE - Name: ${name}; Key: ${crypto.createHash('md5').update(`_notification:${name}_`).digest('hex')}`);
-		this.pub.del(crypto.createHash('md5').update(`_notification:${name}_`).digest('hex'));
-	}
+class NotificationsModule extends CoreClass {
+    constructor() {
+        super("notifications");
+    }
+
+    initialize() {
+        return new Promise((resolve, reject) => {
+            const url = (this.url = config.get("redis").url);
+            const password = (this.password = config.get("redis").password);
+
+            this.pub = redis.createClient({
+                url,
+                password,
+                retry_strategy: (options) => {
+                    if (this.getStatus() === "LOCKDOWN") return;
+                    if (this.getStatus() !== "RECONNECTING")
+                        this.setStatus("RECONNECTING");
+
+                    this.log("INFO", `Attempting to reconnect.`);
+
+                    if (options.attempt >= 10) {
+                        this.log("ERROR", `Stopped trying to reconnect.`);
+
+                        this.setStatus("FAILED");
+
+                        // this.failed = true;
+                        // this._lockdown();
+
+                        return undefined;
+                    }
+
+                    return 3000;
+                },
+            });
+            this.sub = redis.createClient({
+                url,
+                password,
+                retry_strategy: (options) => {
+                    if (this.getStatus() === "LOCKDOWN") return;
+                    if (this.getStatus() !== "RECONNECTING")
+                        this.setStatus("RECONNECTING");
+
+                    this.log("INFO", `Attempting to reconnect.`);
+
+                    if (options.attempt >= 10) {
+                        this.log("ERROR", `Stopped trying to reconnect.`);
+
+                        this.setStatus("FAILED");
+
+                        // this.failed = true;
+                        // this._lockdown();
+
+                        return undefined;
+                    }
+
+                    return 3000;
+                },
+            });
+
+            this.sub.on("error", (err) => {
+                if (this.getStatus() === "INITIALIZING") reject(err);
+                if (this.getStatus() === "LOCKDOWN") return;
+
+                this.log("ERROR", `Error ${err.message}.`);
+            });
+
+            this.pub.on("error", (err) => {
+                if (this.getStatus() === "INITIALIZING") reject(err);
+                if (this.getStatus() === "LOCKDOWN") return;
+
+                this.log("ERROR", `Error ${err.message}.`);
+            });
+
+            this.sub.on("connect", () => {
+                this.log("INFO", "Sub connected succesfully.");
+
+                if (this.getStatus() === "INITIALIZING") resolve();
+                else if (
+                    this.getStatus() === "LOCKDOWN" ||
+                    this.getStatus() === "RECONNECTING"
+                )
+                    this.setStatus("READY");
+            });
+
+            this.pub.on("connect", () => {
+                this.log("INFO", "Pub connected succesfully.");
+
+                if (this.getStatus() === "INITIALIZING") resolve();
+                else if (
+                    this.getStatus() === "LOCKDOWN" ||
+                    this.getStatus() === "RECONNECTING"
+                )
+                    this.setStatus("INITIALIZED");
+            });
+
+            this.sub.on("pmessage", (pattern, channel, expiredKey) => {
+                this.log(
+                    "STATION_ISSUE",
+                    `PMESSAGE1 - Pattern: ${pattern}; Channel: ${channel}; ExpiredKey: ${expiredKey}`
+                );
+                subscriptions.forEach((sub) => {
+                    this.log(
+                        "STATION_ISSUE",
+                        `PMESSAGE2 - Sub name: ${sub.name}; Calls cb: ${!(
+                            sub.name !== expiredKey
+                        )}`
+                    );
+                    if (sub.name !== expiredKey) return;
+                    sub.cb();
+                });
+            });
+
+            this.sub.psubscribe("__keyevent@0__:expired");
+        });
+    }
+
+    /**
+     * Schedules a notification to be dispatched in a specific amount of milliseconds,
+     * notifications are unique by name, and the first one is always kept, as in
+     * attempting to schedule a notification that already exists won't do anything
+     *
+     * @param {String} name - the name of the notification we want to schedule
+     * @param {Integer} time - how long in milliseconds until the notification should be fired
+     * @param {Function} cb - gets called when the notification has been scheduled
+     */
+    SCHEDULE(payload) {
+        //name, time, cb, station
+        return new Promise((resolve, reject) => {
+            const time = Math.round(payload.time);
+            this.log(
+                "STATION_ISSUE",
+                `SCHEDULE - Time: ${time}; Name: ${payload.name}; Key: ${crypto
+                    .createHash("md5")
+                    .update(`_notification:${payload.name}_`)
+                    .digest("hex")}; StationId: ${
+                    payload.station._id
+                }; StationName: ${payload.station.name}`
+            );
+            this.pub.set(
+                crypto
+                    .createHash("md5")
+                    .update(`_notification:${payload.name}_`)
+                    .digest("hex"),
+                "",
+                "PX",
+                time,
+                "NX",
+                () => {
+                    resolve();
+                }
+            );
+        });
+    }
+
+    /**
+     * Subscribes a callback function to be called when a notification gets called
+     *
+     * @param {String} name - the name of the notification we want to subscribe to
+     * @param {Function} cb - gets called when the subscribed notification gets called
+     * @param {Boolean} unique - only subscribe if another subscription with the same name doesn't already exist
+     * @return {Object} - the subscription object
+     */
+    SUBSCRIBE(payload) {
+        //name, cb, unique = false, station
+        return new Promise((resolve, reject) => {
+            this.log(
+                "STATION_ISSUE",
+                `SUBSCRIBE - Name: ${payload.name}; Key: ${crypto
+                    .createHash("md5")
+                    .update(`_notification:${payload.name}_`)
+                    .digest("hex")}, StationId: ${
+                    payload.station._id
+                }; StationName: ${payload.station.name}; Unique: ${
+                    payload.unique
+                }; SubscriptionExists: ${!!subscriptions.find(
+                    (subscription) => subscription.originalName === payload.name
+                )};`
+            );
+            if (
+                payload.unique &&
+                !!subscriptions.find(
+                    (subscription) => subscription.originalName === payload.name
+                )
+            )
+                return;
+            let subscription = {
+                originalName: payload.name,
+                name: crypto
+                    .createHash("md5")
+                    .update(`_notification:${payload.name}_`)
+                    .digest("hex"),
+                cb: payload.cb,
+            };
+            subscriptions.push(subscription);
+            resolve({ subscription });
+        });
+    }
+
+    /**
+     * Remove a notification subscription
+     *
+     * @param {Object} subscription - the subscription object returned by {@link subscribe}
+     */
+    REMOVE(payload) {
+        //subscription
+        return new Promise((resolve, reject) => {
+            let index = subscriptions.indexOf(payload.subscription);
+            if (index) subscriptions.splice(index, 1);
+            resolve();
+        });
+    }
+
+    UNSCHEDULE(payload) {
+        //name
+        return new Promise((resolve, reject) => {
+            this.log(
+                "STATION_ISSUE",
+                `UNSCHEDULE - Name: ${payload.name}; Key: ${crypto
+                    .createHash("md5")
+                    .update(`_notification:${payload.name}_`)
+                    .digest("hex")}`
+            );
+            this.pub.del(
+                crypto
+                    .createHash("md5")
+                    .update(`_notification:${payload.name}_`)
+                    .digest("hex")
+            );
+
+            resolve();
+        });
+    }
 }
+
+module.exports = new NotificationsModule();

+ 280 - 166
backend/logic/playlists.js

@@ -1,167 +1,281 @@
-'use strict';
-
-const coreClass = require("../core");
-
-const async = require('async');
-
-module.exports = class extends coreClass {
-	constructor(name, moduleManager) {
-		super(name, moduleManager);
-
-		this.dependsOn = ["cache", "db", "utils"];
-	}
-
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
-
-			this.cache = this.moduleManager.modules["cache"];
-			this.db	= this.moduleManager.modules["db"];
-			this.utils	= this.moduleManager.modules["utils"];
-
-			async.waterfall([
-				(next) => {
-					this.setStage(2);
-					this.cache.hgetall('playlists', next);
-				},
-	
-				(playlists, next) => {
-					this.setStage(3);
-					if (!playlists) return next();
-					let playlistIds = Object.keys(playlists);
-					async.each(playlistIds, (playlistId, next) => {
-						this.db.models.playlist.findOne({_id: playlistId}, (err, playlist) => {
-							if (err) next(err);
-							else if (!playlist) {
-								this.cache.hdel('playlists', playlistId, next);
-							}
-							else next();
-						});
-					}, next);
-				},
-	
-				(next) => {
-					this.setStage(4);
-					this.db.models.playlist.find({}, next);
-				},
-	
-				(playlists, next) => {
-					this.setStage(5);
-					async.each(playlists, (playlist, next) => {
-						this.cache.hset('playlists', playlist._id, this.cache.schemas.playlist(playlist), next);
-					}, next);
-				}
-			], async (err) => {
-				if (err) {
-					err = await this.utils.getError(err);
-					reject(err);
-				} else {
-					resolve();
-				}
-			});
-		});
-	}
-
-	/**
-	 * Gets a playlist by id from the cache or Mongo, and if it isn't in the cache yet, adds it the cache
-	 *
-	 * @param {String} playlistId - the id of the playlist we are trying to get
-	 * @param {Function} cb - gets called once we're done initializing
-	 */
-	async getPlaylist(playlistId, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-			(next) => {
-				this.cache.hgetall('playlists', next);
-			},
-
-			(playlists, next) => {
-				if (!playlists) return next();
-				let playlistIds = Object.keys(playlists);
-				async.each(playlistIds, (playlistId, next) => {
-					this.db.models.playlist.findOne({_id: playlistId}, (err, playlist) => {
-						if (err) next(err);
-						else if (!playlist) {
-							this.cache.hdel('playlists', playlistId, next);
-						}
-						else next();
-					});
-				}, next);
-			},
-
-			(next) => {
-				this.cache.hget('playlists', playlistId, next);
-			},
-
-			(playlist, next) => {
-				if (playlist) return next(true, playlist);
-				this.db.models.playlist.findOne({ _id: playlistId }, next);
-			},
-
-			(playlist, next) => {
-				if (playlist) {
-					this.cache.hset('playlists', playlistId, playlist, next);
-				} else next('Playlist not found');
-			},
-
-		], (err, playlist) => {
-			if (err && err !== true) return cb(err);
-			else cb(null, playlist);
-		});
-	}
-
-	/**
-	 * Gets a playlist from id from Mongo and updates the cache with it
-	 *
-	 * @param {String} playlistId - the id of the playlist we are trying to update
-	 * @param {Function} cb - gets called when an error occurred or when the operation was successful
-	 */
-	async updatePlaylist(playlistId, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-			(next) => {
-				this.db.models.playlist.findOne({ _id: playlistId }, next);
-			},
-
-			(playlist, next) => {
-				if (!playlist) {
-					this.cache.hdel('playlists', playlistId);
-					return next('Playlist not found');
-				}
-				this.cache.hset('playlists', playlistId, playlist, next);
-			}
-
-		], (err, playlist) => {
-			if (err && err !== true) return cb(err);
-			cb(null, playlist);
-		});
-	}
-
-	/**
-	 * Deletes playlist from id from Mongo and cache
-	 *
-	 * @param {String} playlistId - the id of the playlist we are trying to delete
-	 * @param {Function} cb - gets called when an error occurred or when the operation was successful
-	 */
-	async deletePlaylist(playlistId, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-
-			(next) => {
-				this.db.models.playlist.deleteOne({ _id: playlistId }, next);
-			},
-
-			(res, next) => {
-				this.cache.hdel('playlists', playlistId, next);
-			}
-
-		], (err) => {
-			if (err && err !== true) return cb(err);
-
-			cb(null);
-		});
-	}
+const CoreClass = require("../core.js");
+
+const async = require("async");
+
+class ExampleModule extends CoreClass {
+    constructor() {
+        super("playlists");
+    }
+
+    initialize() {
+        return new Promise(async (resolve, reject) => {
+            this.setStage(1);
+
+            this.cache = this.moduleManager.modules["cache"];
+            this.db = this.moduleManager.modules["db"];
+            this.utils = this.moduleManager.modules["utils"];
+
+            const playlistModel = await this.db.runJob("GET_MODEL", {
+                modelName: "playlist",
+            });
+
+            const playlistSchema = await this.cache.runJob("GET_SCHEMA", {
+                schemaName: "playlist",
+            });
+
+            this.setStage(2);
+
+            async.waterfall(
+                [
+                    (next) => {
+                        this.setStage(3);
+                        this.cache
+                            .runJob("HGETALL", { table: "playlists" })
+                            .then((playlists) => next(null, playlists))
+                            .catch(next);
+                    },
+
+                    (playlists, next) => {
+                        this.setStage(4);
+                        if (!playlists) return next();
+                        let playlistIds = Object.keys(playlists);
+                        async.each(
+                            playlistIds,
+                            (playlistId, next) => {
+                                playlistModel.findOne(
+                                    { _id: playlistId },
+                                    (err, playlist) => {
+                                        if (err) next(err);
+                                        else if (!playlist) {
+                                            this.cache
+                                                .runJob("HDEL", {
+                                                    table: "playlists",
+                                                    key: playlistId,
+                                                })
+                                                .then(() => next())
+                                                .catch(next);
+                                        } else next();
+                                    }
+                                );
+                            },
+                            next
+                        );
+                    },
+
+                    (next) => {
+                        this.setStage(5);
+                        playlistModel.find({}, next);
+                    },
+
+                    (playlists, next) => {
+                        this.setStage(6);
+                        async.each(
+                            playlists,
+                            (playlist, next) => {
+                                this.cache
+                                    .runJob("HSET", {
+                                        table: "playlists",
+                                        key: playlist._id,
+                                        value: playlistSchema(playlist),
+                                    })
+                                    .then(() => {
+                                        next();
+                                    })
+                                    .catch(next);
+                            },
+                            next
+                        );
+                    },
+                ],
+                async (err) => {
+                    if (err) {
+                        err = await this.utils.runJob("GET_ERROR", {
+                            error: err,
+                        });
+                        reject(new Error(err));
+                    } else {
+                        resolve();
+                    }
+                }
+            );
+        });
+    }
+
+    /**
+     * Gets a playlist by id from the cache or Mongo, and if it isn't in the cache yet, adds it the cache
+     *
+     * @param {String} playlistId - the id of the playlist we are trying to get
+     * @param {Function} cb - gets called once we're done initializing
+     */
+    GET_PLAYLIST(payload) {
+        //playlistId, cb
+        return new Promise(async (resolve, reject) => {
+            const playlistModel = await this.db.runJob("GET_MODEL", {
+                modelName: "playlist",
+            });
+
+            async.waterfall(
+                [
+                    (next) => {
+                        this.cache
+                            .runJob("HGETALL", { table: "playlists" })
+                            .then((playlists) => next(null, playlists))
+                            .catch(next);
+                    },
+
+                    (playlists, next) => {
+                        if (!playlists) return next();
+                        let playlistIds = Object.keys(playlists);
+                        async.each(
+                            playlistIds,
+                            (playlistId, next) => {
+                                playlistModel.findOne(
+                                    { _id: playlistId },
+                                    (err, playlist) => {
+                                        if (err) next(err);
+                                        else if (!playlist) {
+                                            this.cache
+                                                .runJob("HDEL", {
+                                                    table: "playlists",
+                                                    key: playlistId,
+                                                })
+                                                .then(() => next())
+                                                .catch(next);
+                                        } else next();
+                                    }
+                                );
+                            },
+                            next
+                        );
+                    },
+
+                    (next) => {
+                        this.cache
+                            .runJob("HGET", {
+                                table: "playlists",
+                                key: payload.playlistId,
+                            })
+                            .then((playlist) => next(null, playlist))
+                            .catch(next);
+                    },
+
+                    (playlist, next) => {
+                        if (playlist) return next(true, playlist);
+                        playlistModel.findOne(
+                            { _id: payload.playlistId },
+                            next
+                        );
+                    },
+
+                    (playlist, next) => {
+                        if (playlist) {
+                            this.cache
+                                .runJob("HSET", {
+                                    table: "playlists",
+                                    key: payload.playlistId,
+                                    value: playlist,
+                                })
+                                .then((playlist) => next(null, playlist))
+                                .catch(next);
+                        } else next("Playlist not found");
+                    },
+                ],
+                (err, playlist) => {
+                    if (err && err !== true) return reject(new Error(err));
+                    resolve(playlist);
+                }
+            );
+        });
+    }
+
+    /**
+     * Gets a playlist from id from Mongo and updates the cache with it
+     *
+     * @param {String} playlistId - the id of the playlist we are trying to update
+     * @param {Function} cb - gets called when an error occurred or when the operation was successful
+     */
+    UPDATE_PLAYLIST(payload) {
+        //playlistId, cb
+        return new Promise(async (resolve, reject) => {
+            const playlistModel = await this.db.runJob("GET_MODEL", {
+                modelName: "playlist",
+            });
+
+            async.waterfall(
+                [
+                    (next) => {
+                        playlistModel.findOne(
+                            { _id: payload.playlistId },
+                            next
+                        );
+                    },
+
+                    (playlist, next) => {
+                        if (!playlist) {
+                            this.cache.runJob("HDEL", {
+                                table: "playlists",
+                                key: payload.playlistId,
+                            });
+                            return next("Playlist not found");
+                        }
+                        this.cache
+                            .runJob("HSET", {
+                                table: "playlists",
+                                key: payload.playlistId,
+                                value: playlist,
+                            })
+                            .then((playlist) => next(null, playlist))
+                            .catch(next);
+                    },
+                ],
+                (err, playlist) => {
+                    if (err && err !== true) return reject(new Error(err));
+                    resolve(playlist);
+                }
+            );
+        });
+    }
+
+    /**
+     * Deletes playlist from id from Mongo and cache
+     *
+     * @param {String} playlistId - the id of the playlist we are trying to delete
+     * @param {Function} cb - gets called when an error occurred or when the operation was successful
+     */
+    DELETE_PLAYLIST(payload) {
+        //playlistId, cb
+        return new Promise(async (resolve, reject) => {
+            const playlistModel = await this.db.runJob("GET_MODEL", {
+                modelName: "playlist",
+            });
+
+            async.waterfall(
+                [
+                    (next) => {
+                        playlistModel.deleteOne(
+                            { _id: payload.playlistId },
+                            next
+                        );
+                    },
+
+                    (res, next) => {
+                        this.cache
+                            .runJob("HDEL", {
+                                table: "playlists",
+                                key: payload.playlistId,
+                            })
+                            .then(() => next())
+                            .catch(next);
+                    },
+                ],
+                (err) => {
+                    if (err && err !== true) return reject(new Error(err));
+
+                    resolve();
+                }
+            );
+        });
+    }
 }
+
+module.exports = new ExampleModule();

+ 313 - 241
backend/logic/punishments.js

@@ -1,243 +1,315 @@
-'use strict';
-
-const coreClass = require("../core");
-
-const async = require('async');
-const mongoose = require('mongoose');
-
-module.exports = class extends coreClass {
-	constructor(name, moduleManager) {
-		super(name, moduleManager);
-
-		this.dependsOn = ["cache", "db", "utils"];
-	}
-
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
-
-			this.cache = this.moduleManager.modules['cache'];
-			this.db = this.moduleManager.modules['db'];
-			this.io = this.moduleManager.modules['io'];
-			this.utils = this.moduleManager.modules['utils'];
-
-			async.waterfall([
-				(next) => {
-					this.setStage(2);
-					this.cache.hgetall('punishments', next);
-				},
-	
-				(punishments, next) => {
-					this.setStage(3);
-					if (!punishments) return next();
-					let punishmentIds = Object.keys(punishments);
-					async.each(punishmentIds, (punishmentId, next) => {
-						this.db.models.punishment.findOne({_id: punishmentId}, (err, punishment) => {
-							if (err) next(err);
-							else if (!punishment) this.cache.hdel('punishments', punishmentId, next);
-							else next();
-						});
-					}, next);
-				},
-	
-				(next) => {
-					this.setStage(4);
-					this.db.models.punishment.find({}, next);
-				},
-	
-				(punishments, next) => {
-					this.setStage(5);
-					async.each(punishments, (punishment, next) => {
-						if (punishment.active === false || punishment.expiresAt < Date.now()) return next();
-						this.cache.hset('punishments', punishment._id, this.cache.schemas.punishment(punishment, punishment._id), next);
-					}, next);
-				}
-			], async (err) => {
-				if (err) {
-					err = await utils.getError(err);
-					reject(err);
-				} else {
-					resolve();
-				}
-			});
-		});
-	}
-
-	/**
-	 * Gets all punishments in the cache that are active, and removes those that have expired
-	 *
-	 * @param {Function} cb - gets called once we're done initializing
-	 */
-	async getPunishments(cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		let punishmentsToRemove = [];
-		async.waterfall([
-			(next) => {
-				this.cache.hgetall('punishments', next);
-			},
-
-			(punishmentsObj, next) => {
-				let punishments = [];
-				for (let id in punishmentsObj) {
-					let obj = punishmentsObj[id];
-					obj.punishmentId = id;
-					punishments.push(obj);
-				}
-				punishments = punishments.filter(punishment => {
-					if (punishment.expiresAt < Date.now()) punishmentsToRemove.push(punishment);
-					return punishment.expiresAt > Date.now();
-				});
-				next(null, punishments);
-			},
-
-			(punishments, next) => {
-				async.each(
-					punishmentsToRemove,
-					(punishment, next2) => {
-						this.cache.hdel('punishments', punishment.punishmentId, () => {
-							next2();
-						});
-					},
-					() => {
-						next(null, punishments);
-					}
-				);
-			}
-		], (err, punishments) => {
-			if (err && err !== true) return cb(err);
-
-			cb(null, punishments);
-		});
-	}
-
-	/**
-	 * Gets a punishment by id
-	 *
-	 * @param {String} id - the id of the punishment we are trying to get
-	 * @param {Function} cb - gets called once we're done initializing
-	 */
-	async getPunishment(id, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-
-			(next) => {
-				if (!mongoose.Types.ObjectId.isValid(id)) return next('Id is not a valid ObjectId.');
-				this.cache.hget('punishments', id, next);
-			},
-
-			(punishment, next) => {
-				if (punishment) return next(true, punishment);
-				this.db.models.punishment.findOne({_id: id}, next);
-			},
-
-			(punishment, next) => {
-				if (punishment) {
-					this.cache.hset('punishments', id, punishment, next);
-				} else next('Punishment not found.');
-			},
-
-		], (err, punishment) => {
-			if (err && err !== true) return cb(err);
-
-			cb(null, punishment);
-		});
-	}
-
-	/**
-	 * Gets all punishments from a userId
-	 *
-	 * @param {String} userId - the userId of the punishment(s) we are trying to get
-	 * @param {Function} cb - gets called once we're done initializing
-	 */
-	async getPunishmentsFromUserId(userId, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-			(next) => {
-				this.getPunishments(next);
-			},
-			(punishments, next) => {
-				punishments = punishments.filter((punishment) => {
-					return punishment.type === 'banUserId' && punishment.value === userId;
-				});
-				next(null, punishments);
-			}
-		], (err, punishments) => {
-			if (err && err !== true) return cb(err);
-
-			cb(null, punishments);
-		});
-	}
-
-	async addPunishment(type, value, reason, expiresAt, punishedBy, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-			(next) => {
-				const punishment = new this.db.models.punishment({
-					type,
-					value,
-					reason,
-					active: true,
-					expiresAt,
-					punishedAt: Date.now(),
-					punishedBy
-				});
-				punishment.save((err, punishment) => {
-					if (err) return next(err);
-					next(null, punishment);
-				});
-			},
-
-			(punishment, next) => {
-				this.cache.hset('punishments', punishment._id, this.cache.schemas.punishment(punishment, punishment._id), (err) => {
-					next(err, punishment);
-				});
-			},
-
-			(punishment, next) => {
-				// DISCORD MESSAGE
-				next(null, punishment);
-			}
-		], (err, punishment) => {
-			cb(err, punishment);
-		});
-	}
-
-	async removePunishmentFromCache(punishmentId, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-			(next) => {
-				const punishment = new this.db.models.punishment({
-					type,
-					value,
-					reason,
-					active: true,
-					expiresAt,
-					punishedAt: Date.now(),
-					punishedBy
-				});
-				punishment.save((err, punishment) => {
-					console.log(err);
-					if (err) return next(err);
-					next(null, punishment);
-				});
-			},
-
-			(punishment, next) => {
-				this.cache.hset('punishments', punishment._id, punishment, next);
-			},
-
-			(punishment, next) => {
-				// DISCORD MESSAGE
-				next();
-			}
-		], (err) => {
-			cb(err);
-		});
-	}
+const CoreClass = require("../core.js");
+
+const async = require("async");
+const mongoose = require("mongoose");
+
+class PunishmentsModule extends CoreClass {
+    constructor() {
+        super("punishments");
+    }
+
+    initialize() {
+        return new Promise(async (resolve, reject) => {
+            this.setStage(1);
+
+            this.cache = this.moduleManager.modules["cache"];
+            this.db = this.moduleManager.modules["db"];
+            this.io = this.moduleManager.modules["io"];
+            this.utils = this.moduleManager.modules["utils"];
+
+            const punishmentModel = await this.db.runJob("GET_MODEL", {
+                modelName: "punishment",
+            });
+
+            const punishmentSchema = await this.cache.runJob("GET_SCHEMA", {
+                schemaName: "punishment",
+            });
+
+            async.waterfall(
+                [
+                    (next) => {
+                        this.setStage(2);
+                        this.cache
+                            .runJob("HGETALL", { table: "punishments" })
+                            .then((punishments) => next(null, punishments))
+                            .catch(next);
+                    },
+
+                    (punishments, next) => {
+                        this.setStage(3);
+                        if (!punishments) return next();
+                        let punishmentIds = Object.keys(punishments);
+                        async.each(
+                            punishmentIds,
+                            (punishmentId, next) => {
+                                punishmentModel.findOne(
+                                    { _id: punishmentId },
+                                    (err, punishment) => {
+                                        if (err) next(err);
+                                        else if (!punishment)
+                                            this.cache
+                                                .runJob("HDEL", {
+                                                    table: "punishments",
+                                                    key: punishmentId,
+                                                })
+                                                .then(() => next())
+                                                .catch(next);
+                                        else next();
+                                    }
+                                );
+                            },
+                            next
+                        );
+                    },
+
+                    (next) => {
+                        this.setStage(4);
+                        punishmentModel.find({}, next);
+                    },
+
+                    (punishments, next) => {
+                        this.setStage(5);
+                        async.each(
+                            punishments,
+                            (punishment, next) => {
+                                if (
+                                    punishment.active === false ||
+                                    punishment.expiresAt < Date.now()
+                                )
+                                    return next();
+                                this.cache
+                                    .runJob("HSET", {
+                                        table: "punishments",
+                                        key: punishment._id,
+                                        value: punishmentSchema(
+                                            punishment,
+                                            punishment._id
+                                        ),
+                                    })
+                                    .then(() => next())
+                                    .catch(next);
+                            },
+                            next
+                        );
+                    },
+                ],
+                async (err) => {
+                    if (err) {
+                        err = await utils.runJob("GET_ERROR", { error: err });
+                        reject(new Error(err));
+                    } else {
+                        resolve();
+                    }
+                }
+            );
+        });
+    }
+
+    /**
+     * Gets all punishments in the cache that are active, and removes those that have expired
+     *
+     * @param {Function} cb - gets called once we're done initializing
+     */
+    GET_PUNISHMENTS() {
+        //cb
+        return new Promise((resolve, reject) => {
+            let punishmentsToRemove = [];
+            async.waterfall(
+                [
+                    (next) => {
+                        this.cache
+                            .runJob("HGETALL", { table: "punishments" })
+                            .then((punishmentsObj) =>
+                                next(null, punishmentsObj)
+                            )
+                            .catch(next);
+                    },
+
+                    (punishmentsObj, next) => {
+                        let punishments = [];
+                        for (let id in punishmentsObj) {
+                            let obj = punishmentsObj[id];
+                            obj.punishmentId = id;
+                            punishments.push(obj);
+                        }
+                        punishments = punishments.filter((punishment) => {
+                            if (punishment.expiresAt < Date.now())
+                                punishmentsToRemove.push(punishment);
+                            return punishment.expiresAt > Date.now();
+                        });
+                        next(null, punishments);
+                    },
+
+                    (punishments, next) => {
+                        async.each(
+                            punishmentsToRemove,
+                            (punishment, next2) => {
+                                this.cache
+                                    .runJob("HDEL", {
+                                        table: "punishments",
+                                        key: punishment.punishmentId,
+                                    })
+                                    .finally(() => next2());
+                            },
+                            () => {
+                                next(null, punishments);
+                            }
+                        );
+                    },
+                ],
+                (err, punishments) => {
+                    if (err && err !== true) return reject(new Error(err));
+
+                    resolve(punishments);
+                }
+            );
+        });
+    }
+
+    /**
+     * Gets a punishment by id
+     *
+     * @param {String} id - the id of the punishment we are trying to get
+     * @param {Function} cb - gets called once we're done initializing
+     */
+    GET_PUNISHMENT() {
+        //id, cb
+        return new Promise(async (resolve, reject) => {
+            const punishmentModel = await db.runJob("GET_MODEL", {
+                modelName: "punishment",
+            });
+
+            async.waterfall(
+                [
+                    (next) => {
+                        if (!mongoose.Types.ObjectId.isValid(payload.id))
+                            return next("Id is not a valid ObjectId.");
+                        this.cache
+                            .runJob("HGET", {
+                                table: "punishments",
+                                key: payload.id,
+                            })
+                            .then((punishment) => next(null, punishment))
+                            .catch(next);
+                    },
+
+                    (punishment, next) => {
+                        if (punishment) return next(true, punishment);
+                        punishmentModel.findOne({ _id: payload.id }, next);
+                    },
+
+                    (punishment, next) => {
+                        if (punishment) {
+                            this.cache
+                                .runJob("HSET", {
+                                    table: "punishments",
+                                    key: payload.id,
+                                    value: punishment,
+                                })
+                                .then((punishment) => next(null, punishment))
+                                .catch(next);
+                        } else next("Punishment not found.");
+                    },
+                ],
+                (err, punishment) => {
+                    if (err && err !== true) return reject(new Error(err));
+
+                    resolve(punishment);
+                }
+            );
+        });
+    }
+
+    /**
+     * Gets all punishments from a userId
+     *
+     * @param {String} userId - the userId of the punishment(s) we are trying to get
+     * @param {Function} cb - gets called once we're done initializing
+     */
+    GET_PUNISHMENTS_FROM_USER_ID(payload) {
+        //userId, cb
+        return new Promise((resolve, reject) => {
+            async.waterfall(
+                [
+                    (next) => {
+                        this.runJob("GET_PUNISHMENTS", {})
+                            .then((punishments) => next(null, punishments))
+                            .catch(next);
+                    },
+                    (punishments, next) => {
+                        punishments = punishments.filter((punishment) => {
+                            return (
+                                punishment.type === "banUserId" &&
+                                punishment.value === payload.userId
+                            );
+                        });
+                        next(null, punishments);
+                    },
+                ],
+                (err, punishments) => {
+                    if (err && err !== true) return reject(new Error(err));
+
+                    resolve(punishments);
+                }
+            );
+        });
+    }
+
+    ADD_PUNISHMENT(payload) {
+        //type, value, reason, expiresAt, punishedBy, cb
+        return new Promise(async (resolve, reject) => {
+            const punishmentModel = await db.runJob("GET_MODEL", {
+                modelName: "punishment",
+            });
+
+            const punishmentSchema = await cache.runJob("GET_SCHEMA", {
+                schemaName: "punishment",
+            });
+
+            async.waterfall(
+                [
+                    (next) => {
+                        const punishment = new punishmentModel({
+                            type: payload.type,
+                            value: payload.value,
+                            reason: payload.reason,
+                            active: true,
+                            expiresAt: payload.expiresAt,
+                            punishedAt: Date.now(),
+                            punishedBy: payload.punishedBy,
+                        });
+                        punishment.save((err, punishment) => {
+                            if (err) return next(err);
+                            next(null, punishment);
+                        });
+                    },
+
+                    (punishment, next) => {
+                        this.cache
+                            .runJob("HSET", {
+                                table: "punishments",
+                                key: punishment._id,
+                                value: punishmentSchema(
+                                    punishment,
+                                    punishment._id
+                                ),
+                            })
+                            .then(() => next())
+                            .catch(next);
+                    },
+
+                    (punishment, next) => {
+                        // DISCORD MESSAGE
+                        next(null, punishment);
+                    },
+                ],
+                (err, punishment) => {
+                    if (err) return reject(new Error(err));
+                    resolve(punishment);
+                }
+            );
+        });
+    }
 }
 
+module.exports = new PunishmentsModule();

+ 257 - 175
backend/logic/songs.js

@@ -1,176 +1,258 @@
-'use strict';
-
-const coreClass = require("../core");
-
-const async = require('async');
-const mongoose = require('mongoose');
-
-
-
-
-module.exports = class extends coreClass {
-	constructor(name, moduleManager) {
-		super(name, moduleManager);
-
-		this.dependsOn = ["utils", "cache", "db"];
-	}
-
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
-
-			this.cache = this.moduleManager.modules["cache"];
-			this.db = this.moduleManager.modules["db"];
-			this.io = this.moduleManager.modules["io"];
-			this.utils = this.moduleManager.modules["utils"];
-
-			async.waterfall([
-				(next) => {
-					this.setStage(2);
-					this.cache.hgetall('songs', next);
-				},
-	
-				(songs, next) => {
-					this.setStage(3);
-					if (!songs) return next();
-					let songIds = Object.keys(songs);
-					async.each(songIds, (songId, next) => {
-						this.db.models.song.findOne({songId}, (err, song) => {
-							if (err) next(err);
-							else if (!song) this.cache.hdel('songs', songId, next);
-							else next();
-						});
-					}, next);
-				},
-	
-				(next) => {
-					this.setStage(4);
-					this.db.models.song.find({}, next);
-				},
-	
-				(songs, next) => {
-					this.setStage(5);
-					async.each(songs, (song, next) => {
-						this.cache.hset('songs', song.songId, this.cache.schemas.song(song), next);
-					}, next);
-				}
-			], async (err) => {
-				if (err) {
-					err = await this.utils.getError(err);
-					reject(err);
-				} else {
-					resolve();
-				}
-			});
-		});
-	}
-
-	/**
-	 * Gets a song by id from the cache or Mongo, and if it isn't in the cache yet, adds it the cache
-	 *
-	 * @param {String} id - the id of the song we are trying to get
-	 * @param {Function} cb - gets called once we're done initializing
-	 */
-	async getSong(id, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-			(next) => {
-				if (!mongoose.Types.ObjectId.isValid(id)) return next('Id is not a valid ObjectId.');
-				this.cache.hget('songs', id, next);
-			},
-
-			(song, next) => {
-				if (song) return next(true, song);
-				this.db.models.song.findOne({_id: id}, next);
-			},
-
-			(song, next) => {
-				if (song) {
-					this.cache.hset('songs', id, song, next);
-				} else next('Song not found.');
-			},
-
-		], (err, song) => {
-			if (err && err !== true) return cb(err);
-
-			cb(null, song);
-		});
-	}
-
-	/**
-	 * Gets a song by song id from the cache or Mongo, and if it isn't in the cache yet, adds it the cache
-	 *
-	 * @param {String} songId - the mongo id of the song we are trying to get
-	 * @param {Function} cb - gets called once we're done initializing
-	 */
-	async getSongFromId(songId, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-			(next) => {
-				this.db.models.song.findOne({ songId }, next);
-			}
-		], (err, song) => {
-			if (err && err !== true) return cb(err);
-			else return cb(null, song);
-		});
-	}
-
-	/**
-	 * Gets a song from id from Mongo and updates the cache with it
-	 *
-	 * @param {String} songId - the id of the song we are trying to update
-	 * @param {Function} cb - gets called when an error occurred or when the operation was successful
-	 */
-	async updateSong(songId, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-
-			(next) => {
-				this.db.models.song.findOne({_id: songId}, next);
-			},
-
-			(song, next) => {
-				if (!song) {
-					this.cache.hdel('songs', songId);
-					return next('Song not found.');
-				}
-
-				this.cache.hset('songs', songId, song, next);
-			}
-
-		], (err, song) => {
-			if (err && err !== true) return cb(err);
-
-			cb(null, song);
-		});
-	}
-
-	/**
-	 * Deletes song from id from Mongo and cache
-	 *
-	 * @param {String} songId - the id of the song we are trying to delete
-	 * @param {Function} cb - gets called when an error occurred or when the operation was successful
-	 */
-	async deleteSong(songId, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-
-			(next) => {
-				this.db.models.song.deleteOne({ songId }, next);
-			},
-
-			(next) => {
-				this.cache.hdel('songs', songId, next);
-			}
-
-		], (err) => {
-			if (err && err !== true) cb(err);
-
-			cb(null);
-		});
-	}
+const CoreClass = require("../core.js");
+
+const async = require("async");
+const mongoose = require("mongoose");
+
+class SongsModule extends CoreClass {
+    constructor() {
+        super("songs");
+    }
+
+    initialize() {
+        return new Promise(async (resolve, reject) => {
+            this.setStage(1);
+
+            this.cache = this.moduleManager.modules["cache"];
+            this.db = this.moduleManager.modules["db"];
+            this.io = this.moduleManager.modules["io"];
+            this.utils = this.moduleManager.modules["utils"];
+
+            const songModel = await this.db.runJob("GET_MODEL", {
+                modelName: "song",
+            });
+
+            const songSchema = await this.cache.runJob("GET_SCHEMA", {
+                schemaName: "song",
+            });
+
+            async.waterfall(
+                [
+                    (next) => {
+                        this.setStage(2);
+                        this.cache
+                            .runJob("HGETALL", { table: "songs" })
+                            .then((songs) => next(null, songs))
+                            .catch(next);
+                    },
+
+                    (songs, next) => {
+                        this.setStage(3);
+                        if (!songs) return next();
+                        let songIds = Object.keys(songs);
+                        async.each(
+                            songIds,
+                            (songId, next) => {
+                                songModel.findOne({ songId }, (err, song) => {
+                                    if (err) next(err);
+                                    else if (!song)
+                                        this.cache
+                                            .runJob("HDEL", {
+                                                table: "songs",
+                                                key: songId,
+                                            })
+                                            .then(() => next())
+                                            .catch(next);
+                                    else next();
+                                });
+                            },
+                            next
+                        );
+                    },
+
+                    (next) => {
+                        this.setStage(4);
+                        songModel.find({}, next);
+                    },
+
+                    (songs, next) => {
+                        this.setStage(5);
+                        async.each(
+                            songs,
+                            (song, next) => {
+                                this.cache
+                                    .runJob("HSET", {
+                                        table: "songs",
+                                        key: song.songId,
+                                        value: songSchema.song(song),
+                                    })
+                                    .then(() => next())
+                                    .catch(next);
+                            },
+                            next
+                        );
+                    },
+                ],
+                async (err) => {
+                    if (err) {
+                        err = await this.utils.runJob("GET_ERROR", {
+                            error: err,
+                        });
+                        reject(new Error(err));
+                    } else {
+                        resolve();
+                    }
+                }
+            );
+        });
+    }
+
+    /**
+     * Gets a song by id from the cache or Mongo, and if it isn't in the cache yet, adds it the cache
+     *
+     * @param {String} id - the id of the song we are trying to get
+     * @param {Function} cb - gets called once we're done initializing
+     */
+    GET_SONG(payload) {
+        //id, cb
+        return new Promise(async (resolve, reject) => {
+            const songModel = await this.db.runJob("GET_MODEL", {
+                modelName: "song",
+            });
+
+            async.waterfall(
+                [
+                    (next) => {
+                        if (!mongoose.Types.ObjectId.isValid(payload.id))
+                            return next("Id is not a valid ObjectId.");
+                        this.runJob("HGET", { table: "songs", key: payload.id })
+                            .then((song) => next(null, song))
+                            .catch(next);
+                    },
+
+                    (song, next) => {
+                        if (song) return next(true, song);
+                        songModel.findOne({ _id: payload.id }, next);
+                    },
+
+                    (song, next) => {
+                        if (song) {
+                            this.cache
+                                .runJob("HSET", {
+                                    table: "songs",
+                                    key: payload.id,
+                                    value: song,
+                                })
+                                .then((song) => next(null, song));
+                        } else next("Song not found.");
+                    },
+                ],
+                (err, song) => {
+                    if (err && err !== true) return reject(new Error(err));
+
+                    resolve({ song });
+                }
+            );
+        });
+    }
+
+    /**
+     * Gets a song by song id from the cache or Mongo, and if it isn't in the cache yet, adds it the cache
+     *
+     * @param {String} songId - the mongo id of the song we are trying to get
+     * @param {Function} cb - gets called once we're done initializing
+     */
+    GET_SONG_FROM_ID(payload) {
+        //songId, cb
+        return new Promise(async (resolve, reject) => {
+            const songModel = await this.db.runJob("GET_MODEL", {
+                modelName: "song",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        songModel.findOne({ songId: payload.songId }, next);
+                    },
+                ],
+                (err, song) => {
+                    if (err && err !== true) return reject(new Error(err));
+                    resolve({ song });
+                }
+            );
+        });
+    }
+
+    /**
+     * Gets a song from id from Mongo and updates the cache with it
+     *
+     * @param {String} songId - the id of the song we are trying to update
+     * @param {Function} cb - gets called when an error occurred or when the operation was successful
+     */
+    UPDATE_SONG(payload) {
+        //songId, cb
+        return new Promise(async (resolve, reject) => {
+            const songModel = await this.db.runJob("GET_MODEL", {
+                modelName: "song",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        songModel.findOne({ _id: payload.songId }, next);
+                    },
+
+                    (song, next) => {
+                        if (!song) {
+                            this.cache.runJob("HDEL", {
+                                table: "songs",
+                                key: payload.songId,
+                            });
+                            return next("Song not found.");
+                        }
+
+                        this.cache
+                            .runJob("HSET", {
+                                table: "songs",
+                                key: payload.songId,
+                                value: song,
+                            })
+                            .then((song) => next(null, song))
+                            .catch(next);
+                    },
+                ],
+                (err, song) => {
+                    if (err && err !== true) return reject(new Error(err));
+
+                    resolve(song);
+                }
+            );
+        });
+    }
+
+    /**
+     * Deletes song from id from Mongo and cache
+     *
+     * @param {String} songId - the id of the song we are trying to delete
+     * @param {Function} cb - gets called when an error occurred or when the operation was successful
+     */
+    DELETE_SONG(payload) {
+        //songId, cb
+        return new Promise(async (resolve, reject) => {
+            const songModel = await this.db.runJob("GET_MODEL", {
+                modelName: "song",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        songModel.deleteOne({ songId: payload.songId }, next);
+                    },
+
+                    (next) => {
+                        this.cache
+                            .runJob("HDEL", {
+                                table: "songs",
+                                key: payload.songId,
+                            })
+                            .then(() => next())
+                            .catch(next);
+                    },
+                ],
+                (err) => {
+                    if (err && err !== true) return reject(new Error(err));
+
+                    resolve();
+                }
+            );
+        });
+    }
 }
+
+module.exports = new SongsModule();

+ 103 - 82
backend/logic/spotify.js

@@ -1,95 +1,116 @@
-const coreClass = require("../core");
+const CoreClass = require("../core.js");
 
-const config = require('config'),
-	async  = require('async');
+const config = require("config"),
+    async = require("async");
 
 let apiResults = {
-	access_token: "",
-	token_type: "",
-	expires_in: 0,
-	expires_at: 0,
-	scope: "",
+    access_token: "",
+    token_type: "",
+    expires_in: 0,
+    expires_at: 0,
+    scope: "",
 };
 
-module.exports = class extends coreClass {
-	constructor(name, moduleManager) {
-		super(name, moduleManager);
+class SpotifyModule extends CoreClass {
+    constructor() {
+        super("spotify");
+    }
 
-		this.dependsOn = ["cache"];
-	}
+    initialize() {
+        return new Promise((resolve, reject) => {
+            this.cache = this.moduleManager.modules["cache"];
+            this.utils = this.moduleManager.modules["utils"];
 
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
+            const client = config.get("apis.spotify.client");
+            const secret = config.get("apis.spotify.secret");
 
-			this.cache = this.moduleManager.modules["cache"];
-			this.utils = this.moduleManager.modules["utils"];
+            const OAuth2 = require("oauth").OAuth2;
+            this.SpotifyOauth = new OAuth2(
+                client,
+                secret,
+                "https://accounts.spotify.com/",
+                null,
+                "api/token",
+                null
+            );
 
-			const client = config.get("apis.spotify.client");
-			const secret = config.get("apis.spotify.secret");
+            async.waterfall(
+                [
+                    (next) => {
+                        this.setStage(2);
+                        this.cache
+                            .runJob("HGET", { table: "api", key: "spotify" })
+                            .then((data) => next(null, data))
+                            .catch(next);
+                    },
 
-			const OAuth2 = require('oauth').OAuth2;
-			this.SpotifyOauth = new OAuth2(
-				client,
-				secret, 
-				'https://accounts.spotify.com/', 
-				null,
-				'api/token',
-				null);
+                    (data, next) => {
+                        this.setStage(3);
+                        if (data) apiResults = data;
+                        next();
+                    },
+                ],
+                async (err) => {
+                    if (err) {
+                        err = await this.utils.runJob("GET_ERROR", {
+                            error: err,
+                        });
+                        reject(new Error(err));
+                    } else {
+                        resolve();
+                    }
+                }
+            );
+        });
+    }
 
-			async.waterfall([
-				(next) => {
-					this.setStage(2);
-					this.cache.hget("api", "spotify", next, true);
-				},
-	
-				(data, next) => {
-					this.setStage(3);
-					if (data) apiResults = data;
-					next();
-				}
-			], async (err) => {
-				if (err) {
-					err = await this.utils.getError(err);
-					reject(err);
-				} else {
-					resolve();
-				}
-			});
-		});
-	}
+    GET_TOKEN(payload) {
+        return new Promise((resolve, reject) => {
+            if (Date.now() > apiResults.expires_at) {
+                this.runJob("REQUEST_TOKEN").then(() => {
+                    resolve(apiResults.access_token);
+                });
+            } else resolve(apiResults.access_token);
+        });
+    }
 
-	async getToken() {
-		try { await this._validateHook(); } catch { return; }
-
-		return new Promise((resolve, reject) => {
-			if (Date.now() > apiResults.expires_at) {
-				this.requestToken(() => {
-					resolve(apiResults.access_token);
-				});
-			} else resolve(apiResults.access_token);
-		});
-	}
-
-	async requestToken(cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-			(next) => {
-				this.logger.info("SPOTIFY_REQUEST_TOKEN", "Requesting new Spotify token.");
-				this.SpotifyOauth.getOAuthAccessToken(
-					'',
-					{ 'grant_type': 'client_credentials' },
-					next
-				);
-			},
-			(access_token, refresh_token, results, next) => {
-				apiResults = results;
-				apiResults.expires_at = Date.now() + (results.expires_in * 1000);
-				this.cache.hset("api", "spotify", apiResults, next, true);
-			}
-		], () => {
-			cb();
-		});
-	}
+    REQUEST_TOKEN(payload) {
+        //cb
+        return new Promise((resolve, reject) => {
+            async.waterfall(
+                [
+                    (next) => {
+                        this.log(
+                            "INFO",
+                            "SPOTIFY_REQUEST_TOKEN",
+                            "Requesting new Spotify token."
+                        );
+                        this.SpotifyOauth.getOAuthAccessToken(
+                            "",
+                            { grant_type: "client_credentials" },
+                            next
+                        );
+                    },
+                    (access_token, refresh_token, results, next) => {
+                        apiResults = results;
+                        apiResults.expires_at =
+                            Date.now() + results.expires_in * 1000;
+                        this.cache
+                            .runJob("HSET", {
+                                table: "api",
+                                key: "spotify",
+                                value: apiResults,
+                                stringifyJson: true,
+                            })
+                            .finally(() => next());
+                    },
+                ],
+                () => {
+                    resolve();
+                }
+            );
+        });
+    }
 }
+
+module.exports = new SpotifyModule();

+ 1149 - 533
backend/logic/stations.js

@@ -1,537 +1,1153 @@
-'use strict';
+const CoreClass = require("../core.js");
 
-const coreClass = require("../core");
-
-const async = require('async');
+const async = require("async");
 
 let subscription = null;
 
-module.exports = class extends coreClass {
-	constructor(name, moduleManager) {
-		super(name, moduleManager);
-
-		this.dependsOn = ["cache", "db", "utils"];
-	}
-
-	initialize() {
-		return new Promise(async (resolve, reject) => {
-			this.setStage(1);
-
-			this.cache = this.moduleManager.modules["cache"];
-			this.db = this.moduleManager.modules["db"];
-			this.utils = this.moduleManager.modules["utils"];
-			this.songs = this.moduleManager.modules["songs"];
-			this.notifications = this.moduleManager.modules["notifications"];
-
-			this.defaultSong = {
-				songId: '60ItHLz5WEA',
-				title: 'Faded - Alan Walker',
-				duration: 212,
-				skipDuration: 0,
-				likes: -1,
-				dislikes: -1
-			};
-
-			//TEMP
-			this.cache.sub('station.pause', async (stationId) => {
-				try { await this._validateHook(); } catch { return; }
-
-				this.notifications.remove(`stations.nextSong?id=${stationId}`);
-			});
-
-			this.cache.sub('station.resume', async (stationId) => {
-				try { await this._validateHook(); } catch { return; }
-
-				this.initializeStation(stationId)
-			});
-
-			this.cache.sub('station.queueUpdate', async (stationId) => {
-				try { await this._validateHook(); } catch { return; }
-
-				this.getStation(stationId, (err, station) => {
-					if (!station.currentSong && station.queue.length > 0) {
-						this.initializeStation(stationId);
-					}
-				});
-			});
-
-			this.cache.sub('station.newOfficialPlaylist', async (stationId) => {
-				try { await this._validateHook(); } catch { return; }
-
-				this.cache.hget("officialPlaylists", stationId, (err, playlistObj) => {
-					if (!err && playlistObj) {
-						this.utils.emitToRoom(`station.${stationId}`, "event:newOfficialPlaylist", playlistObj.songs);
-					}
-				})
-			});
-
-
-			async.waterfall([
-				(next) => {
-					this.setStage(2);
-					this.cache.hgetall('stations', next);
-				},
-	
-				(stations, next) => {
-					this.setStage(3);
-					if (!stations) return next();
-					let stationIds = Object.keys(stations);
-					async.each(stationIds, (stationId, next) => {
-						this.db.models.station.findOne({_id: stationId}, (err, station) => {
-							if (err) next(err);
-							else if (!station) {
-								this.cache.hdel('stations', stationId, next);
-							} else next();
-						});
-					}, next);
-				},
-	
-				(next) => {
-					this.setStage(4);
-					this.db.models.station.find({}, next);
-				},
-	
-				(stations, next) => {
-					this.setStage(5);
-					async.each(stations, (station, next2) => {
-						async.waterfall([
-							(next) => {
-								this.cache.hset('stations', station._id, this.cache.schemas.station(station), next);
-							},
-	
-							(station, next) => {
-								this.initializeStation(station._id, () => {
-									next()
-								}, true);
-							}
-						], (err) => {
-							next2(err);
-						});
-					}, next);
-				}
-			], async (err) => {
-				if (err) {
-					err = await this.utils.getError(err);
-					reject(err);
-				} else {
-					resolve();
-				}
-			});
-		});
-	}
-
-	async initializeStation(stationId, cb, bypassValidate = false) {
-		if (!bypassValidate) try { await this._validateHook(); } catch { return; }
-
-		if (typeof cb !== 'function') cb = ()=>{};
-
-		async.waterfall([
-			(next) => {
-				this.getStation(stationId, next, true);
-			},
-			(station, next) => {
-				if (!station) return next('Station not found.');
-				this.notifications.unschedule(`stations.nextSong?id=${station._id}`);
-				subscription = this.notifications.subscribe(`stations.nextSong?id=${station._id}`, this.skipStation(station._id), true, station);
-				if (station.paused) return next(true, station);
-				next(null, station);
-			},
-			(station, next) => {
-				if (!station.currentSong) {
-					return this.skipStation(station._id)((err, station) => {
-						if (err) return next(err);
-						return next(true, station);
-					}, true);
-				}
-				let timeLeft = ((station.currentSong.duration * 1000) - (Date.now() - station.startedAt - station.timePaused));
-				if (isNaN(timeLeft)) timeLeft = -1;
-				if (station.currentSong.duration * 1000 < timeLeft || timeLeft < 0) {
-					this.skipStation(station._id)((err, station) => {
-						next(err, station);
-					}, true);
-				} else {
-					this.notifications.schedule(`stations.nextSong?id=${station._id}`, timeLeft, null, station);
-					next(null, station);
-				}
-			}
-		], (err, station) => {
-			if (err && err !== true) return cb(err);
-			cb(null, station);
-		});
-	}
-
-	async calculateSongForStation(station, cb, bypassValidate = false) {
-		if (!bypassValidate) try { await this._validateHook(); } catch { return; }
-
-		let songList = [];
-		async.waterfall([
-			(next) => {
-				if (station.genres.length === 0) return next();
-				let genresDone = [];
-				station.genres.forEach((genre) => {
-					this.db.models.song.find({genres: genre}, (err, songs) => {
-						if (!err) {
-							songs.forEach((song) => {
-								if (songList.indexOf(song._id) === -1) {
-									let found = false;
-									song.genres.forEach((songGenre) => {
-										if (station.blacklistedGenres.indexOf(songGenre) !== -1) found = true;
-									});
-									if (!found) {
-										songList.push(song._id);
-									}
-								}
-							});
-						}
-						genresDone.push(genre);
-						if (genresDone.length === station.genres.length) next();
-					});
-				});
-			},
-
-			(next) => {
-				let playlist = [];
-				songList.forEach(function(songId) {
-					if(station.playlist.indexOf(songId) === -1) playlist.push(songId);
-				});
-				station.playlist.filter((songId) => {
-					if (songList.indexOf(songId) !== -1) playlist.push(songId);
-				});
-
-				this.utils.shuffle(playlist).then((playlist) => {
-					next(null, playlist);
-				});
-			},
-
-			(playlist, next) => {
-				this.calculateOfficialPlaylistList(station._id, playlist, () => {
-					next(null, playlist);
-				}, true);
-			},
-
-			(playlist, next) => {
-				this.db.models.station.updateOne({_id: station._id}, {$set: {playlist: playlist}}, {runValidators: true}, (err) => {
-					this.updateStation(station._id, () => {
-						next(err, playlist);
-					}, true);
-				});
-			}
-
-		], (err, newPlaylist) => {
-			cb(err, newPlaylist);
-		});
-	}
-
-	// Attempts to get the station from Redis. If it's not in Redis, get it from Mongo and add it to Redis.
-	async getStation(stationId, cb, bypassValidate = false) {
-		if (!bypassValidate) try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-			(next) => {
-				this.cache.hget('stations', stationId, next);
-			},
-
-			(station, next) => {
-				if (station) return next(true, station);
-				this.db.models.station.findOne({ _id: stationId }, next);
-			},
-
-			(station, next) => {
-				if (station) {
-					if (station.type === 'official') {
-						this.calculateOfficialPlaylistList(station._id, station.playlist, () => {});
-					}
-					station = this.cache.schemas.station(station);
-					this.cache.hset('stations', stationId, station);
-					next(true, station);
-				} else next('Station not found');
-			},
-
-		], (err, station) => {
-			if (err && err !== true) return cb(err);
-			cb(null, station);
-		});
-	}
-
-	// Attempts to get the station from Redis. If it's not in Redis, get it from Mongo and add it to Redis.
-	async getStationByName(stationName, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-
-			(next) => {
-				this.db.models.station.findOne({ name: stationName }, next);
-			},
-
-			(station, next) => {
-				if (station) {
-					if (station.type === 'official') {
-						this.calculateOfficialPlaylistList(station._id, station.playlist, ()=>{});
-					}
-					station = this.cache.schemas.station(station);
-					this.cache.hset('stations', station._id, station);
-					next(true, station);
-				} else next('Station not found');
-			},
-
-		], (err, station) => {
-			if (err && err !== true) return cb(err);
-			cb(null, station);
-		});
-	}
-
-	async updateStation(stationId, cb, bypassValidate = false) {
-		if (!bypassValidate) try { await this._validateHook(); } catch { return; }
-
-		async.waterfall([
-
-			(next) => {
-				this.db.models.station.findOne({ _id: stationId }, next);
-			},
-
-			(station, next) => {
-				if (!station) {
-					this.cache.hdel('stations', stationId);
-					return next('Station not found');
-				}
-				this.cache.hset('stations', stationId, station, next);
-			}
-
-		], (err, station) => {
-			if (err && err !== true) return cb(err);
-			cb(null, station);
-		});
-	}
-
-	async calculateOfficialPlaylistList(stationId, songList, cb, bypassValidate = false) {
-		if (!bypassValidate) try { await this._validateHook(); } catch { return; }
-
-		let lessInfoPlaylist = [];
-		async.each(songList, (song, next) => {
-			this.songs.getSong(song, (err, song) => {
-				if (!err && song) {
-					let newSong = {
-						songId: song.songId,
-						title: song.title,
-						artists: song.artists,
-						duration: song.duration
-					};
-					lessInfoPlaylist.push(newSong);
-				}
-				next();
-			});
-		}, () => {
-			this.cache.hset("officialPlaylists", stationId, this.cache.schemas.officialPlaylist(stationId, lessInfoPlaylist), () => {
-				this.cache.pub("station.newOfficialPlaylist", stationId);
-				cb();
-			});
-		});
-	}
-
-	skipStation(stationId) {
-		this.logger.info("STATION_SKIP", `Skipping station ${stationId}.`, false);
-		return async (cb, bypassValidate = false) => {
-			if (!bypassValidate) try { await this._validateHook(); } catch { return; }
-			this.logger.stationIssue(`SKIP_STATION_CB - Station ID: ${stationId}.`);
-
-			if (typeof cb !== 'function') cb = ()=>{};
-
-			async.waterfall([
-				(next) => {
-					this.getStation(stationId, next, true);
-				},
-				(station, next) => {
-					if (!station) return next('Station not found.');
-					if (station.type === 'community' && station.partyMode && station.queue.length === 0) return next(null, null, -11, station); // Community station with party mode enabled and no songs in the queue
-					if (station.type === 'community' && station.partyMode && station.queue.length > 0) { // Community station with party mode enabled and songs in the queue
-						return this.db.models.station.updateOne({_id: stationId}, {$pull: {queue: {_id: station.queue[0]._id}}}, (err) => {
-							if (err) return next(err);
-							next(null, station.queue[0], -12, station);
-						});
-					}
-					if (station.type === 'community' && !station.partyMode) {
-						return this.db.models.playlist.findOne({_id: station.privatePlaylist}, (err, playlist) => {
-							if (err) return next(err);
-							if (!playlist) return next(null, null, -13, station);
-							playlist = playlist.songs;
-							if (playlist.length > 0) {
-								let currentSongIndex;
-								if (station.currentSongIndex < playlist.length - 1) currentSongIndex = station.currentSongIndex + 1;
-								else currentSongIndex = 0;
-								let callback = (err, song) => {
-									if (err) return next(err);
-									if (song) return next(null, song, currentSongIndex, station);
-									else {
-										let song = playlist[currentSongIndex];
-										let currentSong = {
-											songId: song.songId,
-											title: song.title,
-											duration: song.duration,
-											likes: -1,
-											dislikes: -1
-										};
-										return next(null, currentSong, currentSongIndex, station);
-									}
-								};
-								if (playlist[currentSongIndex]._id) this.songs.getSong(playlist[currentSongIndex]._id, callback);
-								else this.songs.getSongFromId(playlist[currentSongIndex].songId, callback);
-							} else return next(null, null, -14, station);
-						});
-					}
-					if (station.type === 'official' && station.playlist.length === 0) {
-						return this.calculateSongForStation(station, (err, playlist) => {
-							if (err) return next(err);
-							if (playlist.length === 0) return next(null, this.defaultSong, 0, station);
-							else {
-								this.songs.getSong(playlist[0], (err, song) => {
-									if (err || !song) return next(null, this.defaultSong, 0, station);
-									return next(null, song, 0, station);
-								});
-							}
-						}, true);
-					}
-					if (station.type === 'official' && station.playlist.length > 0) {
-						async.doUntil((next) => {
-							if (station.currentSongIndex < station.playlist.length - 1) {
-								this.songs.getSong(station.playlist[station.currentSongIndex + 1], (err, song) => {
-									if (!err) return next(null, song, station.currentSongIndex + 1);
-									else {
-										station.currentSongIndex++;
-										next(null, null, null);
-									}
-								});
-							} else {
-								this.calculateSongForStation(station, (err, newPlaylist) => {
-									if (err) return next(null, this.defaultSong, 0);
-									this.songs.getSong(newPlaylist[0], (err, song) => {
-										if (err || !song) return next(null, this.defaultSong, 0);
-										station.playlist = newPlaylist;
-										next(null, song, 0);
-									});
-								}, true);
-							}
-						}, (song, currentSongIndex, next) => {
-							if (!!song) return next(null, true, currentSongIndex);
-							else return next(null, false);
-						}, (err, song, currentSongIndex) => {
-							return next(err, song, currentSongIndex, station);
-						});
-					}
-				},
-				(song, currentSongIndex, station, next) => {
-					let $set = {};
-					if (song === null) $set.currentSong = null;
-					else if (song.likes === -1 && song.dislikes === -1) {
-						$set.currentSong = {
-							songId: song.songId,
-							title: song.title,
-							duration: song.duration,
-							skipDuration: 0,
-							likes: -1,
-							dislikes: -1
-						};
-					} else {
-						$set.currentSong = {
-							songId: song.songId,
-							title: song.title,
-							artists: song.artists,
-							duration: song.duration,
-							likes: song.likes,
-							dislikes: song.dislikes,
-							skipDuration: song.skipDuration,
-							thumbnail: song.thumbnail
-						};
-					}
-					if (currentSongIndex >= 0) $set.currentSongIndex = currentSongIndex;
-					$set.startedAt = Date.now();
-					$set.timePaused = 0;
-					if (station.paused) $set.pausedAt = Date.now();
-					next(null, $set, station);
-				},
-
-				($set, station, next) => {
-					this.db.models.station.updateOne({_id: station._id}, {$set}, (err) => {
-						this.updateStation(station._id, (err, station) => {
-							if (station.type === 'community' && station.partyMode === true)
-								this.cache.pub('station.queueUpdate', stationId);
-							next(null, station);
-						}, true);
-					});
-				},
-			], async (err, station) => {
-				if (!err) {
-					if (station.currentSong !== null && station.currentSong.songId !== undefined) {
-						station.currentSong.skipVotes = 0;
-					}
-					//TODO Pub/Sub this
-					this.utils.emitToRoom(`station.${station._id}`, "event:songs.next", {
-						currentSong: station.currentSong,
-						startedAt: station.startedAt,
-						paused: station.paused,
-						timePaused: 0
-					});
-
-					if (station.privacy === 'public') this.utils.emitToRoom('home', "event:station.nextSong", station._id, station.currentSong);
-					else {
-						let sockets = await this.utils.getRoomSockets('home');
-						for (let socketId in sockets) {
-							let socket = sockets[socketId];
-							let session = sockets[socketId].session;
-							if (session.sessionId) {
-								this.cache.hget('sessions', session.sessionId, (err, session) => {
-									if (!err && session) {
-										this.db.models.user.findOne({_id: session.userId}, (err, user) => {
-											if (!err && user) {
-												if (user.role === 'admin') socket.emit("event:station.nextSong", station._id, station.currentSong);
-												else if (station.type === "community" && station.owner === session.userId) socket.emit("event:station.nextSong", station._id, station.currentSong);
-											}
-										});
-									}
-								});
-							}
-						}
-					}
-					if (station.currentSong !== null && station.currentSong.songId !== undefined) {
-						this.utils.socketsJoinSongRoom(await this.utils.getRoomSockets(`station.${station._id}`), `song.${station.currentSong.songId}`);
-						if (!station.paused) {
-							this.notifications.schedule(`stations.nextSong?id=${station._id}`, station.currentSong.duration * 1000, null, station);
-						}
-					} else {
-						this.utils.socketsLeaveSongRooms(await this.utils.getRoomSockets(`station.${station._id}`));
-					}
-					cb(null, station);
-				} else {
-					err = await this.utils.getError(err);
-					this.logger.error('SKIP_STATION', `Skipping station "${stationId}" failed. "${err}"`);
-					cb(err);
-				}
-			});
-		}
-	}
-
-	async canUserViewStation(station, userId, cb) {
-		try { await this._validateHook(); } catch { return; }
-		async.waterfall([
-			(next) => {
-				if (station.privacy !== 'private') return next(true);
-				if (!userId) return next("Not allowed");
-				next();
-			},
-			
-			(next) => {
-				this.db.models.user.findOne({_id: userId}, next);
-			},
-			
-			(user, next) => {
-				if (!user) return next("Not allowed");
-				if (user.role === 'admin') return next(true);
-				if (station.type === 'official') return next("Not allowed");
-				if (station.owner === userId) return next(true);
-				next("Not allowed");
-			}
-		], async (errOrResult) => {
-			if (errOrResult === true || errOrResult === "Not allowed") return cb(null, (errOrResult === true) ? true : false);
-			cb(await this.utils.getError(errOrResult));
-		});
-	}
-}
+class StationsModule extends CoreClass {
+    constructor() {
+        super("stations");
+    }
+
+    initialize() {
+        return new Promise(async (resolve, reject) => {
+            this.cache = this.moduleManager.modules["cache"];
+            this.db = this.moduleManager.modules["db"];
+            this.utils = this.moduleManager.modules["utils"];
+            this.songs = this.moduleManager.modules["songs"];
+            this.notifications = this.moduleManager.modules["notifications"];
+
+            this.defaultSong = {
+                songId: "60ItHLz5WEA",
+                title: "Faded - Alan Walker",
+                duration: 212,
+                skipDuration: 0,
+                likes: -1,
+                dislikes: -1,
+            };
+
+            //TEMP
+            this.cache.runJob("SUB", {
+                channel: "station.pause",
+                cb: async (stationId) => {
+                    this.notifications
+                        .runJob("REMOVE", {
+                            subscription: `stations.nextSong?id=${stationId}`,
+                        })
+                        .then();
+                },
+            });
+
+            this.cache.runJob("SUB", {
+                channel: "station.resume",
+                cb: async (stationId) => {
+                    this.runJob("INITIALIZE_STATION", { stationId }).then();
+                },
+            });
+
+            this.cache.runJob("SUB", {
+                channel: "station.queueUpdate",
+                cb: async (stationId) => {
+                    this.runJob("GET_STATION", { stationId }).then(
+                        (station) => {
+                            if (
+                                !station.currentSong &&
+                                station.queue.length > 0
+                            ) {
+                                this.runJob("INITIALIZE_STATION", {
+                                    stationId,
+                                }).then();
+                            }
+                        }
+                    );
+                },
+            });
+
+            this.cache.runJob("SUB", {
+                channel: "station.newOfficialPlaylist",
+                cb: async (stationId) => {
+                    this.cache
+                        .runJob("HGET", {
+                            table: "officialPlaylists",
+                            key: stationId,
+                        })
+                        .then((playlistObj) => {
+                            if (playlistObj) {
+                                this.utils.runJob("EMIT_TO_ROOM", {
+                                    room: `station.${stationId}`,
+                                    args: [
+                                        "event:newOfficialPlaylist",
+                                        playlistObj.songs,
+                                    ],
+                                });
+                            }
+                        });
+                },
+            });
+
+            const stationModel = (this.stationModel = await this.db.runJob(
+                "GET_MODEL",
+                {
+                    modelName: "station",
+                }
+            ));
+
+            const stationSchema = (this.stationSchema = await this.cache.runJob(
+                "GET_SCHEMA",
+                {
+                    schemaName: "station",
+                }
+            ));
+
+            async.waterfall(
+                [
+                    (next) => {
+                        this.setStage(2);
+                        this.cache
+                            .runJob("HGETALL", { table: "stations" })
+                            .then((stations) => next(null, stations))
+                            .catch(next);
+                    },
+
+                    (stations, next) => {
+                        this.setStage(3);
+                        if (!stations) return next();
+                        let stationIds = Object.keys(stations);
+                        async.each(
+                            stationIds,
+                            (stationId, next) => {
+                                stationModel.findOne(
+                                    { _id: stationId },
+                                    (err, station) => {
+                                        if (err) next(err);
+                                        else if (!station) {
+                                            this.cache
+                                                .runJob("HDEL", {
+                                                    table: "stations",
+                                                    key: stationId,
+                                                })
+                                                .then(() => next())
+                                                .catch(next);
+                                        } else next();
+                                    }
+                                );
+                            },
+                            next
+                        );
+                    },
+
+                    (next) => {
+                        this.setStage(4);
+                        stationModel.find({}, next);
+                    },
+
+                    (stations, next) => {
+                        this.setStage(5);
+                        async.each(
+                            stations,
+                            (station, next2) => {
+                                async.waterfall(
+                                    [
+                                        (next) => {
+                                            this.cache
+                                                .runJob("HSET", {
+                                                    table: "stations",
+                                                    key: station._id,
+                                                    value: stationSchema(
+                                                        station
+                                                    ),
+                                                })
+                                                .then((station) =>
+                                                    next(null, station)
+                                                )
+                                                .catch(next);
+                                        },
+
+                                        (station, next) => {
+                                            this.runJob(
+                                                "INITIALIZE_STATION",
+                                                {
+                                                    stationId: station._id,
+                                                    bypassQueue: true,
+                                                },
+                                                { bypassQueue: true }
+                                            )
+                                                .then(() => next())
+                                                .catch(next); // bypassQueue is true because otherwise the module will never initialize
+                                        },
+                                    ],
+                                    (err) => {
+                                        next2(err);
+                                    }
+                                );
+                            },
+                            next
+                        );
+                    },
+                ],
+                async (err) => {
+                    if (err) {
+                        err = await this.utils.runJob("GET_ERROR", {
+                            error: err,
+                        });
+                        reject(new Error(err));
+                    } else {
+                        resolve();
+                    }
+                }
+            );
+        });
+    }
+
+    INITIALIZE_STATION(payload) {
+        //stationId, cb, bypassValidate = false
+        return new Promise((resolve, reject) => {
+            // if (typeof cb !== 'function') cb = ()=>{};
+
+            async.waterfall(
+                [
+                    (next) => {
+                        this.runJob(
+                            "GET_STATION",
+                            { stationId: payload.stationId },
+                            { bypassQueue: payload.bypassQueue }
+                        )
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+                    (station, next) => {
+                        if (!station) return next("Station not found.");
+                        this.notifications
+                            .runJob("UNSCHEDULE", {
+                                subscription: `stations.nextSong?id=${station._id}`,
+                            })
+                            .then()
+                            .catch();
+                        this.notifications
+                            .runJob("SUBSCRIBE", {
+                                subscription: `stations.nextSong?id=${station._id}`,
+                                cb: () =>
+                                    this.runJob("SKIP_STATION", {
+                                        stationId: station._id,
+                                    }),
+                                unique: true,
+                                station,
+                            })
+                            .then()
+                            .catch();
+                        if (station.paused) return next(true, station);
+                        next(null, station);
+                    },
+                    (station, next) => {
+                        if (!station.currentSong) {
+                            return this.runJob(
+                                "SKIP_STATION",
+                                {
+                                    stationId: station._id,
+                                    bypassQueue: payload.bypassQueue,
+                                },
+                                { bypassQueue: payload.bypassQueue }
+                            )
+                                .then((station) => next(true, station))
+                                .catch(next)
+                                .finally(() => {});
+                        }
+                        let timeLeft =
+                            station.currentSong.duration * 1000 -
+                            (Date.now() -
+                                station.startedAt -
+                                station.timePaused);
+                        if (isNaN(timeLeft)) timeLeft = -1;
+                        if (
+                            station.currentSong.duration * 1000 < timeLeft ||
+                            timeLeft < 0
+                        ) {
+                            this.runJob(
+                                "SKIP_STATION",
+                                { stationId: station._id },
+                                { bypassQueue: payload.bypassQueue }
+                            )
+                                .then((station) => next(null, station))
+                                .catch(next);
+                        } else {
+                            //name, time, cb, station
+                            this.notifications.runJob("SCHEDULE", {
+                                name: `stations.nextSong?id=${station._id}`,
+                                time: timeLeft,
+                                station,
+                            });
+                            next(null, station);
+                        }
+                    },
+                ],
+                async (err, station) => {
+                    if (err && err !== true) {
+                        err = await this.utils.runJob("GET_ERROR", {
+                            error: err,
+                        });
+                        reject(new Error(err));
+                    } else resolve(station);
+                }
+            );
+        });
+    }
+
+    CALCULATE_SONG_FOR_STATION(payload) {
+        //station, cb, bypassValidate = false
+        return new Promise(async (resolve, reject) => {
+            const songModel = await this.db.runJob("GET_MODEL", {
+                modelName: "song",
+            });
+            const stationModel = await this.db.runJob("GET_MODEL", {
+                modelName: "station",
+            });
+
+            let songList = [];
+            async.waterfall(
+                [
+                    (next) => {
+                        if (payload.station.genres.length === 0) return next();
+                        let genresDone = [];
+                        payload.station.genres.forEach((genre) => {
+                            songModel.find({ genres: genre }, (err, songs) => {
+                                if (!err) {
+                                    songs.forEach((song) => {
+                                        if (songList.indexOf(song._id) === -1) {
+                                            let found = false;
+                                            song.genres.forEach((songGenre) => {
+                                                if (
+                                                    payload.station.blacklistedGenres.indexOf(
+                                                        songGenre
+                                                    ) !== -1
+                                                )
+                                                    found = true;
+                                            });
+                                            if (!found) {
+                                                songList.push(song._id);
+                                            }
+                                        }
+                                    });
+                                }
+                                genresDone.push(genre);
+                                if (
+                                    genresDone.length ===
+                                    payload.station.genres.length
+                                )
+                                    next();
+                            });
+                        });
+                    },
+
+                    (next) => {
+                        let playlist = [];
+                        songList.forEach(function(songId) {
+                            if (payload.station.playlist.indexOf(songId) === -1)
+                                playlist.push(songId);
+                        });
+                        payload.station.playlist.filter((songId) => {
+                            if (songList.indexOf(songId) !== -1)
+                                playlist.push(songId);
+                        });
+
+                        this.utils
+                            .runJob("SHUFFLE", { array: playlist })
+                            .then((playlist) => next(null, playlist))
+                            .catch(next);
+                    },
+
+                    (playlist, next) => {
+                        this.runJob("CALCULATE_OFFICIAL_PLAYLIST_LIST", {
+                            stationId,
+                            songList: playlist,
+                        })
+                            .then(() => next(null, playlist))
+                            .catch(next);
+                    },
+
+                    (playlist, next) => {
+                        stationModel.updateOne(
+                            { _id: station._id },
+                            { $set: { playlist: playlist } },
+                            { runValidators: true },
+                            (err) => {
+                                this.runJob("UPDATE_STATION", {
+                                    stationId: station._id,
+                                })
+                                    .then(() => next(null, playlist))
+                                    .catch(next);
+                            }
+                        );
+                    },
+                ],
+                (err, newPlaylist) => {
+                    if (err) return reject(new Error(err));
+                    resolve(newPlaylist);
+                }
+            );
+        });
+    }
+
+    // Attempts to get the station from Redis. If it's not in Redis, get it from Mongo and add it to Redis.
+    GET_STATION(payload) {
+        //stationId, cb, bypassValidate = false
+        return new Promise((resolve, reject) => {
+            async.waterfall(
+                [
+                    (next) => {
+                        this.cache
+                            .runJob("HGET", {
+                                table: "stations",
+                                key: payload.stationId,
+                            })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+
+                    (station, next) => {
+                        if (station) return next(true, station);
+                        this.stationModel.findOne(
+                            { _id: payload.stationId },
+                            next
+                        );
+                    },
+
+                    (station, next) => {
+                        if (station) {
+                            if (station.type === "official") {
+                                this.runJob(
+                                    "CALCULATE_OFFICIAL_PLAYLIST_LIST",
+                                    {
+                                        stationId: station._id,
+                                        songList: station.playlist,
+                                    }
+                                )
+                                    .then()
+                                    .catch();
+                            }
+                            station = this.stationSchema(station);
+                            this.cache
+                                .runJob("HSET", {
+                                    table: "stations",
+                                    key: payload.stationId,
+                                    value: station,
+                                })
+                                .then()
+                                .catch();
+                            next(true, station);
+                        } else next("Station not found");
+                    },
+                ],
+                async (err, station) => {
+                    if (err && err !== true) {
+                        err = await this.utils.runJob("GET_ERROR", {
+                            error: err,
+                        });
+                        reject(new Error(err));
+                    } else resolve(station);
+                }
+            );
+        });
+    }
+
+    // Attempts to get the station from Redis. If it's not in Redis, get it from Mongo and add it to Redis.
+    GET_STATION_BY_NAME(payload) {
+        //stationName, cb
+        return new Promise(async (resolve, reject) => {
+            const stationModel = await this.db.runJob("GET_MODEL", {
+                modelName: "station",
+            });
+            async.waterfall(
+                [
+                    (next) => {
+                        stationModel.findOne(
+                            { name: payload.stationName },
+                            next
+                        );
+                    },
+
+                    (station, next) => {
+                        if (station) {
+                            if (station.type === "official") {
+                                this.runJob(
+                                    "CALCULATE_OFFICIAL_PLAYLIST_LIST",
+                                    {
+                                        stationId: station._id,
+                                        songList: station.playlist,
+                                    }
+                                );
+                            }
+                            this.cache
+                                .runJob("GET_SCHEMA", { schemaName: "station" })
+                                .then((stationSchema) => {
+                                    station = stationSchema(station);
+                                    this.cache.runJob("HSET", {
+                                        table: "stations",
+                                        key: station._id,
+                                        value: station,
+                                    });
+                                    next(true, station);
+                                });
+                        } else next("Station not found");
+                    },
+                ],
+                (err, station) => {
+                    if (err && err !== true) return reject(new Error(err));
+                    resolve(station);
+                }
+            );
+        });
+    }
+
+    UPDATE_STATION(payload) {
+        //stationId, cb, bypassValidate = false
+        return new Promise((resolve, reject) => {
+            async.waterfall(
+                [
+                    (next) => {
+                        this.stationModel.findOne(
+                            { _id: payload.stationId },
+                            next
+                        );
+                    },
+
+                    (station, next) => {
+                        if (!station) {
+                            this.cache
+                                .runJob("HDEL", {
+                                    table: "stations",
+                                    key: payload.stationId,
+                                })
+                                .then()
+                                .catch();
+                            return next("Station not found");
+                        }
+                        this.cache
+                            .runJob("HSET", {
+                                table: "stations",
+                                key: payload.stationId,
+                                value: station,
+                            })
+                            .then((station) => next(null, station))
+                            .catch(next);
+                    },
+                ],
+                async (err, station) => {
+                    if (err && err !== true) {
+                        err = await this.utils.runJob("GET_ERROR", {
+                            error: err,
+                        });
+                        reject(new Error(err));
+                    } else resolve(station);
+                }
+            );
+        });
+    }
+
+    CALCULATE_OFFICIAL_PLAYLIST_LIST(payload) {
+        //stationId, songList, cb, bypassValidate = false
+        return new Promise(async (resolve, reject) => {
+            const officialPlaylistSchema = await this.cache.runJob(
+                "GET_SCHEMA",
+                {
+                    schemaName: "officialPlaylist",
+                }
+            );
+
+            let lessInfoPlaylist = [];
+            async.each(
+                payload.songList,
+                (song, next) => {
+                    this.songs
+                        .runJob("GET_SONG", { id: song })
+                        .then((response) => {
+                            const song = response.song;
+                            if (song) {
+                                let newSong = {
+                                    songId: song.songId,
+                                    title: song.title,
+                                    artists: song.artists,
+                                    duration: song.duration,
+                                };
+                                lessInfoPlaylist.push(newSong);
+                            }
+                        })
+                        .finally(() => {
+                            next();
+                        });
+                },
+                () => {
+                    this.cache
+                        .runJob("HSET", {
+                            table: "officialPlaylists",
+                            key: payload.stationId,
+                            value: officialPlaylistSchema(
+                                payload.stationId,
+                                lessInfoPlaylist
+                            ),
+                        })
+                        .finally(() => {
+                            this.cache.runJob("PUB", {
+                                channel: "station.newOfficialPlaylist",
+                                value: payload.stationId,
+                            });
+                            resolve();
+                        });
+                }
+            );
+        });
+    }
+
+    SKIP_STATION(payload) {
+        //stationId
+        return new Promise((resolve, reject) => {
+            this.log("INFO", `Skipping station ${payload.stationId}.`);
+
+            this.log(
+                "STATION_ISSUE",
+                `SKIP_STATION_CB - Station ID: ${payload.stationId}.`
+            );
+
+            async.waterfall(
+                [
+                    (next) => {
+                        this.runJob(
+                            "GET_STATION",
+                            {
+                                stationId: payload.stationId,
+                            },
+                            { bypassQueue: payload.bypassQueue }
+                        )
+                            .then((station) => {
+                                next(null, station);
+                            })
+                            .catch(() => {});
+                    },
+                    (station, next) => {
+                        if (!station) return next("Station not found.");
+
+                        if (
+                            station.type === "community" &&
+                            station.partyMode &&
+                            station.queue.length === 0
+                        )
+                            return next(null, null, -11, station); // Community station with party mode enabled and no songs in the queue
+
+                        if (
+                            station.type === "community" &&
+                            station.partyMode &&
+                            station.queue.length > 0
+                        ) {
+                            // Community station with party mode enabled and songs in the queue
+                            if (station.paused) {
+                                return next(null, null, -19, station);
+                            } else {
+                                return this.stationModel.updateOne(
+                                    { _id: payload.stationId },
+                                    {
+                                        $pull: {
+                                            queue: {
+                                                _id: station.queue[0]._id,
+                                            },
+                                        },
+                                    },
+                                    (err) => {
+                                        if (err) return next(err);
+                                        next(
+                                            null,
+                                            station.queue[0],
+                                            -12,
+                                            station
+                                        );
+                                    }
+                                );
+                            }
+                        }
+                        if (
+                            station.type === "community" &&
+                            !station.partyMode
+                        ) {
+                            this.db
+                                .runJob("GET_MODEL", { modelName: "playlist" })
+                                .then((playlistModel) => {
+                                    return playlistModel.findOne(
+                                        { _id: station.privatePlaylist },
+                                        (err, playlist) => {
+                                            if (err) return next(err);
+                                            if (!playlist)
+                                                return next(
+                                                    null,
+                                                    null,
+                                                    -13,
+                                                    station
+                                                );
+                                            playlist = playlist.songs;
+                                            if (playlist.length > 0) {
+                                                let currentSongIndex;
+                                                if (
+                                                    station.currentSongIndex <
+                                                    playlist.length - 1
+                                                )
+                                                    currentSongIndex =
+                                                        station.currentSongIndex +
+                                                        1;
+                                                else currentSongIndex = 0;
+                                                let callback = (err, song) => {
+                                                    if (err) return next(err);
+                                                    if (song)
+                                                        return next(
+                                                            null,
+                                                            song,
+                                                            currentSongIndex,
+                                                            station
+                                                        );
+                                                    else {
+                                                        let song =
+                                                            playlist[
+                                                                currentSongIndex
+                                                            ];
+                                                        let currentSong = {
+                                                            songId: song.songId,
+                                                            title: song.title,
+                                                            duration:
+                                                                song.duration,
+                                                            likes: -1,
+                                                            dislikes: -1,
+                                                        };
+                                                        return next(
+                                                            null,
+                                                            currentSong,
+                                                            currentSongIndex,
+                                                            station
+                                                        );
+                                                    }
+                                                };
+                                                if (
+                                                    playlist[currentSongIndex]
+                                                        ._id
+                                                )
+                                                    this.songs
+                                                        .runJob("GET_SONG", {
+                                                            id:
+                                                                playlist[
+                                                                    currentSongIndex
+                                                                ]._id,
+                                                        })
+                                                        .then((response) =>
+                                                            callback(
+                                                                null,
+                                                                response.song
+                                                            )
+                                                        )
+                                                        .catch(callback);
+                                                else
+                                                    this.songs
+                                                        .runJob(
+                                                            "GET_SONG_FROM_ID",
+                                                            {
+                                                                songId:
+                                                                    playlist[
+                                                                        currentSongIndex
+                                                                    ].songId,
+                                                            }
+                                                        )
+                                                        .then((response) =>
+                                                            callback(
+                                                                null,
+                                                                response.song
+                                                            )
+                                                        )
+                                                        .catch(callback);
+                                            } else
+                                                return next(
+                                                    null,
+                                                    null,
+                                                    -14,
+                                                    station
+                                                );
+                                        }
+                                    );
+                                });
+                        }
+                        if (
+                            station.type === "official" &&
+                            station.playlist.length === 0
+                        ) {
+                            return this.runJob(
+                                "CALCULATE_SONG_FOR_STATION",
+                                { station, bypassQueue: payload.bypassQueue },
+                                { bypassQueue: payload.bypassQueue }
+                            )
+                                .then((playlist) => {
+                                    if (playlist.length === 0)
+                                        return next(
+                                            null,
+                                            this.defaultSong,
+                                            0,
+                                            station
+                                        );
+                                    else {
+                                        this.songs
+                                            .runJob("GET_SONG", {
+                                                id: playlist[0],
+                                            })
+                                            .then((response) => {
+                                                next(
+                                                    null,
+                                                    response.song,
+                                                    0,
+                                                    station
+                                                );
+                                            })
+                                            .catch((err) => {
+                                                return next(
+                                                    null,
+                                                    this.defaultSong,
+                                                    0,
+                                                    station
+                                                );
+                                            });
+                                    }
+                                })
+                                .catch(next);
+                        }
+                        if (
+                            station.type === "official" &&
+                            station.playlist.length > 0
+                        ) {
+                            async.doUntil(
+                                (next) => {
+                                    if (
+                                        station.currentSongIndex <
+                                        station.playlist.length - 1
+                                    ) {
+                                        this.songs
+                                            .runJob("GET_SONG", {
+                                                id:
+                                                    station.playlist[
+                                                        station.currentSongIndex +
+                                                            1
+                                                    ],
+                                            })
+                                            .then((response) => {
+                                                return next(
+                                                    null,
+                                                    response.song,
+                                                    station.currentSongIndex + 1
+                                                );
+                                            })
+                                            .catch((err) => {
+                                                station.currentSongIndex++;
+                                                next(null, null, null);
+                                            });
+                                    } else {
+                                        this.runJob(
+                                            "CALCULATE_SONG_FOR_STATION",
+                                            {
+                                                station,
+                                                bypassQueue:
+                                                    payload.bypassQueue,
+                                            }
+                                        )
+                                            .then((newPlaylist) => {
+                                                this.songs.getSong(
+                                                    newPlaylist[0],
+                                                    (err, song) => {
+                                                        if (err || !song)
+                                                            return next(
+                                                                null,
+                                                                this
+                                                                    .defaultSong,
+                                                                0
+                                                            );
+                                                        station.playlist = newPlaylist;
+                                                        next(null, song, 0);
+                                                    }
+                                                );
+                                            })
+                                            .catch((err) => {
+                                                next(null, this.defaultSong, 0);
+                                            });
+                                    }
+                                },
+                                (song, currentSongIndex, next) => {
+                                    if (!!song)
+                                        return next(
+                                            null,
+                                            true,
+                                            currentSongIndex
+                                        );
+                                    else return next(null, false);
+                                },
+                                (err, song, currentSongIndex) => {
+                                    return next(
+                                        err,
+                                        song,
+                                        currentSongIndex,
+                                        station
+                                    );
+                                }
+                            );
+                        }
+                    },
+                    (song, currentSongIndex, station, next) => {
+                        let $set = {};
+                        if (song === null) $set.currentSong = null;
+                        else if (song.likes === -1 && song.dislikes === -1) {
+                            $set.currentSong = {
+                                songId: song.songId,
+                                title: song.title,
+                                duration: song.duration,
+                                skipDuration: 0,
+                                likes: -1,
+                                dislikes: -1,
+                            };
+                        } else {
+                            $set.currentSong = {
+                                songId: song.songId,
+                                title: song.title,
+                                artists: song.artists,
+                                duration: song.duration,
+                                likes: song.likes,
+                                dislikes: song.dislikes,
+                                skipDuration: song.skipDuration,
+                                thumbnail: song.thumbnail,
+                            };
+                        }
+                        if (currentSongIndex >= 0)
+                            $set.currentSongIndex = currentSongIndex;
+                        $set.startedAt = Date.now();
+                        $set.timePaused = 0;
+                        if (station.paused) $set.pausedAt = Date.now();
+                        next(null, $set, station);
+                    },
+
+                    ($set, station, next) => {
+                        this.stationModel.updateOne(
+                            { _id: station._id },
+                            { $set },
+                            (err) => {
+                                this.runJob(
+                                    "UPDATE_STATION",
+                                    {
+                                        stationId: station._id,
+                                        bypassQueue: payload.bypassQueue,
+                                    },
+
+                                    { bypassQueue: payload.bypassQueue }
+                                )
+                                    .then((station) => {
+                                        if (
+                                            station.type === "community" &&
+                                            station.partyMode === true
+                                        )
+                                            this.cache
+                                                .runJob("PUB", {
+                                                    channel:
+                                                        "station.queueUpdate",
+                                                    value: payload.stationId,
+                                                })
+                                                .then()
+                                                .catch();
+                                        next(null, station);
+                                    })
+                                    .catch(next);
+                            }
+                        );
+                    },
+                ],
+                async (err, station) => {
+                    if (err) {
+                        err = await this.utils.runJob("GET_ERROR", {
+                            error: err,
+                        });
+                        this.log(
+                            "ERROR",
+                            `Skipping station "${payload.stationId}" failed. "${err}"`
+                        );
+                        reject(new Error(err));
+                    } else {
+                        if (
+                            station.currentSong !== null &&
+                            station.currentSong.songId !== undefined
+                        ) {
+                            station.currentSong.skipVotes = 0;
+                        }
+                        //TODO Pub/Sub this
+
+                        this.utils
+                            .runJob("EMIT_TO_ROOM", {
+                                room: `station.${station._id}`,
+                                args: [
+                                    "event:songs.next",
+                                    {
+                                        currentSong: station.currentSong,
+                                        startedAt: station.startedAt,
+                                        paused: station.paused,
+                                        timePaused: 0,
+                                    },
+                                ],
+                            })
+                            .then()
+                            .catch();
+
+                        if (station.privacy === "public") {
+                            this.utils
+                                .runJob("EMIT_TO_ROOM", {
+                                    room: "home",
+                                    args: [
+                                        "event:station.nextSong",
+                                        station._id,
+                                        station.currentSong,
+                                    ],
+                                })
+                                .then()
+                                .catch();
+                        } else {
+                            let sockets = await this.utils.runJob(
+                                "GET_ROOM_SOCKETS",
+                                { room: "home" }
+                            );
+                            for (let socketId in sockets) {
+                                let socket = sockets[socketId];
+                                let session = sockets[socketId].session;
+                                if (session.sessionId) {
+                                    this.cache
+                                        .runJob("HGET", {
+                                            table: "sessions",
+                                            key: session.sessionId,
+                                        })
+                                        .then((session) => {
+                                            if (session) {
+                                                this.db
+                                                    .runJob("GET_MODEL", {
+                                                        modelName: "user",
+                                                    })
+                                                    .then((userModel) => {
+                                                        userModel.findOne(
+                                                            {
+                                                                _id:
+                                                                    session.userId,
+                                                            },
+                                                            (err, user) => {
+                                                                if (
+                                                                    !err &&
+                                                                    user
+                                                                ) {
+                                                                    if (
+                                                                        user.role ===
+                                                                        "admin"
+                                                                    )
+                                                                        socket.emit(
+                                                                            "event:station.nextSong",
+                                                                            station._id,
+                                                                            station.currentSong
+                                                                        );
+                                                                    else if (
+                                                                        station.type ===
+                                                                            "community" &&
+                                                                        station.owner ===
+                                                                            session.userId
+                                                                    )
+                                                                        socket.emit(
+                                                                            "event:station.nextSong",
+                                                                            station._id,
+                                                                            station.currentSong
+                                                                        );
+                                                                }
+                                                            }
+                                                        );
+                                                    });
+                                            }
+                                        });
+                                }
+                            }
+                        }
+
+                        if (
+                            station.currentSong !== null &&
+                            station.currentSong.songId !== undefined
+                        ) {
+                            this.utils.runJob("SOCKETS_JOIN_SONG_ROOM", {
+                                sockets: await this.utils.runJob(
+                                    "GET_ROOM_SOCKETS",
+                                    { room: `station.${station._id}` }
+                                ),
+                                room: `song.${station.currentSong.songId}`,
+                            });
+                            if (!station.paused) {
+                                this.notifications.runJob("SCHEDULE", {
+                                    name: `stations.nextSong?id=${station._id}`,
+                                    time: station.currentSong.duration * 1000,
+                                    station,
+                                });
+                            }
+                        } else {
+                            this.utils
+                                .runJob("SOCKETS_LEAVE_SONG_ROOMS", {
+                                    sockets: await this.utils.runJob(
+                                        "GET_ROOM_SOCKETS",
+                                        { room: `station.${station._id}` }
+                                    ),
+                                })
+                                .then()
+                                .catch();
+                        }
+
+                        resolve({ station: station });
+                    }
+                }
+            );
+        });
+    }
+
+    CAN_USER_VIEW_STATION(payload) {
+        // station, userId, cb
+        return new Promise((resolve, reject) => {
+            async.waterfall(
+                [
+                    (next) => {
+                        if (payload.station.privacy !== "private")
+                            return next(true);
+                        if (!payload.userId) return next("Not allowed");
+                        next();
+                    },
+
+                    (next) => {
+                        this.db
+                            .runJob("GET_MODEL", {
+                                modelName: "user",
+                            })
+                            .then((userModel) => {
+                                userModel.findOne(
+                                    { _id: payload.userId },
+                                    next
+                                );
+                            });
+                    },
+
+                    (user, next) => {
+                        if (!user) return next("Not allowed");
+                        if (user.role === "admin") return next(true);
+                        if (payload.station.type === "official")
+                            return next("Not allowed");
+                        if (payload.station.owner === payload.userId)
+                            return next(true);
+                        next("Not allowed");
+                    },
+                ],
+                async (errOrResult) => {
+                    if (errOrResult !== true && errOrResult !== "Not allowed") {
+                        errOrResult = await this.utils.runJob("GET_ERROR", {
+                            error: errOrResult,
+                        });
+                        reject(new Error(errOrResult));
+                    } else {
+                        resolve(errOrResult === true ? true : false);
+                    }
+                }
+            );
+        });
+    }
+}
+
+module.exports = new StationsModule();

+ 318 - 167
backend/logic/tasks.js

@@ -1,173 +1,324 @@
-'use strict';
+const CoreClass = require("../core.js");
 
-const coreClass = require("../core");
+const tasks = {};
 
 const async = require("async");
 const fs = require("fs");
 
-let tasks = {};
-
-module.exports = class extends coreClass {
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
-			
-			this.cache = this.moduleManager.modules["cache"];
-			this.stations = this.moduleManager.modules["stations"];
-			this.notifications = this.moduleManager.modules["notifications"];
-			this.utils = this.moduleManager.modules["utils"];
-
-			//this.createTask("testTask", testTask, 5000, true);
-			this.createTask("stationSkipTask", this.checkStationSkipTask, 1000 * 60 * 30);
-			this.createTask("sessionClearTask", this.sessionClearingTask, 1000 * 60 * 60 * 6);
-			this.createTask("logFileSizeCheckTask", this.logFileSizeCheckTask, 1000 * 60 * 60);
-
-			resolve();
-		});
-	}
-
-	async createTask(name, fn, timeout, paused = false) {
-		try { await this._validateHook(); } catch { return; }
-
-		tasks[name] = {
-			name,
-			fn,
-			timeout,
-			lastRan: 0,
-			timer: null
-		};
-		if (!paused) this.handleTask(tasks[name]);
-	}
-
-	async pauseTask(name) {
-		try { await this._validateHook(); } catch { return; }
-
-		if (tasks[name].timer) tasks[name].timer.pause();
-	}
-
-	async resumeTask(name) {
-		try { await this._validateHook(); } catch { return; }
-
-		tasks[name].timer.resume();
-	}
-
-	async handleTask(task) {
-		try { await this._validateHook(); } catch { return; }
-
-		if (task.timer) task.timer.pause();
-		
-		task.fn.apply(this, [
-			() => {
-				task.lastRan = Date.now();
-				task.timer = new this.utils.Timer(() => {
-					this.handleTask(task);
-				}, task.timeout, false);
-			}
-		]);
-	}
-
-	/*testTask(callback) {
-		//Stuff
-		console.log("Starting task");
-		setTimeout(() => {
-			console.log("Callback");
-			callback();
-		}, 10000);
-	}*/
-
-	async checkStationSkipTask(callback) {
-		this.logger.info("TASK_STATIONS_SKIP_CHECK", `Checking for stations to be skipped.`, false);
-		async.waterfall([
-			(next) => {
-				this.cache.hgetall('stations', next);
-			},
-			(stations, next) => {
-				async.each(stations, (station, next2) => {
-					if (station.paused || !station.currentSong || !station.currentSong.title) return next2();
-					const timeElapsed = Date.now() - station.startedAt - station.timePaused;
-					if (timeElapsed <= station.currentSong.duration) return next2();
-					else {
-						this.logger.error("TASK_STATIONS_SKIP_CHECK", `Skipping ${station._id} as it should have skipped already.`);
-						this.stations.initializeStation(station._id);
-						next2();
-					}
-				}, () => {
-					next();
-				});
-			}
-		], () => {
-			callback();
-		});
-	}
-
-	async sessionClearingTask(callback) {
-		this.logger.info("TASK_SESSION_CLEAR", `Checking for sessions to be cleared.`, false);
-		async.waterfall([
-			(next) => {
-				this.cache.hgetall('sessions', next);
-			},
-			(sessions, next) => {
-				if (!sessions) return next();
-				let keys = Object.keys(sessions);
-				async.each(keys, (sessionId, next2) => {
-					let session = sessions[sessionId];
-					if (session && session.refreshDate && (Date.now() - session.refreshDate) < (60 * 60 * 24 * 30 * 1000)) return next2();
-					if (!session) {
-						this.logger.info("TASK_SESSION_CLEAR", 'Removing an empty session.');
-						this.cache.hdel('sessions', sessionId, () => {
-							next2();
-						});
-					} else if (!session.refreshDate) {
-						session.refreshDate = Date.now();
-						this.cache.hset('sessions', sessionId, session, () => {
-							next2();
-						});
-					} else if ((Date.now() - session.refreshDate) > (60 * 60 * 24 * 30 * 1000)) {
-						this.utils.socketsFromSessionId(session.sessionId, (sockets) => {
-							if (sockets.length > 0) {
-								session.refreshDate = Date.now();
-								this.cache.hset('sessions', sessionId, session, () => {
-									next2()
-								});
-							} else {
-								this.logger.info("TASK_SESSION_CLEAR", `Removing session ${sessionId} for user ${session.userId} since inactive for 30 days and not currently in use.`);
-								this.cache.hdel('sessions', session.sessionId, () => {
-									next2();
-								});
-							}
-						});
-					} else {
-						this.logger.error("TASK_SESSION_CLEAR", "This should never log.");
-						next2();
-					}
-				}, () => {
-					next();
-				});
-			}
-		], () => {
-			callback();
-		});
-	}
-
-	async logFileSizeCheckTask(callback) {
-		this.logger.info("TASK_LOG_FILE_SIZE_CHECK", `Checking the size for the log files.`);
-		async.each(
-			["all.log", "debugStation.log", "error.log", "info.log", "success.log"],
-			(fileName, next) => {
-				const stats = fs.statSync(`${__dirname}/../../log/${fileName}`);
-				const mb = stats.size / 1000000;
-				if (mb > 25) return next(true);
-				else next();
-			},
-			(err) => {
-				if (err === true) {
-					this.logger.error("LOGGER_FILE_SIZE_WARNING", "************************************WARNING*************************************");
-					this.logger.error("LOGGER_FILE_SIZE_WARNING", "***************ONE OR MORE LOG FILES APPEAR TO BE MORE THAN 25MB****************");
-					this.logger.error("LOGGER_FILE_SIZE_WARNING", "****MAKE SURE TO REGULARLY CLEAR UP THE LOG FILES, MANUALLY OR AUTOMATICALLY****");
-					this.logger.error("LOGGER_FILE_SIZE_WARNING", "********************************************************************************");
-				}
-				callback();
-			}
-		);
-	}
+const Timer = require("../classes/Timer.class");
+
+class TasksModule extends CoreClass {
+    constructor() {
+        super("tasks");
+    }
+
+    initialize() {
+        return new Promise((resolve, reject) => {
+            // return reject(new Error("Not fully migrated yet."));
+
+            this.cache = this.moduleManager.modules["cache"];
+            this.stations = this.moduleManager.modules["stations"];
+            this.notifications = this.moduleManager.modules["notifications"];
+            this.utils = this.moduleManager.modules["utils"];
+
+            //this.createTask("testTask", testTask, 5000, true);
+
+            this.runJob("CREATE_TASK", {
+                name: "stationSkipTask",
+                fn: this.checkStationSkipTask,
+                timeout: 1000 * 60 * 30,
+            });
+
+            this.runJob("CREATE_TASK", {
+                name: "sessionClearTask",
+                fn: this.sessionClearingTask,
+                timeout: 1000 * 60 * 60 * 6,
+            });
+
+            this.runJob("CREATE_TASK", {
+                name: "logFileSizeCheckTask",
+                fn: this.logFileSizeCheckTask,
+                timeout: 1000 * 60 * 60,
+            });
+
+            resolve();
+        });
+    }
+
+    CREATE_TASK(payload) {
+        return new Promise((resolve, reject) => {
+            tasks[payload.name] = {
+                name: payload.name,
+                fn: payload.fn,
+                timeout: payload.timeout,
+                lastRan: 0,
+                timer: null,
+            };
+
+            if (!payload.paused) {
+                this.runJob("RUN_TASK", { name: payload.name })
+                    .then(() => resolve())
+                    .catch((err) => reject(err));
+            } else resolve();
+        });
+    }
+
+    PAUSE_TASK(payload) {
+        return new Promise((resolve, reject) => {
+            if (tasks[payload.name].timer) tasks[name].timer.pause();
+            resolve();
+        });
+    }
+
+    RESUME_TASK(payload) {
+        return new Promise((resolve, reject) => {
+            tasks[payload.name].timer.resume();
+            resolve();
+        });
+    }
+
+    RUN_TASK(payload) {
+        return new Promise((resolve, reject) => {
+            const task = tasks[payload.name];
+            if (task.timer) task.timer.pause();
+
+            task.fn.apply(this).then(() => {
+                task.lastRan = Date.now();
+                task.timer = new Timer(
+                    () => {
+                        this.runJob("RUN_TASK", { name: payload.name });
+                    },
+                    task.timeout,
+                    false
+                );
+
+                resolve();
+            });
+        });
+    }
+
+    checkStationSkipTask(callback) {
+        return new Promise((resolve, reject) => {
+            this.log(
+                "INFO",
+                "TASK_STATIONS_SKIP_CHECK",
+                `Checking for stations to be skipped.`,
+                false
+            );
+            async.waterfall(
+                [
+                    (next) => {
+                        this.cache
+                            .runJob("HGETALL", {
+                                table: "stations",
+                            })
+                            .then((response) => next(null, response))
+                            .catch(next);
+                    },
+                    (stations, next) => {
+                        async.each(
+                            stations,
+                            (station, next2) => {
+                                if (
+                                    station.paused ||
+                                    !station.currentSong ||
+                                    !station.currentSong.title
+                                )
+                                    return next2();
+                                const timeElapsed =
+                                    Date.now() -
+                                    station.startedAt -
+                                    station.timePaused;
+                                if (timeElapsed <= station.currentSong.duration)
+                                    return next2();
+                                else {
+                                    this.log(
+                                        "ERROR",
+                                        "TASK_STATIONS_SKIP_CHECK",
+                                        `Skipping ${station._id} as it should have skipped already.`
+                                    );
+                                    this.stations
+                                        .runJob("INITIALIZE_STATION", {
+                                            stationId: station._id,
+                                        })
+                                        .then(() => {
+                                            next2();
+                                        });
+                                }
+                            },
+                            () => {
+                                next();
+                            }
+                        );
+                    },
+                ],
+                () => {
+                    resolve();
+                }
+            );
+        });
+    }
+
+    sessionClearingTask() {
+        return new Promise((resolve, reject) => {
+            this.log(
+                "INFO",
+                "TASK_SESSION_CLEAR",
+                `Checking for sessions to be cleared.`
+            );
+
+            async.waterfall(
+                [
+                    (next) => {
+                        this.cache
+                            .runJob("HGETALL", {
+                                table: "sessions",
+                            })
+                            .then((sessions) => {
+                                next(null, sessions);
+                            })
+                            .catch(next);
+                    },
+                    (sessions, next) => {
+                        if (!sessions) return next();
+                        let keys = Object.keys(sessions);
+                        async.each(
+                            keys,
+                            (sessionId, next2) => {
+                                let session = sessions[sessionId];
+                                if (
+                                    session &&
+                                    session.refreshDate &&
+                                    Date.now() - session.refreshDate <
+                                        60 * 60 * 24 * 30 * 1000
+                                )
+                                    return next2();
+                                if (!session) {
+                                    this.log(
+                                        "INFO",
+                                        "TASK_SESSION_CLEAR",
+                                        "Removing an empty session."
+                                    );
+                                    this.cache
+                                        .runJob("HDEL", {
+                                            table: "sessions",
+                                            key: sessionId,
+                                        })
+                                        .finally(() => next2());
+                                } else if (!session.refreshDate) {
+                                    session.refreshDate = Date.now();
+                                    this.cache
+                                        .runJob("HSET", {
+                                            table: "sessions",
+                                            key: sessionId,
+                                            value: session,
+                                        })
+                                        .finally(() => next2());
+                                } else if (
+                                    Date.now() - session.refreshDate >
+                                    60 * 60 * 24 * 30 * 1000
+                                ) {
+                                    this.utils
+                                        .runJob("SOCKETS_FROM_SESSION_ID", {
+                                            sessionId: session.sessionId,
+                                        })
+                                        .then((response) => {
+                                            if (response.sockets.length > 0) {
+                                                session.refreshDate = Date.now();
+                                                this.cache
+                                                    .runJob("HSET", {
+                                                        table: "sessions",
+                                                        key: sessionId,
+                                                        value: session,
+                                                    })
+                                                    .finally(() => next2());
+                                            } else {
+                                                this.log(
+                                                    "INFO",
+                                                    "TASK_SESSION_CLEAR",
+                                                    `Removing session ${sessionId} for user ${session.userId} since inactive for 30 days and not currently in use.`
+                                                );
+                                                this.cache
+                                                    .runJob("HDEL", {
+                                                        table: "sessions",
+                                                        key: session.sessionId,
+                                                    })
+                                                    .finally(() => next2());
+                                            }
+                                        });
+                                } else {
+                                    this.log(
+                                        "ERROR",
+                                        "TASK_SESSION_CLEAR",
+                                        "This should never log."
+                                    );
+                                    next2();
+                                }
+                            },
+                            () => {
+                                next();
+                            }
+                        );
+                    },
+                ],
+                () => {
+                    resolve();
+                }
+            );
+        });
+    }
+
+    logFileSizeCheckTask() {
+        return new Promise((resolve, reject) => {
+            this.log(
+                "INFO",
+                "TASK_LOG_FILE_SIZE_CHECK",
+                `Checking the size for the log files.`
+            );
+            async.each(
+                [
+                    "all.log",
+                    "debugStation.log",
+                    "error.log",
+                    "info.log",
+                    "success.log",
+                ],
+                (fileName, next) => {
+                    const stats = fs.statSync(
+                        `${__dirname}/../../log/${fileName}`
+                    );
+                    const mb = stats.size / 1000000;
+                    if (mb > 25) return next(true);
+                    else next();
+                },
+                (err) => {
+                    if (err === true) {
+                        this.log(
+                            "ERROR",
+                            "LOGGER_FILE_SIZE_WARNING",
+                            "************************************WARNING*************************************"
+                        );
+                        this.log(
+                            "ERROR",
+                            "LOGGER_FILE_SIZE_WARNING",
+                            "***************ONE OR MORE LOG FILES APPEAR TO BE MORE THAN 25MB****************"
+                        );
+                        this.log(
+                            "ERROR",
+                            "LOGGER_FILE_SIZE_WARNING",
+                            "****MAKE SURE TO REGULARLY CLEAR UP THE LOG FILES, MANUALLY OR AUTOMATICALLY****"
+                        );
+                        this.log(
+                            "ERROR",
+                            "LOGGER_FILE_SIZE_WARNING",
+                            "********************************************************************************"
+                        );
+                    }
+                    resolve();
+                }
+            );
+        });
+    }
 }
+
+module.exports = new TasksModule();

+ 756 - 559
backend/logic/utils.js

@@ -1,565 +1,762 @@
-'use strict';
-
-const coreClass = require("../core");
-
-const config  = require('config'),
-	  async	  = require('async'),
-	  request = require('request');
-
-class Timer {
-	constructor(callback, delay, paused) {
-		this.callback = callback;
-		this.timerId = undefined;
-		this.start = undefined;
-		this.paused = paused;
-		this.remaining = delay;
-		this.timeWhenPaused = 0;
-		this.timePaused = Date.now();
-
-		if (!paused) {
-			this.resume();
-		}
-	}
-
-	pause() {
-		clearTimeout(this.timerId);
-		this.remaining -= Date.now() - this.start;
-		this.timePaused = Date.now();
-		this.paused = true;
-	}
-
-	ifNotPaused() {
-		if (!this.paused) {
-			this.resume();
-		}
-	}
-
-	resume() {
-		this.start = Date.now();
-		clearTimeout(this.timerId);
-		this.timerId = setTimeout(this.callback, this.remaining);
-		this.timeWhenPaused = Date.now() - this.timePaused;
-		this.paused = false;
-	}
-
-	resetTimeWhenPaused() {
-		this.timeWhenPaused = 0;
-	}
-
-	getTimePaused() {
-		if (!this.paused) {
-			return this.timeWhenPaused;
-		} else {
-			return Date.now() - this.timePaused;
-		}
-	}
-} 
+const CoreClass = require("../core.js");
+
+const config = require("config");
+const async = require("async");
+const request = require("request");
+const crypto = require("crypto");
 
 let youtubeRequestCallbacks = [];
 let youtubeRequestsPending = 0;
 let youtubeRequestsActive = false;
 
-module.exports = class extends coreClass {
-	initialize() {
-		return new Promise((resolve, reject) => {
-			this.setStage(1);
-			
-			this.io = this.moduleManager.modules["io"];
-			this.db = this.moduleManager.modules["db"];
-			this.spotify = this.moduleManager.modules["spotify"];
-			this.cache = this.moduleManager.modules["cache"];
-
-			this.Timer = Timer;
-
-			resolve();
-		});
-	}
-
-	async parseCookies(cookieString) {
-		try { await this._validateHook(); } catch { return; }
-		let cookies = {};
-		if (cookieString) cookieString.split("; ").map((cookie) => {
-			(cookies[cookie.substring(0, cookie.indexOf("="))] = cookie.substring(cookie.indexOf("=") + 1, cookie.length));
-		});
-		return cookies;
-	}
-
-	async cookiesToString(cookies) {
-		try { await this._validateHook(); } catch { return; }
-		let newCookie = [];
-		for (let prop in cookie) {
-			newCookie.push(prop + "=" + cookie[prop]);
-		}
-		return newCookie.join("; ");
-	}
-
-	async removeCookie(cookieString, cookieName) {
-		try { await this._validateHook(); } catch { return; }
-		var cookies = this.parseCookies(cookieString);
-		delete cookies[cookieName];
-		return this.toString(cookies);
-	}
-
-	async htmlEntities(str) {
-		try { await this._validateHook(); } catch { return; }
-		return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
-	}
-
-	async generateRandomString(len) {
-		try { await this._validateHook(); } catch { return; }
-		let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split("");
-		let result = [];
-		for (let i = 0; i < len; i++) {
-			result.push(chars[await this.getRandomNumber(0, chars.length - 1)]);
-		}
-		return result.join("");
-	}
-
-	async getSocketFromId(socketId) {
-		try { await this._validateHook(); } catch { return; }
-		return globals.io.sockets.sockets[socketId];
-	}
-
-	async getRandomNumber(min, max) {
-		try { await this._validateHook(); } catch { return; }
-		return Math.floor(Math.random() * (max - min + 1)) + min
-	}
-
-	async convertTime(duration) {
-		try { await this._validateHook(); } catch { return; }
-		let a = duration.match(/\d+/g);
-	
-		if (duration.indexOf('M') >= 0 && duration.indexOf('H') == -1 && duration.indexOf('S') == -1) {
-			a = [0, a[0], 0];
-		}
-	
-		if (duration.indexOf('H') >= 0 && duration.indexOf('M') == -1) {
-			a = [a[0], 0, a[1]];
-		}
-		if (duration.indexOf('H') >= 0 && duration.indexOf('M') == -1 && duration.indexOf('S') == -1) {
-			a = [a[0], 0, 0];
-		}
-	
-		duration = 0;
-	
-		if (a.length == 3) {
-			duration = duration + parseInt(a[0]) * 3600;
-			duration = duration + parseInt(a[1]) * 60;
-			duration = duration + parseInt(a[2]);
-		}
-	
-		if (a.length == 2) {
-			duration = duration + parseInt(a[0]) * 60;
-			duration = duration + parseInt(a[1]);
-		}
-	
-		if (a.length == 1) {
-			duration = duration + parseInt(a[0]);
-		}
-	
-		let hours = Math.floor(duration / 3600);
-		let minutes = Math.floor(duration % 3600 / 60);
-		let seconds = Math.floor(duration % 3600 % 60);
-	
-		return (hours < 10 ? ("0" + hours + ":") : (hours + ":")) + (minutes < 10 ? ("0" + minutes + ":") : (minutes + ":")) + (seconds < 10 ? ("0" + seconds) : seconds);
-	}
-
-	async guid () {
-		try { await this._validateHook(); } catch { return; }
-		return [1,1,0,1,0,1,0,1,0,1,1,1].map(b => b ? Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1) : '-').join('');
-	}
-
-	async socketFromSession(socketId) {
-		try { await this._validateHook(); } catch { return; }
-
-		let io = await this.io.io();
-		let ns = io.of("/");
-		if (ns) {
-			return ns.connected[socketId];
-		}
-	}
-
-	async socketsFromSessionId(sessionId, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		let io = await this.io.io();
-		let ns = io.of("/");
-		let sockets = [];
-		if (ns) {
-			async.each(Object.keys(ns.connected), (id, next) => {
-				let session = ns.connected[id].session;
-				if (session.sessionId === sessionId) sockets.push(session.sessionId);
-				next();
-			}, () => {
-				cb(sockets);
-			});
-		}
-	}
-
-	async socketsFromUser(userId, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		let io = await this.io.io();
-		let ns = io.of("/");
-		let sockets = [];
-		if (ns) {
-			async.each(Object.keys(ns.connected), (id, next) => {
-				let session = ns.connected[id].session;
-				this.cache.hget('sessions', session.sessionId, (err, session) => {
-					if (!err && session && session.userId === userId) sockets.push(ns.connected[id]);
-					next();
-				});
-			}, () => {
-				cb(sockets);
-			});
-		}
-	}
-
-	async socketsFromIP(ip, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		let io = await this.io.io();
-		let ns = io.of("/");
-		let sockets = [];
-		if (ns) {
-			async.each(Object.keys(ns.connected), (id, next) => {
-				let session = ns.connected[id].session;
-				this.cache.hget('sessions', session.sessionId, (err, session) => {
-					if (!err && session && ns.connected[id].ip === ip) sockets.push(ns.connected[id]);
-					next();
-				});
-			}, () => {
-				cb(sockets);
-			});
-		}
-	}
-
-	async socketsFromUserWithoutCache(userId, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		let io = await this.io.io();
-		let ns = io.of("/");
-		let sockets = [];
-		if (ns) {
-			async.each(Object.keys(ns.connected), (id, next) => {
-				let session = ns.connected[id].session;
-				if (session.userId === userId) sockets.push(ns.connected[id]);
-				next();
-			}, () => {
-				cb(sockets);
-			});
-		}
-	}
-
-	async socketLeaveRooms(socketid) {
-		try { await this._validateHook(); } catch { return; }
-
-		let socket = await this.socketFromSession(socketid);
-		let rooms = socket.rooms;
-		for (let room in rooms) {
-			socket.leave(room);
-		}
-	}
-
-	async socketJoinRoom(socketId, room) {
-		try { await this._validateHook(); } catch { return; }
-
-		let socket = await this.socketFromSession(socketId);
-		let rooms = socket.rooms;
-		for (let room in rooms) {
-			socket.leave(room);
-		}
-		socket.join(room);
-	}
-
-	async socketJoinSongRoom(socketId, room) {
-		try { await this._validateHook(); } catch { return; }
-
-		let socket = await this.socketFromSession(socketId);
-		let rooms = socket.rooms;
-		for (let room in rooms) {
-			if (room.indexOf('song.') !== -1) socket.leave(rooms);
-		}
-		socket.join(room);
-	}
-
-	async socketsJoinSongRoom(sockets, room) {
-		try { await this._validateHook(); } catch { return; }
-
-		for (let id in sockets) {
-			let socket = sockets[id];
-			let rooms = socket.rooms;
-			for (let room in rooms) {
-				if (room.indexOf('song.') !== -1) socket.leave(room);
-			}
-			socket.join(room);
-		}
-	}
-
-	async socketsLeaveSongRooms(sockets) {
-		try { await this._validateHook(); } catch { return; }
-
-		for (let id in sockets) {
-			let socket = sockets[id];
-			let rooms = socket.rooms;
-			for (let room in rooms) {
-				if (room.indexOf('song.') !== -1) socket.leave(room);
-			}
-		}
-	}
-
-	async emitToRoom(room, ...args) {
-		try { await this._validateHook(); } catch { return; }
-
-		let io = await this.io.io();
-		let sockets = io.sockets.sockets;
-		for (let id in sockets) {
-			let socket = sockets[id];
-			if (socket.rooms[room]) {
-				socket.emit.apply(socket, args);
-			}
-		}
-	}
-
-	async getRoomSockets(room) {
-		try { await this._validateHook(); } catch { return; }
-
-		let io = await this.io.io();
-		let sockets = io.sockets.sockets;
-		let roomSockets = [];
-		for (let id in sockets) {
-			let socket = sockets[id];
-			if (socket.rooms[room]) roomSockets.push(socket);
-		}
-		return roomSockets;
-	}
-
-	async getSongFromYouTube(songId, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		youtubeRequestCallbacks.push({cb: (test) => {
-			youtubeRequestsActive = true;
-			const youtubeParams = [
-				'part=snippet,contentDetails,statistics,status',
-				`id=${encodeURIComponent(songId)}`,
-				`key=${config.get('apis.youtube.key')}`
-			].join('&');
-
-			request(`https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`, (err, res, body) => {
-
-				youtubeRequestCallbacks.splice(0, 1);
-				if (youtubeRequestCallbacks.length > 0) {
-					youtubeRequestCallbacks[0].cb(youtubeRequestCallbacks[0].songId);
-				} else youtubeRequestsActive = false;
-
-				if (err) {
-					console.error(err);
-					return null;
-				}
-
-				body = JSON.parse(body);
-
-				//TODO Clean up duration converter
-  				let dur = body.items[0].contentDetails.duration;
-				dur = dur.replace('PT', '');
-				let duration = 0;
-				dur = dur.replace(/([\d]*)H/, (v, v2) => {
-					v2 = Number(v2);
-					duration = (v2 * 60 * 60);
-					return '';
-				});
-				dur = dur.replace(/([\d]*)M/, (v, v2) => {
-					v2 = Number(v2);
-					duration += (v2 * 60);
-					return '';
-				});
-				dur = dur.replace(/([\d]*)S/, (v, v2) => {
-					v2 = Number(v2);
-					duration += v2;
-					return '';
-				});
-
-				let song = {
-					songId: body.items[0].id,
-					title: body.items[0].snippet.title,
-					duration
-				};
-				cb(song);
-			});
-		}, songId});
-
-		if (!youtubeRequestsActive) {
-			youtubeRequestCallbacks[0].cb(youtubeRequestCallbacks[0].songId);
-		}
-	}
-
-	async getPlaylistFromYouTube(url, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		let name = 'list'.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
-		var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
-		let playlistId = regex.exec(url)[1];
-
-		function getPage(pageToken, songs) {
-			let nextPageToken = (pageToken) ? `pageToken=${pageToken}` : '';
-			const youtubeParams = [
-				'part=contentDetails',
-				`playlistId=${encodeURIComponent(playlistId)}`,
-				`maxResults=5`,
-				`key=${config.get('apis.youtube.key')}`,
-				nextPageToken
-			].join('&');
-
-			request(`https://www.googleapis.com/youtube/v3/playlistItems?${youtubeParams}`, (err, res, body) => {
-				if (err) {
-					console.error(err);
-					return next('Failed to find playlist from YouTube');
-				}
-
-				body = JSON.parse(body);
-				songs = songs.concat(body.items);
-				if (body.nextPageToken) getPage(body.nextPageToken, songs);
-				else {
-					console.log(songs);
-					cb(songs);
-				}
-			});
-		}
-		getPage(null, []);
-	}
-
-	async getSongFromSpotify(song, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		if (!config.get("apis.spotify.enabled")) return cb("Spotify is not enabled", null);
-
-		const spotifyParams = [
-			`q=${encodeURIComponent(song.title)}`,
-			`type=track`
-		].join('&');
-
-		const token = await this.spotify.getToken();
-		const options = {
-			url: `https://api.spotify.com/v1/search?${spotifyParams}`,
-			headers: {
-				Authorization: `Bearer ${token}`
-			}
-		};
-
-		request(options, (err, res, body) => {
-			if (err) console.error(err);
-			body = JSON.parse(body);
-			if (body.error) console.error(body.error);
-
-			durationArtistLoop:
-			for (let i in body) {
-				let items = body[i].items;
-				for (let j in items) {
-					let item = items[j];
-					let hasArtist = false;
-					for (let k = 0; k < item.artists.length; k++) {
-						let artist = item.artists[k];
-						if (song.title.indexOf(artist.name) !== -1) hasArtist = true;
-					}
-					if (hasArtist && song.title.indexOf(item.name) !== -1) {
-						song.duration = item.duration_ms / 1000;
-						song.artists = item.artists.map(artist => {
-							return artist.name;
-						});
-						song.title = item.name;
-						song.explicit = item.explicit;
-						song.thumbnail = item.album.images[1].url;
-						break durationArtistLoop;
-					}
-				}
-			}
-
-			cb(null, song);
-		});
-	}
-
-	async getSongsFromSpotify(title, artist, cb) {
-		try { await this._validateHook(); } catch { return; }
-
-		if (!config.get("apis.spotify.enabled")) return cb([]);
-
-		const spotifyParams = [
-			`q=${encodeURIComponent(title)}`,
-			`type=track`
-		].join('&');
-		
-		const token = await this.spotify.getToken();
-		const options = {
-			url: `https://api.spotify.com/v1/search?${spotifyParams}`,
-			headers: {
-				Authorization: `Bearer ${token}`
-			}
-		};
-
-		request(options, (err, res, body) => {
-			if (err) return console.error(err);
-			body = JSON.parse(body);
-			if (body.error) return console.error(body.error);
-
-			let songs = [];
-
-			for (let i in body) {
-				let items = body[i].items;
-				for (let j in items) {
-					let item = items[j];
-					let hasArtist = false;
-					for (let k = 0; k < item.artists.length; k++) {
-						let localArtist = item.artists[k];
-						if (artist.toLowerCase() === localArtist.name.toLowerCase()) hasArtist = true;
-					}
-					if (hasArtist && (title.indexOf(item.name) !== -1 || item.name.indexOf(title) !== -1)) {
-						let song = {};
-						song.duration = item.duration_ms / 1000;
-						song.artists = item.artists.map(artist => {
-							return artist.name;
-						});
-						song.title = item.name;
-						song.explicit = item.explicit;
-						song.thumbnail = item.album.images[1].url;
-						songs.push(song);
-					}
-				}
-			}
-
-			cb(songs);
-		});
-	}
-
-	async shuffle(array) {
-		try { await this._validateHook(); } catch { return; }
-
-		let currentIndex = array.length, temporaryValue, randomIndex;
-
-		// While there remain elements to shuffle...
-		while (0 !== currentIndex) {
-
-			// Pick a remaining element...
-			randomIndex = Math.floor(Math.random() * currentIndex);
-			currentIndex -= 1;
-
-			// And swap it with the current element.
-			temporaryValue = array[currentIndex];
-			array[currentIndex] = array[randomIndex];
-			array[randomIndex] = temporaryValue;
-		}
-
-		return array;
-	}
-
-	async getError(err) {
-		try { await this._validateHook(); } catch { return; }
-
-		let error = 'An error occurred.';
-		if (typeof err === "string") error = err;
-		else if (err.message) {
-			if (err.message !== 'Validation failed') error = err.message;
-			else error = err.errors[Object.keys(err.errors)].message;
-		}
-		return error;
-	}
+class UtilsModule extends CoreClass {
+    constructor() {
+        super("utils");
+    }
+
+    initialize() {
+        return new Promise((resolve, reject) => {
+            this.io = this.moduleManager.modules["io"];
+            this.db = this.moduleManager.modules["db"];
+            this.spotify = this.moduleManager.modules["spotify"];
+            this.cache = this.moduleManager.modules["cache"];
+
+            resolve();
+        });
+    }
+
+    PARSE_COOKIES(payload) {
+        //cookieString
+        return new Promise((resolve, reject) => {
+            let cookies = {};
+            payload.cookieString.split("; ").map((cookie) => {
+                cookies[
+                    cookie.substring(0, cookie.indexOf("="))
+                ] = cookie.substring(cookie.indexOf("=") + 1, cookie.length);
+            });
+            resolve(cookies);
+        });
+    }
+
+    // COOKIES_TO_STRING() {//cookies
+    // 	return new Promise((resolve, reject) => {
+    //         let newCookie = [];
+    //         for (let prop in cookie) {
+    //             newCookie.push(prop + "=" + cookie[prop]);
+    //         }
+    //         return newCookie.join("; ");
+    //     });
+    // }
+
+    REMOVE_COOKIE(payload) {
+        //cookieString, cookieName
+        return new Promise(async (resolve, reject) => {
+            var cookies = await this.runJob("PARSE_COOKIES", {
+                cookieString: payload.cookieString,
+            });
+            delete cookies[payload.cookieName];
+            resolve(this.toString(cookies));
+        });
+    }
+
+    HTML_ENTITIES(payload) {
+        //str
+        return new Promise((resolve, reject) => {
+            resolve(
+                String(payload.str)
+                    .replace(/&/g, "&amp;")
+                    .replace(/</g, "&lt;")
+                    .replace(/>/g, "&gt;")
+                    .replace(/"/g, "&quot;")
+            );
+        });
+    }
+
+    GENERATE_RANDOM_STRING(payload) {
+        //length
+        return new Promise(async (resolve, reject) => {
+            let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(
+                ""
+            );
+            let result = [];
+            for (let i = 0; i < payload.length; i++) {
+                result.push(
+                    chars[
+                        await this.runJob("GET_RANDOM_NUMBER", {
+                            min: 0,
+                            max: chars.length - 1,
+                        })
+                    ]
+                );
+            }
+            resolve(result.join(""));
+        });
+    }
+
+    GET_SOCKET_FROM_ID(payload) {
+        //socketId
+        return new Promise(async (resolve, reject) => {
+            let io = await this.io.runJob("IO", {});
+            resolve(io.sockets.sockets[payload.socketId]);
+        });
+    }
+
+    GET_RANDOM_NUMBER(payload) {
+        //min, max
+        return new Promise((resolve, reject) => {
+            resolve(
+                Math.floor(Math.random() * (payload.max - payload.min + 1)) +
+                    payload.min
+            );
+        });
+    }
+
+    CONVERT_TIME(payload) {
+        //duration
+        return new Promise((resolve, reject) => {
+            let duration = payload.duration;
+            let a = duration.match(/\d+/g);
+
+            if (
+                duration.indexOf("M") >= 0 &&
+                duration.indexOf("H") == -1 &&
+                duration.indexOf("S") == -1
+            ) {
+                a = [0, a[0], 0];
+            }
+
+            if (duration.indexOf("H") >= 0 && duration.indexOf("M") == -1) {
+                a = [a[0], 0, a[1]];
+            }
+            if (
+                duration.indexOf("H") >= 0 &&
+                duration.indexOf("M") == -1 &&
+                duration.indexOf("S") == -1
+            ) {
+                a = [a[0], 0, 0];
+            }
+
+            duration = 0;
+
+            if (a.length == 3) {
+                duration = duration + parseInt(a[0]) * 3600;
+                duration = duration + parseInt(a[1]) * 60;
+                duration = duration + parseInt(a[2]);
+            }
+
+            if (a.length == 2) {
+                duration = duration + parseInt(a[0]) * 60;
+                duration = duration + parseInt(a[1]);
+            }
+
+            if (a.length == 1) {
+                duration = duration + parseInt(a[0]);
+            }
+
+            let hours = Math.floor(duration / 3600);
+            let minutes = Math.floor((duration % 3600) / 60);
+            let seconds = Math.floor((duration % 3600) % 60);
+
+            resolve(
+                (hours < 10 ? "0" + hours + ":" : hours + ":") +
+                    (minutes < 10 ? "0" + minutes + ":" : minutes + ":") +
+                    (seconds < 10 ? "0" + seconds : seconds)
+            );
+        });
+    }
+
+    GUID(payload) {
+        return new Promise((resolve, reject) => {
+            resolve(
+                [1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1]
+                    .map((b) =>
+                        b
+                            ? Math.floor((1 + Math.random()) * 0x10000)
+                                  .toString(16)
+                                  .substring(1)
+                            : "-"
+                    )
+                    .join("")
+            );
+        });
+    }
+
+    SOCKET_FROM_SESSION(payload) {
+        //socketId
+        return new Promise(async (resolve, reject) => {
+            let io = await this.io.runJob("IO", {});
+            let ns = io.of("/");
+            if (ns) {
+                resolve(ns.connected[payload.socketId]);
+            }
+        });
+    }
+
+    SOCKETS_FROM_SESSION_ID(payload) {
+        //sessionId, cb
+        return new Promise(async (resolve, reject) => {
+            let io = await this.io.runJob("IO", {});
+            let ns = io.of("/");
+            let sockets = [];
+            if (ns) {
+                async.each(
+                    Object.keys(ns.connected),
+                    (id, next) => {
+                        let session = ns.connected[id].session;
+                        if (session.sessionId === payload.sessionId)
+                            sockets.push(session.sessionId);
+                        next();
+                    },
+                    () => {
+                        resolve({ sockets });
+                    }
+                );
+            }
+        });
+    }
+
+    SOCKETS_FROM_USER(payload) {
+        //userId, cb
+        return new Promise(async (resolve, reject) => {
+            let io = await this.io.runJob("IO", {});
+            let ns = io.of("/");
+            let sockets = [];
+            if (ns) {
+                async.each(
+                    Object.keys(ns.connected),
+                    (id, next) => {
+                        let session = ns.connected[id].session;
+                        this.cache
+                            .runJob("HGET", {
+                                table: "sessions",
+                                key: session.sessionId,
+                            })
+                            .then((session) => {
+                                if (
+                                    session &&
+                                    session.userId === payload.userId
+                                )
+                                    sockets.push(ns.connected[id]);
+                                next();
+                            })
+                            .catch(() => {
+                                next();
+                            });
+                    },
+                    () => {
+                        resolve({ sockets });
+                    }
+                );
+            }
+        });
+    }
+
+    SOCKETS_FROM_IP(payload) {
+        //ip, cb
+        return new Promise(async (resolve, reject) => {
+            let io = await this.io.runJob("IO", {});
+            let ns = io.of("/");
+            let sockets = [];
+            if (ns) {
+                async.each(
+                    Object.keys(ns.connected),
+                    (id, next) => {
+                        let session = ns.connected[id].session;
+                        this.cache
+                            .runJob("HGET", {
+                                table: "sessions",
+                                key: session.sessionId,
+                            })
+                            .then((session) => {
+                                if (
+                                    session &&
+                                    ns.connected[id].ip === payload.ip
+                                )
+                                    sockets.push(ns.connected[id]);
+                                next();
+                            })
+                            .catch((err) => {
+                                next();
+                            });
+                    },
+                    () => {
+                        resolve({ sockets });
+                    }
+                );
+            }
+        });
+    }
+
+    SOCKETS_FROM_USER_WITHOUT_CACHE(payload) {
+        //userId, cb
+        return new Promise(async (resolve, reject) => {
+            let io = await this.io.runJob("IO", {});
+            let ns = io.of("/");
+            let sockets = [];
+            if (ns) {
+                async.each(
+                    Object.keys(ns.connected),
+                    (id, next) => {
+                        let session = ns.connected[id].session;
+                        if (session.userId === payload.userId)
+                            sockets.push(ns.connected[id]);
+                        next();
+                    },
+                    () => {
+                        resolve({ sockets });
+                    }
+                );
+            }
+        });
+    }
+
+    SOCKET_LEAVE_ROOMS(payload) {
+        //socketId
+        return new Promise(async (resolve, reject) => {
+            let socket = await this.runJob("SOCKET_FROM_SESSION", {
+                socketId: payload.socketId,
+            });
+            let rooms = socket.rooms;
+            for (let room in rooms) {
+                socket.leave(room);
+            }
+
+            resolve();
+        });
+    }
+
+    SOCKET_JOIN_ROOM(payload) {
+        //socketId, room
+        return new Promise(async (resolve, reject) => {
+            let socket = await this.runJob("SOCKET_FROM_SESSION", {
+                socketId: payload.socketId,
+            });
+            let rooms = socket.rooms;
+            for (let room in rooms) {
+                socket.leave(room);
+            }
+            socket.join(payload.room);
+            resolve();
+        });
+    }
+
+    SOCKET_JOIN_SONG_ROOM(payload) {
+        //socketId, room
+        return new Promise(async (resolve, reject) => {
+            let socket = await this.runJob("SOCKET_FROM_SESSION", {
+                socketId: payload.socketId,
+            });
+            let rooms = socket.rooms;
+            for (let room in rooms) {
+                if (room.indexOf("song.") !== -1) socket.leave(rooms);
+            }
+            socket.join(payload.room);
+            resolve();
+        });
+    }
+
+    SOCKETS_JOIN_SONG_ROOM(payload) {
+        //sockets, room
+        return new Promise((resolve, reject) => {
+            for (let id in payload.sockets) {
+                let socket = payload.sockets[id];
+                let rooms = socket.rooms;
+                for (let room in rooms) {
+                    if (room.indexOf("song.") !== -1) socket.leave(room);
+                }
+                socket.join(payload.room);
+            }
+            resolve();
+        });
+    }
+
+    SOCKETS_LEAVE_SONG_ROOMS(payload) {
+        //sockets
+        return new Promise((resolve, reject) => {
+            for (let id in payload.sockets) {
+                let socket = payload.sockets[id];
+                let rooms = socket.rooms;
+                for (let room in rooms) {
+                    if (room.indexOf("song.") !== -1) socket.leave(room);
+                }
+            }
+            resolve();
+        });
+    }
+
+    EMIT_TO_ROOM(payload) {
+        //room, ...args
+        return new Promise(async (resolve, reject) => {
+            let io = await this.io.runJob("IO", {});
+            let sockets = io.sockets.sockets;
+            for (let id in sockets) {
+                let socket = sockets[id];
+                if (socket.rooms[payload.room]) {
+                    socket.emit.apply(socket, payload.args);
+                }
+            }
+            resolve();
+        });
+    }
+
+    GET_ROOM_SOCKETS(payload) {
+        //room
+        return new Promise(async (resolve, reject) => {
+            let io = await this.io.runJob("IO", {});
+            let sockets = io.sockets.sockets;
+            let roomSockets = [];
+            for (let id in sockets) {
+                let socket = sockets[id];
+                if (socket.rooms[payload.room]) roomSockets.push(socket);
+            }
+            resolve(roomSockets);
+        });
+    }
+
+    GET_SONG_FROM_YOUTUBE(payload) {
+        //songId, cb
+        return new Promise((resolve, reject) => {
+            youtubeRequestCallbacks.push({
+                cb: (test) => {
+                    youtubeRequestsActive = true;
+                    const youtubeParams = [
+                        "part=snippet,contentDetails,statistics,status",
+                        `id=${encodeURIComponent(payload.songId)}`,
+                        `key=${config.get("apis.youtube.key")}`,
+                    ].join("&");
+
+                    request(
+                        `https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`,
+                        (err, res, body) => {
+                            youtubeRequestCallbacks.splice(0, 1);
+                            if (youtubeRequestCallbacks.length > 0) {
+                                youtubeRequestCallbacks[0].cb(
+                                    youtubeRequestCallbacks[0].songId
+                                );
+                            } else youtubeRequestsActive = false;
+
+                            if (err) {
+                                console.error(err);
+                                return null;
+                            }
+
+                            body = JSON.parse(body);
+
+                            //TODO Clean up duration converter
+                            let dur = body.items[0].contentDetails.duration;
+                            dur = dur.replace("PT", "");
+                            let duration = 0;
+                            dur = dur.replace(/([\d]*)H/, (v, v2) => {
+                                v2 = Number(v2);
+                                duration = v2 * 60 * 60;
+                                return "";
+                            });
+                            dur = dur.replace(/([\d]*)M/, (v, v2) => {
+                                v2 = Number(v2);
+                                duration += v2 * 60;
+                                return "";
+                            });
+                            dur = dur.replace(/([\d]*)S/, (v, v2) => {
+                                v2 = Number(v2);
+                                duration += v2;
+                                return "";
+                            });
+
+                            let song = {
+                                songId: body.items[0].id,
+                                title: body.items[0].snippet.title,
+                                duration,
+                            };
+                            resolve({ song });
+                        }
+                    );
+                },
+                songId: payload.songId,
+            });
+
+            if (!youtubeRequestsActive) {
+                youtubeRequestCallbacks[0].cb(
+                    youtubeRequestCallbacks[0].songId
+                );
+            }
+        });
+    }
+
+    FILTER_MUSIC_VIDEOS_YOUTUBE(payload) {
+        //videoIds, cb
+        return new Promise((resolve, reject) => {
+            function getNextPage(cb2) {
+                let localVideoIds = payload.videoIds.splice(0, 50);
+
+                const youtubeParams = [
+                    "part=topicDetails",
+                    `id=${encodeURIComponent(localVideoIds.join(","))}`,
+                    `maxResults=50`,
+                    `key=${config.get("apis.youtube.key")}`,
+                ].join("&");
+
+                request(
+                    `https://www.googleapis.com/youtube/v3/videos?${youtubeParams}`,
+                    async (err, res, body) => {
+                        if (err) {
+                            console.error(err);
+                            return next("Failed to find playlist from YouTube");
+                        }
+
+                        body = JSON.parse(body);
+
+                        let songIds = [];
+                        body.items.forEach((item) => {
+                            const songId = item.id;
+                            if (!item.topicDetails) return;
+                            else if (
+                                item.topicDetails.relevantTopicIds.indexOf(
+                                    "/m/04rlf"
+                                ) !== -1
+                            ) {
+                                songIds.push(songId);
+                            }
+                        });
+
+                        if (payload.videoIds.length > 0) {
+                            getNextPage((newSongIds) => {
+                                cb2(songIds.concat(newSongIds));
+                            });
+                        } else cb2(songIds);
+                    }
+                );
+            }
+
+            if (payload.videoIds.length === 0) resolve({ songIds: [] });
+            else
+                getNextPage((songIds) => {
+                    resolve({ songIds });
+                });
+        });
+    }
+
+    GET_PLAYLIST_FROM_YOUTUBE(payload) {
+        //url, musicOnly, cb
+        return new Promise((resolve, reject) => {
+            let local = this;
+
+            let name = "list".replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+            var regex = new RegExp("[\\?&]" + name + "=([^&#]*)");
+            let playlistId = regex.exec(payload.url)[1];
+
+            function getPage(pageToken, songs) {
+                let nextPageToken = pageToken ? `pageToken=${pageToken}` : "";
+                const youtubeParams = [
+                    "part=contentDetails",
+                    `playlistId=${encodeURIComponent(playlistId)}`,
+                    `maxResults=50`,
+                    `key=${config.get("apis.youtube.key")}`,
+                    nextPageToken,
+                ].join("&");
+
+                request(
+                    `https://www.googleapis.com/youtube/v3/playlistItems?${youtubeParams}`,
+                    async (err, res, body) => {
+                        if (err) {
+                            console.error(err);
+                            return next("Failed to find playlist from YouTube");
+                        }
+
+                        body = JSON.parse(body);
+                        songs = songs.concat(body.items);
+                        if (body.nextPageToken)
+                            getPage(body.nextPageToken, songs);
+                        else {
+                            songs = songs.map(
+                                (song) => song.contentDetails.videoId
+                            );
+                            if (!payload.musicOnly) resolve({ songs });
+                            else {
+                                local
+                                    .runJob("FILTER_MUSIC_VIDEOS_YOUTUBE", {
+                                        videoIds: songs.slice(),
+                                    })
+                                    .then((filteredSongs) => {
+                                        resolve({ filteredSongs, songs });
+                                    });
+                            }
+                        }
+                    }
+                );
+            }
+            getPage(null, []);
+        });
+    }
+
+    GET_SONG_FROM_SPOTIFY(payload) {
+        //song, cb
+        return new Promise(async (resolve, reject) => {
+            if (!config.get("apis.spotify.enabled"))
+                return reject(new Error("Spotify is not enabled."));
+
+            const song = Object.assign({}, payload.song);
+
+            const spotifyParams = [
+                `q=${encodeURIComponent(payload.song.title)}`,
+                `type=track`,
+            ].join("&");
+
+            const token = await this.spotify.runJob("GET_TOKEN", {});
+            const options = {
+                url: `https://api.spotify.com/v1/search?${spotifyParams}`,
+                headers: {
+                    Authorization: `Bearer ${token}`,
+                },
+            };
+
+            request(options, (err, res, body) => {
+                if (err) console.error(err);
+                body = JSON.parse(body);
+                if (body.error) console.error(body.error);
+
+                durationArtistLoop: for (let i in body) {
+                    let items = body[i].items;
+                    for (let j in items) {
+                        let item = items[j];
+                        let hasArtist = false;
+                        for (let k = 0; k < item.artists.length; k++) {
+                            let artist = item.artists[k];
+                            if (song.title.indexOf(artist.name) !== -1)
+                                hasArtist = true;
+                        }
+                        if (hasArtist && song.title.indexOf(item.name) !== -1) {
+                            song.duration = item.duration_ms / 1000;
+                            song.artists = item.artists.map((artist) => {
+                                return artist.name;
+                            });
+                            song.title = item.name;
+                            song.explicit = item.explicit;
+                            song.thumbnail = item.album.images[1].url;
+                            break durationArtistLoop;
+                        }
+                    }
+                }
+
+                resolve({ song });
+            });
+        });
+    }
+
+    GET_SONGS_FROM_SPOTIFY(payload) {
+        //title, artist, cb
+        return new Promise(async (resolve, reject) => {
+            if (!config.get("apis.spotify.enabled"))
+                return reject(new Error("Spotify is not enabled."));
+
+            const spotifyParams = [
+                `q=${encodeURIComponent(payload.title)}`,
+                `type=track`,
+            ].join("&");
+
+            const token = await this.spotify.runJob("GET_TOKEN", {});
+            const options = {
+                url: `https://api.spotify.com/v1/search?${spotifyParams}`,
+                headers: {
+                    Authorization: `Bearer ${token}`,
+                },
+            };
+
+            request(options, (err, res, body) => {
+                if (err) return console.error(err);
+                body = JSON.parse(body);
+                if (body.error) return console.error(body.error);
+
+                let songs = [];
+
+                for (let i in body) {
+                    let items = body[i].items;
+                    for (let j in items) {
+                        let item = items[j];
+                        let hasArtist = false;
+                        for (let k = 0; k < item.artists.length; k++) {
+                            let localArtist = item.artists[k];
+                            if (
+                                payload.artist.toLowerCase() ===
+                                localArtist.name.toLowerCase()
+                            )
+                                hasArtist = true;
+                        }
+                        if (
+                            hasArtist &&
+                            (payload.title.indexOf(item.name) !== -1 ||
+                                item.name.indexOf(payload.title) !== -1)
+                        ) {
+                            let song = {};
+                            song.duration = item.duration_ms / 1000;
+                            song.artists = item.artists.map((artist) => {
+                                return artist.name;
+                            });
+                            song.title = item.name;
+                            song.explicit = item.explicit;
+                            song.thumbnail = item.album.images[1].url;
+                            songs.push(song);
+                        }
+                    }
+                }
+
+                resolve({ songs });
+            });
+        });
+    }
+
+    SHUFFLE(payload) {
+        //array
+        return new Promise((resolve, reject) => {
+            const array = payload.array.slice();
+
+            let currentIndex = payload.array.length,
+                temporaryValue,
+                randomIndex;
+
+            // While there remain elements to shuffle...
+            while (0 !== currentIndex) {
+                // Pick a remaining element...
+                randomIndex = Math.floor(Math.random() * currentIndex);
+                currentIndex -= 1;
+
+                // And swap it with the current element.
+                temporaryValue = array[currentIndex];
+                array[currentIndex] = array[randomIndex];
+                array[randomIndex] = temporaryValue;
+            }
+
+            return array;
+        });
+    }
+
+    GET_ERROR(payload) {
+        //err
+        return new Promise((resolve, reject) => {
+            let error = "An error occurred.";
+            if (typeof payload.error === "string") error = payload.error;
+            else if (payload.error.message) {
+                if (payload.error.message !== "Validation failed")
+                    error = payload.error.message;
+                else
+                    error =
+                        payload.error.errors[Object.keys(payload.error.errors)]
+                            .message;
+            }
+            resolve(error);
+        });
+    }
+
+    CREATE_GRAVATAR(payload) {
+        //email
+        return new Promise((resolve, reject) => {
+            const hash = crypto
+                .createHash("md5")
+                .update(payload.email)
+                .digest("hex");
+
+            resolve(`https://www.gravatar.com/avatar/${hash}`);
+        });
+    }
 }
+
+module.exports = new UtilsModule();

+ 2291 - 0
backend/package-lock.json

@@ -0,0 +1,2291 @@
+{
+  "name": "musare-backend",
+  "version": "2.1.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "abbrev": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+    },
+    "accepts": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "requires": {
+        "mime-types": "~2.1.24",
+        "negotiator": "0.6.2"
+      }
+    },
+    "after": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
+      "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
+    },
+    "agent-base": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
+      "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
+      "requires": {
+        "es6-promisify": "^5.0.0"
+      }
+    },
+    "ajv": {
+      "version": "6.12.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
+      "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
+      "requires": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "ansi-regex": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+    },
+    "aproba": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
+    },
+    "are-we-there-yet": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
+      "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+      "requires": {
+        "delegates": "^1.0.0",
+        "readable-stream": "^2.0.6"
+      }
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+    },
+    "arraybuffer.slice": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
+      "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
+    },
+    "asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
+    "assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+    },
+    "ast-types": {
+      "version": "0.13.2",
+      "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.2.tgz",
+      "integrity": "sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA=="
+    },
+    "async": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/async/-/async-3.1.0.tgz",
+      "integrity": "sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ=="
+    },
+    "async-limiter": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+      "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
+    },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+    },
+    "aws-sign2": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+    },
+    "aws4": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
+      "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
+    },
+    "backo2": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
+      "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+    },
+    "base64-arraybuffer": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
+      "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
+    },
+    "base64id": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+      "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
+    },
+    "bcrypt": {
+      "version": "3.0.8",
+      "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.8.tgz",
+      "integrity": "sha512-jKV6RvLhI36TQnPDvUFqBEnGX9c8dRRygKxCZu7E+MgLfKZbmmXL8a7/SFFOyHoPNX9nV81cKRC5tbQfvEQtpw==",
+      "requires": {
+        "nan": "2.14.0",
+        "node-pre-gyp": "0.14.0"
+      }
+    },
+    "bcrypt-pbkdf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "requires": {
+        "tweetnacl": "^0.14.3"
+      },
+      "dependencies": {
+        "tweetnacl": {
+          "version": "0.14.5",
+          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+          "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+        }
+      }
+    },
+    "better-assert": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
+      "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
+      "requires": {
+        "callsite": "1.0.0"
+      }
+    },
+    "bl": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz",
+      "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==",
+      "requires": {
+        "readable-stream": "^2.3.5",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "blob": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
+      "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig=="
+    },
+    "bluebird": {
+      "version": "3.7.2",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
+    },
+    "body-parser": {
+      "version": "1.19.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+      "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+      "requires": {
+        "bytes": "3.1.0",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "on-finished": "~2.3.0",
+        "qs": "6.7.0",
+        "raw-body": "2.4.0",
+        "type-is": "~1.6.17"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "bson": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.4.tgz",
+      "integrity": "sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q=="
+    },
+    "bytes": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+      "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
+    },
+    "callsite": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
+      "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
+    },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+    },
+    "chownr": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
+    },
+    "co": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+      "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
+    },
+    "code-point-at": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
+    },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
+    "component-bind": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
+      "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
+    },
+    "component-emitter": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+      "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
+    },
+    "component-inherit": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
+      "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+    },
+    "config": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/config/-/config-3.3.1.tgz",
+      "integrity": "sha512-+2/KaaaAzdwUBE3jgZON11L1ggLLhpf2FsGrfqYFHZW22ySGv/HqYIXrBwKKvn+XZh1UBUjHwAcrfsSkSygT+Q==",
+      "requires": {
+        "json5": "^2.1.1"
+      }
+    },
+    "console-control-strings": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
+    },
+    "content-disposition": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+      "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+      "requires": {
+        "safe-buffer": "5.1.2"
+      }
+    },
+    "content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+    },
+    "convert-hex": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/convert-hex/-/convert-hex-0.1.0.tgz",
+      "integrity": "sha1-CMBFaJIsJ3drii6BqV05M2LqC2U="
+    },
+    "convert-string": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/convert-string/-/convert-string-0.1.0.tgz",
+      "integrity": "sha1-ec5BqbsNA7z3LNxqjzxW+7xkQQo="
+    },
+    "cookie": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+      "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
+    },
+    "cookie-parser": {
+      "version": "1.4.5",
+      "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
+      "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
+      "requires": {
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6"
+      },
+      "dependencies": {
+        "cookie": {
+          "version": "0.4.0",
+          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+          "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
+        }
+      }
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+    },
+    "cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "requires": {
+        "object-assign": "^4",
+        "vary": "^1"
+      }
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "data-uri-to-buffer": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz",
+      "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ=="
+    },
+    "debug": {
+      "version": "3.2.6",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+      "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+      "requires": {
+        "ms": "^2.1.1"
+      }
+    },
+    "deep-extend": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
+    },
+    "deep-is": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
+    },
+    "degenerator": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz",
+      "integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=",
+      "requires": {
+        "ast-types": "0.x.x",
+        "escodegen": "1.x.x",
+        "esprima": "3.x.x"
+      }
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+    },
+    "delegates": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
+    },
+    "denque": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
+      "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
+    },
+    "depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+    },
+    "detect-libc": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+      "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
+    },
+    "discord.js": {
+      "version": "11.6.4",
+      "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-11.6.4.tgz",
+      "integrity": "sha512-cK6rH1PuGjSjpmEQbnpuTxq1Yv8B89SotyKUFcr4RhnsiZnfBfDOev7DD7v5vhtEyyj51NuMWFoRJzgy/m08Uw==",
+      "requires": {
+        "long": "^4.0.0",
+        "prism-media": "^0.0.4",
+        "snekfetch": "^3.6.4",
+        "tweetnacl": "^1.0.0",
+        "ws": "^6.0.0"
+      }
+    },
+    "double-ended-queue": {
+      "version": "2.1.0-0",
+      "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
+      "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw="
+    },
+    "ecc-jsbn": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "requires": {
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.1.0"
+      }
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+    },
+    "engine.io": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.0.tgz",
+      "integrity": "sha512-XCyYVWzcHnK5cMz7G4VTu2W7zJS7SM1QkcelghyIk/FmobWBtXE7fwhBusEKvCSqc3bMh8fNFMlUkCKTFRxH2w==",
+      "requires": {
+        "accepts": "~1.3.4",
+        "base64id": "2.0.0",
+        "cookie": "0.3.1",
+        "debug": "~4.1.0",
+        "engine.io-parser": "~2.2.0",
+        "ws": "^7.1.2"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ws": {
+          "version": "7.2.1",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.1.tgz",
+          "integrity": "sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A=="
+        }
+      }
+    },
+    "engine.io-client": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.0.tgz",
+      "integrity": "sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==",
+      "requires": {
+        "component-emitter": "1.2.1",
+        "component-inherit": "0.0.3",
+        "debug": "~4.1.0",
+        "engine.io-parser": "~2.2.0",
+        "has-cors": "1.1.0",
+        "indexof": "0.0.1",
+        "parseqs": "0.0.5",
+        "parseuri": "0.0.5",
+        "ws": "~6.1.0",
+        "xmlhttprequest-ssl": "~1.5.4",
+        "yeast": "0.1.2"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ws": {
+          "version": "6.1.4",
+          "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
+          "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
+          "requires": {
+            "async-limiter": "~1.0.0"
+          }
+        }
+      }
+    },
+    "engine.io-parser": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz",
+      "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==",
+      "requires": {
+        "after": "0.8.2",
+        "arraybuffer.slice": "~0.0.7",
+        "base64-arraybuffer": "0.1.5",
+        "blob": "0.0.5",
+        "has-binary2": "~1.0.2"
+      }
+    },
+    "es6-promise": {
+      "version": "4.2.8",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
+    },
+    "es6-promisify": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+      "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
+      "requires": {
+        "es6-promise": "^4.0.3"
+      }
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "escodegen": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz",
+      "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==",
+      "requires": {
+        "esprima": "^4.0.1",
+        "estraverse": "^4.2.0",
+        "esutils": "^2.0.2",
+        "optionator": "^0.8.1",
+        "source-map": "~0.6.1"
+      },
+      "dependencies": {
+        "esprima": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+          "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+        }
+      }
+    },
+    "esprima": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
+      "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
+    },
+    "estraverse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
+    },
+    "esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+    },
+    "express": {
+      "version": "4.17.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+      "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+      "requires": {
+        "accepts": "~1.3.7",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.19.0",
+        "content-disposition": "0.5.3",
+        "content-type": "~1.0.4",
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.1.2",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.5",
+        "qs": "6.7.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.1.2",
+        "send": "0.17.1",
+        "serve-static": "1.14.1",
+        "setprototypeof": "1.1.1",
+        "statuses": "~1.5.0",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "dependencies": {
+        "cookie": {
+          "version": "0.4.0",
+          "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+          "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
+        },
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+    },
+    "extsprintf": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+    },
+    "fast-deep-equal": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+      "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+    },
+    "fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
+    },
+    "file-uri-to-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
+    },
+    "finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+    },
+    "form-data": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
+      "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+    },
+    "fs-minipass": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
+      "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
+      "requires": {
+        "minipass": "^2.6.0"
+      }
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+    },
+    "ftp": {
+      "version": "0.3.10",
+      "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz",
+      "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=",
+      "requires": {
+        "readable-stream": "1.1.x",
+        "xregexp": "2.0.0"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+        },
+        "readable-stream": {
+          "version": "1.1.14",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.1",
+            "isarray": "0.0.1",
+            "string_decoder": "~0.10.x"
+          }
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+        }
+      }
+    },
+    "gauge": {
+      "version": "2.7.4",
+      "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+      "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+      "requires": {
+        "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"
+      }
+    },
+    "get-uri": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.4.tgz",
+      "integrity": "sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q==",
+      "requires": {
+        "data-uri-to-buffer": "1",
+        "debug": "2",
+        "extend": "~3.0.2",
+        "file-uri-to-path": "1",
+        "ftp": "~0.3.10",
+        "readable-stream": "2"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "glob": {
+      "version": "7.1.6",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+      "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+      "requires": {
+        "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"
+      }
+    },
+    "har-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+    },
+    "har-validator": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+      "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+      "requires": {
+        "ajv": "^6.5.5",
+        "har-schema": "^2.0.0"
+      }
+    },
+    "has-binary2": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
+      "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
+      "requires": {
+        "isarray": "2.0.1"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
+          "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
+        }
+      }
+    },
+    "has-cors": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
+      "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
+    },
+    "has-unicode": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
+    },
+    "http-errors": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+      "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+      "requires": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.1.1",
+        "statuses": ">= 1.5.0 < 2",
+        "toidentifier": "1.0.0"
+      },
+      "dependencies": {
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+        }
+      }
+    },
+    "http-proxy-agent": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
+      "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
+      "requires": {
+        "agent-base": "4",
+        "debug": "3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
+    "http-signature": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "jsprim": "^1.2.2",
+        "sshpk": "^1.7.0"
+      }
+    },
+    "https-proxy-agent": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz",
+      "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==",
+      "requires": {
+        "agent-base": "^4.3.0",
+        "debug": "^3.1.0"
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "ignore-walk": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
+      "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
+      "requires": {
+        "minimatch": "^3.0.4"
+      }
+    },
+    "indexof": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
+      "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
+    },
+    "inflection": {
+      "version": "1.12.0",
+      "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz",
+      "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY="
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "ini": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
+    },
+    "ip": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
+      "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
+    },
+    "ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
+    },
+    "is-fullwidth-code-point": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+      "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+      "requires": {
+        "number-is-nan": "^1.0.0"
+      }
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+    },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+    },
+    "json5": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
+      "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
+      "requires": {
+        "minimist": "^1.2.5"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.5",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+          "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+        }
+      }
+    },
+    "jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
+    "kareem": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
+      "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw=="
+    },
+    "levn": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+      "requires": {
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2"
+      }
+    },
+    "lodash": {
+      "version": "4.17.15",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+    },
+    "long": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+      "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+    },
+    "lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "requires": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "mailgun-js": {
+      "version": "0.22.0",
+      "resolved": "https://registry.npmjs.org/mailgun-js/-/mailgun-js-0.22.0.tgz",
+      "integrity": "sha512-a2alg5nuTZA9Psa1pSEIEsbxr1Zrmqx4VkgGCQ30xVh0kIH7Bu57AYILo+0v8QLSdXtCyLaS+KVmdCrQo0uWFA==",
+      "requires": {
+        "async": "^2.6.1",
+        "debug": "^4.1.0",
+        "form-data": "^2.3.3",
+        "inflection": "~1.12.0",
+        "is-stream": "^1.1.0",
+        "path-proxy": "~1.0.0",
+        "promisify-call": "^2.0.2",
+        "proxy-agent": "^3.0.3",
+        "tsscmp": "^1.0.6"
+      },
+      "dependencies": {
+        "async": {
+          "version": "2.6.3",
+          "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+          "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+          "requires": {
+            "lodash": "^4.17.14"
+          }
+        },
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+    },
+    "memory-pager": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+      "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+      "optional": true
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+    },
+    "mime-db": {
+      "version": "1.43.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
+      "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ=="
+    },
+    "mime-types": {
+      "version": "2.1.26",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
+      "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
+      "requires": {
+        "mime-db": "1.43.0"
+      }
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+    },
+    "minipass": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
+      "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
+      "requires": {
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.0"
+      }
+    },
+    "minizlib": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
+      "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
+      "requires": {
+        "minipass": "^2.9.0"
+      }
+    },
+    "mkdirp": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+      "requires": {
+        "minimist": "^1.2.5"
+      }
+    },
+    "moment": {
+      "version": "2.24.0",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
+      "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
+    },
+    "mongodb": {
+      "version": "3.5.6",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.6.tgz",
+      "integrity": "sha512-sh3q3GLDLT4QmoDLamxtAECwC3RGjq+oNuK1ENV8+tnipIavss6sMYt77hpygqlMOCt0Sla5cl7H4SKCVBCGEg==",
+      "requires": {
+        "bl": "^2.2.0",
+        "bson": "^1.1.4",
+        "denque": "^1.4.1",
+        "require_optional": "^1.0.1",
+        "safe-buffer": "^5.1.2",
+        "saslprep": "^1.0.0"
+      }
+    },
+    "mongoose": {
+      "version": "5.9.10",
+      "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.9.10.tgz",
+      "integrity": "sha512-w1HNukfJzzDLfcI1f79h2Wj4ogVbf+X8hRkyFgqlcjK7OnDlAgahjDMIsT+mCS9jKojrMhjSsZIs9FiRPkLqMg==",
+      "requires": {
+        "bson": "^1.1.4",
+        "kareem": "2.3.1",
+        "mongodb": "3.5.6",
+        "mongoose-legacy-pluralize": "1.0.2",
+        "mpath": "0.7.0",
+        "mquery": "3.2.2",
+        "ms": "2.1.2",
+        "regexp-clone": "1.0.0",
+        "safe-buffer": "5.1.2",
+        "sift": "7.0.1",
+        "sliced": "1.0.1"
+      }
+    },
+    "mongoose-legacy-pluralize": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz",
+      "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ=="
+    },
+    "mpath": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz",
+      "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg=="
+    },
+    "mquery": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz",
+      "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==",
+      "requires": {
+        "bluebird": "3.5.1",
+        "debug": "3.1.0",
+        "regexp-clone": "^1.0.0",
+        "safe-buffer": "5.1.2",
+        "sliced": "1.0.1"
+      },
+      "dependencies": {
+        "bluebird": {
+          "version": "3.5.1",
+          "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
+          "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA=="
+        },
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
+    "ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+    },
+    "nan": {
+      "version": "2.14.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+      "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
+    },
+    "needle": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/needle/-/needle-2.3.2.tgz",
+      "integrity": "sha512-DUzITvPVDUy6vczKKYTnWc/pBZ0EnjMJnQ3y+Jo5zfKFimJs7S3HFCxCRZYB9FUZcrzUQr3WsmvZgddMEIZv6w==",
+      "requires": {
+        "debug": "^3.2.6",
+        "iconv-lite": "^0.4.4",
+        "sax": "^1.2.4"
+      }
+    },
+    "negotiator": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+    },
+    "netmask": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz",
+      "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU="
+    },
+    "node-pre-gyp": {
+      "version": "0.14.0",
+      "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz",
+      "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==",
+      "requires": {
+        "detect-libc": "^1.0.2",
+        "mkdirp": "^0.5.1",
+        "needle": "^2.2.1",
+        "nopt": "^4.0.1",
+        "npm-packlist": "^1.1.6",
+        "npmlog": "^4.0.2",
+        "rc": "^1.2.7",
+        "rimraf": "^2.6.1",
+        "semver": "^5.3.0",
+        "tar": "^4.4.2"
+      }
+    },
+    "nopt": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+      "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+      "requires": {
+        "abbrev": "1",
+        "osenv": "^0.1.4"
+      }
+    },
+    "npm-bundled": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
+      "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
+      "requires": {
+        "npm-normalize-package-bin": "^1.0.1"
+      }
+    },
+    "npm-normalize-package-bin": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
+      "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
+    },
+    "npm-packlist": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
+      "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
+      "requires": {
+        "ignore-walk": "^3.0.1",
+        "npm-bundled": "^1.0.1",
+        "npm-normalize-package-bin": "^1.0.1"
+      }
+    },
+    "npmlog": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+      "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+      "requires": {
+        "are-we-there-yet": "~1.1.2",
+        "console-control-strings": "~1.1.0",
+        "gauge": "~2.7.3",
+        "set-blocking": "~2.0.0"
+      }
+    },
+    "number-is-nan": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
+    },
+    "oauth": {
+      "version": "0.9.15",
+      "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
+      "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE="
+    },
+    "oauth-sign": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+    },
+    "object-component": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
+      "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE="
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "optionator": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+      "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+      "requires": {
+        "deep-is": "~0.1.3",
+        "fast-levenshtein": "~2.0.6",
+        "levn": "~0.3.0",
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2",
+        "word-wrap": "~1.2.3"
+      }
+    },
+    "os-homedir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
+    },
+    "os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
+    },
+    "osenv": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+      "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+      "requires": {
+        "os-homedir": "^1.0.0",
+        "os-tmpdir": "^1.0.0"
+      }
+    },
+    "pac-proxy-agent": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz",
+      "integrity": "sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ==",
+      "requires": {
+        "agent-base": "^4.2.0",
+        "debug": "^4.1.1",
+        "get-uri": "^2.0.0",
+        "http-proxy-agent": "^2.1.0",
+        "https-proxy-agent": "^3.0.0",
+        "pac-resolver": "^3.0.0",
+        "raw-body": "^2.2.0",
+        "socks-proxy-agent": "^4.0.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "pac-resolver": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz",
+      "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==",
+      "requires": {
+        "co": "^4.6.0",
+        "degenerator": "^1.0.4",
+        "ip": "^1.1.5",
+        "netmask": "^1.0.6",
+        "thunkify": "^2.1.2"
+      }
+    },
+    "parseqs": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
+      "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
+      "requires": {
+        "better-assert": "~1.0.0"
+      }
+    },
+    "parseuri": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
+      "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
+      "requires": {
+        "better-assert": "~1.0.0"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+    },
+    "path-proxy": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/path-proxy/-/path-proxy-1.0.0.tgz",
+      "integrity": "sha1-GOijaFn8nS8aU7SN7hOFQ8Ag3l4=",
+      "requires": {
+        "inflection": "~1.3.0"
+      },
+      "dependencies": {
+        "inflection": {
+          "version": "1.3.8",
+          "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.3.8.tgz",
+          "integrity": "sha1-y9Fg2p91sUw8xjV41POWeEvzAU4="
+        }
+      }
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
+    "performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+    },
+    "prelude-ls": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
+    },
+    "prism-media": {
+      "version": "0.0.4",
+      "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-0.0.4.tgz",
+      "integrity": "sha512-dG2w7WtovUa4SiYTdWn9H8Bd4JNdei2djtkP/Bk9fXq81j5Q15ZPHYSwhUVvBRbp5zMkGtu0Yk62HuMcly0pRw=="
+    },
+    "process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+    },
+    "promisify-call": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/promisify-call/-/promisify-call-2.0.4.tgz",
+      "integrity": "sha1-1IwtRWUszM1SgB3ey9UzptS9X7o=",
+      "requires": {
+        "with-callback": "^1.0.2"
+      }
+    },
+    "proxy-addr": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
+      "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
+      "requires": {
+        "forwarded": "~0.1.2",
+        "ipaddr.js": "1.9.1"
+      }
+    },
+    "proxy-agent": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.1.1.tgz",
+      "integrity": "sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw==",
+      "requires": {
+        "agent-base": "^4.2.0",
+        "debug": "4",
+        "http-proxy-agent": "^2.1.0",
+        "https-proxy-agent": "^3.0.0",
+        "lru-cache": "^5.1.1",
+        "pac-proxy-agent": "^3.0.1",
+        "proxy-from-env": "^1.0.0",
+        "socks-proxy-agent": "^4.0.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "proxy-from-env": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz",
+      "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4="
+    },
+    "psl": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz",
+      "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ=="
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+    },
+    "qs": {
+      "version": "6.7.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+      "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
+    },
+    "range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+    },
+    "raw-body": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+      "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+      "requires": {
+        "bytes": "3.1.0",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      }
+    },
+    "rc": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "requires": {
+        "deep-extend": "^0.6.0",
+        "ini": "~1.3.0",
+        "minimist": "^1.2.0",
+        "strip-json-comments": "~2.0.1"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.5",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+          "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+        }
+      }
+    },
+    "readable-stream": {
+      "version": "2.3.7",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+      "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+      "requires": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "redis": {
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz",
+      "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==",
+      "requires": {
+        "double-ended-queue": "^2.1.0-0",
+        "redis-commands": "^1.2.0",
+        "redis-parser": "^2.6.0"
+      }
+    },
+    "redis-commands": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz",
+      "integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg=="
+    },
+    "redis-parser": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz",
+      "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs="
+    },
+    "regexp-clone": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz",
+      "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw=="
+    },
+    "request": {
+      "version": "2.88.2",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+      "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+      "requires": {
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.8.0",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "form-data": "~2.3.2",
+        "har-validator": "~5.1.3",
+        "http-signature": "~1.2.0",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "~2.1.19",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.2",
+        "safe-buffer": "^5.1.2",
+        "tough-cookie": "~2.5.0",
+        "tunnel-agent": "^0.6.0",
+        "uuid": "^3.3.2"
+      },
+      "dependencies": {
+        "form-data": {
+          "version": "2.3.3",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+          "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+          "requires": {
+            "asynckit": "^0.4.0",
+            "combined-stream": "^1.0.6",
+            "mime-types": "^2.1.12"
+          }
+        },
+        "qs": {
+          "version": "6.5.2",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+          "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+        }
+      }
+    },
+    "require_optional": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz",
+      "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==",
+      "requires": {
+        "resolve-from": "^2.0.0",
+        "semver": "^5.1.0"
+      }
+    },
+    "resolve-from": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz",
+      "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c="
+    },
+    "rimraf": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+      "requires": {
+        "glob": "^7.1.3"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "saslprep": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
+      "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
+      "optional": true,
+      "requires": {
+        "sparse-bitfield": "^3.0.3"
+      }
+    },
+    "sax": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+    },
+    "semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+    },
+    "send": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+      "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "~1.7.2",
+        "mime": "1.6.0",
+        "ms": "2.1.1",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.1",
+        "statuses": "~1.5.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          },
+          "dependencies": {
+            "ms": {
+              "version": "2.0.0",
+              "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+              "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+            }
+          }
+        },
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+        }
+      }
+    },
+    "serve-static": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+      "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.17.1"
+      }
+    },
+    "set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
+    },
+    "setprototypeof": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+      "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
+    },
+    "sha256": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/sha256/-/sha256-0.2.0.tgz",
+      "integrity": "sha1-c6C0GNqrcDW/+G6EkeNjQS/CqwU=",
+      "requires": {
+        "convert-hex": "~0.1.0",
+        "convert-string": "~0.1.0"
+      }
+    },
+    "sift": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz",
+      "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g=="
+    },
+    "signal-exit": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+    },
+    "sliced": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
+      "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E="
+    },
+    "smart-buffer": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz",
+      "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw=="
+    },
+    "snekfetch": {
+      "version": "3.6.4",
+      "resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz",
+      "integrity": "sha512-NjxjITIj04Ffqid5lqr7XdgwM7X61c/Dns073Ly170bPQHLm6jkmelye/eglS++1nfTWktpP6Y2bFXjdPlQqdw=="
+    },
+    "socket.io": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz",
+      "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==",
+      "requires": {
+        "debug": "~4.1.0",
+        "engine.io": "~3.4.0",
+        "has-binary2": "~1.0.2",
+        "socket.io-adapter": "~1.1.0",
+        "socket.io-client": "2.3.0",
+        "socket.io-parser": "~3.4.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "socket.io-adapter": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz",
+      "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g=="
+    },
+    "socket.io-client": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz",
+      "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==",
+      "requires": {
+        "backo2": "1.0.2",
+        "base64-arraybuffer": "0.1.5",
+        "component-bind": "1.0.0",
+        "component-emitter": "1.2.1",
+        "debug": "~4.1.0",
+        "engine.io-client": "~3.4.0",
+        "has-binary2": "~1.0.2",
+        "has-cors": "1.1.0",
+        "indexof": "0.0.1",
+        "object-component": "0.0.3",
+        "parseqs": "0.0.5",
+        "parseuri": "0.0.5",
+        "socket.io-parser": "~3.3.0",
+        "to-array": "0.1.4"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "isarray": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
+          "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
+        },
+        "socket.io-parser": {
+          "version": "3.3.0",
+          "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz",
+          "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==",
+          "requires": {
+            "component-emitter": "1.2.1",
+            "debug": "~3.1.0",
+            "isarray": "2.0.1"
+          },
+          "dependencies": {
+            "debug": {
+              "version": "3.1.0",
+              "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+              "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+              "requires": {
+                "ms": "2.0.0"
+              }
+            },
+            "ms": {
+              "version": "2.0.0",
+              "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+              "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+            }
+          }
+        }
+      }
+    },
+    "socket.io-parser": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.0.tgz",
+      "integrity": "sha512-/G/VOI+3DBp0+DJKW4KesGnQkQPFmUCbA/oO2QGT6CWxU7hLGWqU3tyuzeSK/dqcyeHsQg1vTe9jiZI8GU9SCQ==",
+      "requires": {
+        "component-emitter": "1.2.1",
+        "debug": "~4.1.0",
+        "isarray": "2.0.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "isarray": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
+          "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
+        }
+      }
+    },
+    "socks": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz",
+      "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==",
+      "requires": {
+        "ip": "1.1.5",
+        "smart-buffer": "^4.1.0"
+      }
+    },
+    "socks-proxy-agent": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz",
+      "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==",
+      "requires": {
+        "agent-base": "~4.2.1",
+        "socks": "~2.3.2"
+      },
+      "dependencies": {
+        "agent-base": {
+          "version": "4.2.1",
+          "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
+          "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
+          "requires": {
+            "es6-promisify": "^5.0.0"
+          }
+        }
+      }
+    },
+    "source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "optional": true
+    },
+    "sparse-bitfield": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+      "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=",
+      "optional": true,
+      "requires": {
+        "memory-pager": "^1.0.2"
+      }
+    },
+    "sshpk": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+      "requires": {
+        "asn1": "~0.2.3",
+        "assert-plus": "^1.0.0",
+        "bcrypt-pbkdf": "^1.0.0",
+        "dashdash": "^1.12.0",
+        "ecc-jsbn": "~0.1.1",
+        "getpass": "^0.1.1",
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.0.2",
+        "tweetnacl": "~0.14.0"
+      },
+      "dependencies": {
+        "tweetnacl": {
+          "version": "0.14.5",
+          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+          "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+        }
+      }
+    },
+    "statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+    },
+    "string-width": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+      "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+      "requires": {
+        "code-point-at": "^1.0.0",
+        "is-fullwidth-code-point": "^1.0.0",
+        "strip-ansi": "^3.0.0"
+      }
+    },
+    "string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "strip-ansi": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+      "requires": {
+        "ansi-regex": "^2.0.0"
+      }
+    },
+    "strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
+    },
+    "tar": {
+      "version": "4.4.13",
+      "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
+      "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
+      "requires": {
+        "chownr": "^1.1.1",
+        "fs-minipass": "^1.2.5",
+        "minipass": "^2.8.6",
+        "minizlib": "^1.2.1",
+        "mkdirp": "^0.5.0",
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.3"
+      }
+    },
+    "thunkify": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz",
+      "integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0="
+    },
+    "to-array": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
+      "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
+    },
+    "toidentifier": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+      "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
+    },
+    "tough-cookie": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+      "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+      "requires": {
+        "psl": "^1.1.28",
+        "punycode": "^2.1.1"
+      }
+    },
+    "tsscmp": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
+      "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="
+    },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "tweetnacl": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
+      "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
+    },
+    "type-check": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+      "requires": {
+        "prelude-ls": "~1.1.2"
+      }
+    },
+    "type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      }
+    },
+    "underscore": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz",
+      "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg=="
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+    },
+    "uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+    },
+    "uuid": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+    },
+    "verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    },
+    "wide-align": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+      "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+      "requires": {
+        "string-width": "^1.0.2 || 2"
+      }
+    },
+    "with-callback": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/with-callback/-/with-callback-1.0.2.tgz",
+      "integrity": "sha1-oJYpuakgAo1yFAT7Q1vc/1yRvCE="
+    },
+    "word-wrap": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+    },
+    "ws": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
+      "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
+      "requires": {
+        "async-limiter": "~1.0.0"
+      }
+    },
+    "xmlhttprequest-ssl": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
+      "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
+    },
+    "xregexp": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz",
+      "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM="
+    },
+    "yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+    },
+    "yeast": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
+      "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
+    }
+  }
+}

+ 5 - 5
backend/package.json

@@ -19,20 +19,20 @@
     "bcrypt": "^3.0.6",
     "bluebird": "^3.5.5",
     "body-parser": "^1.19.0",
-    "config": "^3.2.0",
-    "cookie-parser": "^1.4.4",
+    "config": "^3.3.1",
+    "cookie-parser": "^1.4.5",
     "cors": "^2.8.5",
-    "discord.js": "^11.5.1",
+    "discord.js": "^11.6.4",
     "express": "^4.17.1",
     "mailgun-js": "^0.22.0",
     "moment": "^2.24.0",
-    "mongoose": "^5.6.4",
+    "mongoose": "^5.9.10",
     "oauth": "^0.9.15",
     "redis": "^2.8.0",
     "request": "^2.88.0",
     "sha256": "^0.2.0",
     "socket.io": "^2.2.0",
-    "underscore": "^1.9.1",
+    "underscore": "^1.10.2"
     "snyk": "^1.230.5"
   },
   "snyk": true

+ 1 - 2
frontend/.prettierignore

@@ -1,3 +1,2 @@
 node_modules/
-build/
-yarn.lock
+build/

+ 90 - 21
frontend/App.vue

@@ -1,12 +1,8 @@
 <template>
-	<div>
+	<div class="upper-container">
 		<banned v-if="banned" />
-		<div v-else>
-			<h1 v-if="!socketConnected" class="alert">
-				Could not connect to the server.
-			</h1>
-			<!-- should be a persistant toast -->
-			<router-view />
+		<div v-else class="upper-container">
+			<router-view :key="$route.fullPath" class="main-container" />
 			<what-is-new />
 			<mobile-alert />
 			<login-modal v-if="modals.header.login" />
@@ -17,7 +13,6 @@
 
 <script>
 import { mapState, mapActions } from "vuex";
-
 import Toast from "toasters";
 
 import Banned from "./components/pages/Banned.vue";
@@ -26,13 +21,15 @@ import MobileAlert from "./components/Modals/MobileAlert.vue";
 import LoginModal from "./components/Modals/Login.vue";
 import RegisterModal from "./components/Modals/Register.vue";
 import io from "./io";
+import keyboardShortcuts from "./keyboardShortcuts";
 
 export default {
 	replace: false,
 	data() {
 		return {
 			serverDomain: "",
-			socketConnected: true
+			socketConnected: true,
+			keyIsDown: false
 		};
 	},
 	computed: mapState({
@@ -42,33 +39,89 @@ export default {
 		userId: state => state.user.auth.userId,
 		banned: state => state.user.auth.banned,
 		modals: state => state.modals.modals,
-		currentlyActive: state => state.modals.currentlyActive
+		currentlyActive: state => state.modals.currentlyActive,
+		nightmode: state => state.user.preferences.nightmode
 	}),
 	methods: {
 		submitOnEnter: (cb, event) => {
 			if (event.which === 13) cb();
 		},
-		...mapActions("modals", ["closeCurrentModal"])
-	},
-	beforeMount() {
-		const nightmode =
-			false || JSON.parse(localStorage.getItem("nightmode"));
-		if (nightmode) {
+		enableNightMode: () => {
 			document
 				.getElementsByTagName("body")[0]
 				.classList.add("night-mode");
+		},
+		disableNightMode: () => {
+			document
+				.getElementsByTagName("body")[0]
+				.classList.remove("night-mode");
+		},
+		...mapActions("modals", ["closeCurrentModal"]),
+		...mapActions("user/preferences", ["changeNightmode"])
+	},
+	watch: {
+		socketConnected: connected => {
+			console.log(connected);
+			if (!connected)
+				new Toast({
+					content: "Could not connect to the server.",
+					persistant: true
+				});
+			else {
+				// better implementation once vue-roaster is updated
+				document
+					.getElementById("toasts-content")
+					.childNodes.forEach(toast => {
+						if (
+							toast.innerHTML ===
+							"Could not connect to the server."
+						) {
+							toast.remove();
+						}
+					});
+			}
+		},
+		nightmode(nightmode) {
+			if (nightmode) this.enableNightMode();
+			else this.disableNightMode();
 		}
 	},
+	beforeMount() {
+		const nightmode =
+			false || JSON.parse(localStorage.getItem("nightmode"));
+		this.changeNightmode(nightmode);
+		if (nightmode) this.enableNightMode();
+		else this.disableNightMode();
+	},
 	mounted() {
 		document.onkeydown = ev => {
 			const event = ev || window.event;
-			if (
-				event.keyCode === 27 &&
-				Object.keys(this.currentlyActive).length !== 0
-			)
-				this.closeCurrentModal();
+			const { keyCode } = event;
+			const shift = event.shiftKey;
+			const ctrl = event.ctrlKey;
+
+			const identifier = `${keyCode}.${shift}.${ctrl}`;
+
+			if (this.keyIsDown === identifier) return;
+			this.keyIsDown = identifier;
+
+			keyboardShortcuts.handleKeyDown(keyCode, shift, ctrl);
 		};
 
+		document.onkeyup = () => {
+			this.keyIsDown = "";
+		};
+
+		keyboardShortcuts.registerShortcut("closeModal", {
+			keyCode: 27,
+			shift: false,
+			ctrl: false,
+			handler: () => {
+				if (Object.keys(this.currentlyActive).length !== 0)
+					this.closeCurrentModal();
+			}
+		});
+
 		if (localStorage.getItem("github_redirect")) {
 			this.$router.go(localStorage.getItem("github_redirect"));
 			localStorage.removeItem("github_redirect");
@@ -155,12 +208,28 @@ body.night-mode {
 
 html {
 	overflow: auto !important;
+	height: 100%;
 }
 
 body {
 	background-color: $light-grey;
 	color: $dark-grey;
 	font-family: "Roboto", Helvetica, Arial, sans-serif;
+	height: 100%;
+}
+
+.upper-container {
+	height: 100%;
+}
+
+.main-container {
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+
+	> .container {
+		flex: 1 0 auto;
+	}
 }
 
 a {

+ 5 - 7
frontend/Dockerfile

@@ -3,18 +3,16 @@ FROM node:12
 RUN apt-get update
 RUN apt-get install nginx -y
 
-RUN npm install -g yarn
-
-RUN yarn global add snyk
-RUN yarn global add webpack@4.35.3
-RUN yarn global add webpack-cli@3.3.5
-RUN yarn global add webpack-dev-server@3.7.2
+RUN npm install -g snyk
+RUN npm install -g webpack@4.35.3
+RUN npm install -g webpack-cli@3.3.5
+RUN npm install -g webpack-dev-server@3.7.2
 
 RUN mkdir -p /opt
 WORKDIR /opt
 ADD package.json /opt/package.json
 
-RUN yarn install
+RUN npm install
 
 RUN mkdir -p /run/nginx
 

+ 7 - 0
frontend/api/admin/index.js

@@ -0,0 +1,7 @@
+import reports from "./reports";
+
+// when Vuex needs to interact with socket.io
+
+export default {
+	reports
+};

+ 17 - 0
frontend/api/admin/reports.js

@@ -0,0 +1,17 @@
+import Toast from "toasters";
+import io from "../../io";
+
+export default {
+	resolve(reportId) {
+		return new Promise((resolve, reject) => {
+			io.getSocket(socket => {
+				socket.emit("reports.resolve", reportId, res => {
+					new Toast({ content: res.message, timeout: 3000 });
+					if (res.status === "success")
+						return resolve({ status: "success" });
+					return reject(new Error(res.message));
+				});
+			});
+		});
+	}
+};

+ 8 - 5
frontend/api/auth.js

@@ -33,7 +33,10 @@ export default {
 										cookie.domain
 									}; ${secure}path=/`;
 
-									return resolve({ status: "success" });
+									return resolve({
+										status: "success",
+										message: "Account registered!"
+									});
 								});
 							}
 							return reject(new Error("You must login"));
@@ -77,15 +80,15 @@ export default {
 	logout() {
 		return new Promise((resolve, reject) => {
 			io.getSocket(socket => {
-				socket.emit("users.logout", result => {
-					if (result.status === "success") {
+				socket.emit("users.logout", res => {
+					if (res.status === "success") {
 						return lofig.get("cookie").then(cookie => {
 							document.cookie = `${cookie.SIDname}=;expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
 							return window.location.reload();
 						});
 					}
-					new Toast({ content: result.message, timeout: 4000 });
-					return reject(new Error(result.message));
+					new Toast({ content: res.message, timeout: 4000 });
+					return reject(new Error(res.message));
 				});
 			});
 		});

+ 2 - 2
frontend/bootstrap.sh

@@ -1,9 +1,9 @@
 #!/bin/bash
 
 if [ "$FRONTEND_MODE" == "prod" ] ; then
-	cd /opt/app ; yarn run $FRONTEND_MODE
+	cd /opt/app ; npm run $FRONTEND_MODE
 	nginx -c /opt/app/$FRONTEND_MODE.nginx.conf -g "daemon off;"
 elif [ "$FRONTEND_MODE" == "dev" ] ; then
 	nginx -c /opt/app/$FRONTEND_MODE.nginx.conf
-	cd /opt/app; yarn run $FRONTEND_MODE
+	cd /opt/app; npm run $FRONTEND_MODE
 fi

+ 12 - 9
frontend/components/Admin/Reports.vue

@@ -129,17 +129,20 @@ export default {
 			this.openModal({ sector: "admin", modal: "viewReport" });
 		},
 		resolve(reportId) {
-			this.socket.emit("reports.resolve", reportId, res => {
-				new Toast({ content: res.message, timeout: 3000 });
-				if (res.status === "success" && this.modals.viewReport)
-					this.closeModal({
-						sector: "admin",
-						modal: "viewReport"
-					});
-			});
+			return this.resolveReport(reportId)
+				.then(res => {
+					if (res.status === "success" && this.modals.viewReport)
+						this.closeModal({
+							sector: "admin",
+							modal: "viewReport"
+						});
+				})
+				.catch(
+					err => new Toast({ content: err.message, timeout: 5000 })
+				);
 		},
 		...mapActions("modals", ["openModal", "closeModal"]),
-		...mapActions("admin/reports", ["viewReport"])
+		...mapActions("admin/reports", ["viewReport", "resolveReport"])
 	}
 };
 </script>

+ 13 - 7
frontend/components/Admin/Stations.vue

@@ -194,7 +194,6 @@ export default {
 	components: { EditStation, UserIdToUsername },
 	data() {
 		return {
-			stations: [],
 			newStation: {
 				genres: [],
 				blacklistedGenres: []
@@ -202,6 +201,9 @@ export default {
 		};
 	},
 	computed: {
+		...mapState("admin/stations", {
+			stations: state => state.stations
+		}),
 		...mapState("modals", {
 			modals: state => state.modals.station
 		})
@@ -329,24 +331,28 @@ export default {
 		},
 		init() {
 			this.socket.emit("stations.index", data => {
-				this.stations = data.stations;
+				this.loadStations(data.stations);
 			});
 			this.socket.emit("apis.joinAdminRoom", "stations", () => {});
 		},
 		...mapActions("modals", ["openModal"]),
-		...mapActions("admin/stations", ["editStation"])
+		...mapActions("admin/stations", [
+			"editStation",
+			"loadStations",
+			"stationRemoved",
+			"stationAdded"
+		])
 	},
 	mounted() {
 		io.getSocket(socket => {
 			this.socket = socket;
 			if (this.socket.connected) this.init();
+
 			this.socket.on("event:admin.station.added", station => {
-				this.stations.push(station);
+				this.stationAdded(station);
 			});
 			this.socket.on("event:admin.station.removed", stationId => {
-				this.stations = this.stations.filter(station => {
-					return station._id !== stationId;
-				});
+				this.stationRemoved(stationId);
 			});
 			io.onConnect(() => {
 				this.init();

+ 9 - 1
frontend/components/Admin/Users.vue

@@ -88,7 +88,15 @@ export default {
 		},
 		init() {
 			this.socket.emit("users.index", result => {
-				if (result.status === "success") this.users = result.data;
+				if (result.status === "success") {
+					this.users = result.data;
+					if (this.$route.query.userId) {
+						const user = this.users.find(
+							user => user._id === this.$route.query.userId
+						);
+						if (user) this.edit(user);
+					}
+				}
 			});
 			this.socket.emit("apis.joinAdminRoom", "users", () => {});
 		},

+ 2 - 5
frontend/components/MainFooter.vue

@@ -54,7 +54,7 @@
 					</router-link>
 				</p>
 				<p>
-					© Copyright Musare 2015 - 2019
+					© Copyright Musare 2015 - 2020
 				</p>
 			</div>
 		</div>
@@ -107,10 +107,7 @@ export default {
 }
 
 .footer {
-	position: absolute;
-	right: 0;
-	bottom: 0;
-	left: 0;
+	flex-shrink: 0;
 	height: 240px;
 	padding: 40px 20px 40px;
 	border-radius: 33% 33% 0% 0% / 7% 7% 0% 0%;

+ 1 - 0
frontend/components/MainHeader.vue

@@ -121,6 +121,7 @@ export default {
 }
 
 .nav {
+	flex-shrink: 0;
 	background-color: $primary-color;
 	height: 64px;
 	border-radius: 0% 0% 33% 33% / 0% 0% 7% 7%;

+ 1 - 0
frontend/components/Modals/AddSongToPlaylist.vue

@@ -78,6 +78,7 @@ export default {
 		addSongToPlaylist(playlistId) {
 			this.socket.emit(
 				"playlists.addSongToPlaylist",
+				false,
 				this.currentSong.songId,
 				playlistId,
 				res => {

+ 9 - 13
frontend/components/Modals/AddSongToQueue.vue

@@ -4,12 +4,9 @@
 			<aside class="menu" v-if="loggedIn && station.type === 'community'">
 				<ul class="menu-list">
 					<li v-for="(playlist, index) in playlists" :key="index">
-						<a
-							href="#"
-							target="_blank"
-							v-on:click="editPlaylist(playlist._id)"
-							>{{ playlist.displayName }}</a
-						>
+						<a href="#" v-on:click="editPlaylist(playlist._id)">{{
+							playlist.displayName
+						}}</a>
 						<div class="controls">
 							<a
 								href="#"
@@ -104,13 +101,14 @@ export default {
 			querySearch: "",
 			queryResults: [],
 			playlists: [],
-			privatePlaylistQueueSelected: null,
 			importQuery: ""
 		};
 	},
 	computed: mapState({
 		loggedIn: state => state.user.auth.loggedIn,
-		station: state => state.station.station
+		station: state => state.station.station,
+		privatePlaylistQueueSelected: state =>
+			state.station.privatePlaylistQueueSelected
 	}),
 	methods: {
 		isPlaylistSelected(playlistId) {
@@ -118,15 +116,13 @@ export default {
 		},
 		selectPlaylist(playlistId) {
 			if (this.station.type === "community") {
-				this.privatePlaylistQueueSelected = playlistId;
-				this.$parent.privatePlaylistQueueSelected = playlistId;
+				this.updatePrivatePlaylistQueueSelected(playlistId);
 				this.$parent.addFirstPrivatePlaylistSongToQueue();
 			}
 		},
 		unSelectPlaylist() {
 			if (this.station.type === "community") {
-				this.privatePlaylistQueueSelected = null;
-				this.$parent.privatePlaylistQueueSelected = null;
+				this.updatePrivatePlaylistQueueSelected(null);
 			}
 		},
 		addSongToQueue(songId) {
@@ -204,6 +200,7 @@ export default {
 				}
 			});
 		},
+		...mapActions("station", ["updatePrivatePlaylistQueueSelected"]),
 		...mapActions("user/playlists", ["editPlaylist"])
 	},
 	mounted() {
@@ -212,7 +209,6 @@ export default {
 			this.socket.emit("playlists.indexForUser", res => {
 				if (res.status === "success") this.playlists = res.data;
 			});
-			this.privatePlaylistQueueSelected = this.$parent.privatePlaylistQueueSelected;
 		});
 	},
 	components: { Modal }

+ 12 - 1
frontend/components/Modals/CreateCommunityStation.vue

@@ -6,7 +6,7 @@
 			<p class="control">
 				<input
 					v-model="newCommunity.name"
-					class="input"
+					class="input station-id"
 					type="text"
 					placeholder="Name..."
 					autofocus
@@ -64,6 +64,7 @@ export default {
 	},
 	methods: {
 		submitModal() {
+			this.newCommunity.name = this.newCommunity.name.toLowerCase();
 			const { name, displayName, description } = this.newCommunity;
 
 			if (!name || !displayName || !description)
@@ -143,3 +144,13 @@ export default {
 	}
 };
 </script>
+
+<style lang="scss" scoped>
+.station-id {
+	text-transform: lowercase;
+
+	&::placeholder {
+		text-transform: none;
+	}
+}
+</style>

+ 2 - 4
frontend/components/Modals/EditSong.vue

@@ -1068,10 +1068,8 @@ export default {
 				this.genreHelper.pos3 = e.clientX;
 				this.genreHelper.pos4 = e.clientY;
 				// set the element's new position:
-				this.genreHelper.top =
-					this.genreHelper.top - this.genreHelper.pos2;
-				this.genreHelper.left =
-					this.genreHelper.left - this.genreHelper.pos1;
+				this.genreHelper.top -= this.genreHelper.pos2;
+				this.genreHelper.left -= this.genreHelper.pos1;
 			};
 
 			document.onmouseup = () => {

+ 31 - 24
frontend/components/Modals/EditStation.vue

@@ -301,16 +301,23 @@ import io from "../../io";
 import validation from "../../validation";
 
 export default {
-	computed: mapState({
-		editing(state) {
-			return this.$props.store.split("/").reduce((a, v) => a[v], state)
-				.editing;
-		},
-		station(state) {
-			return this.$props.store.split("/").reduce((a, v) => a[v], state)
-				.station;
-		}
-	}),
+	computed: {
+		...mapState("admin/station", {
+			stations: state => state.stations
+		}),
+		...mapState({
+			editing(state) {
+				return this.$props.store
+					.split("/")
+					.reduce((a, v) => a[v], state).editing;
+			},
+			station(state) {
+				return this.$props.store
+					.split("/")
+					.reduce((a, v) => a[v], state).station;
+			}
+		})
+	},
 	mounted() {
 		io.getSocket(socket => {
 			this.socket = socket;
@@ -422,9 +429,9 @@ export default {
 					if (res.status === "success") {
 						if (this.station) this.station.name = name;
 						else {
-							this.$parent.stations.forEach((station, index) => {
+							this.stations.forEach((station, index) => {
 								if (station._id === this.editing._id) {
-									this.$parent.stations[index].name = name;
+									this.stations[index].name = name;
 									return name;
 								}
 
@@ -461,9 +468,9 @@ export default {
 						if (this.station)
 							this.station.displayName = displayName;
 						else {
-							this.$parent.stations.forEach((station, index) => {
+							this.stations.forEach((station, index) => {
 								if (station._id === this.editing._id) {
-									this.$parent.stations[
+									this.stations[
 										index
 									].displayName = displayName;
 									return displayName;
@@ -506,9 +513,9 @@ export default {
 						if (this.station)
 							this.station.description = description;
 						else {
-							this.$parent.stations.forEach((station, index) => {
+							this.stations.forEach((station, index) => {
 								if (station._id === this.editing._id) {
-									this.$parent.stations[
+									this.stations[
 										index
 									].description = description;
 									return description;
@@ -543,9 +550,9 @@ export default {
 						if (this.station)
 							this.station.privacy = this.editing.privacy;
 						else {
-							this.$parent.stations.forEach((station, index) => {
+							this.stations.forEach((station, index) => {
 								if (station._id === this.editing._id) {
-									this.$parent.stations[
+									this.stations[
 										index
 									].privacy = this.editing.privacy;
 									return this.editing.privacy;
@@ -575,9 +582,9 @@ export default {
 							JSON.stringify(this.editing.genres)
 						);
 						if (this.station) this.station.genres = genres;
-						this.$parent.stations.forEach((station, index) => {
+						this.stations.forEach((station, index) => {
 							if (station._id === this.editing._id) {
-								this.$parent.stations[index].genres = genres;
+								this.stations[index].genres = genres;
 								return genres;
 							}
 
@@ -606,9 +613,9 @@ export default {
 						);
 						if (this.station)
 							this.station.blacklistedGenres = blacklistedGenres;
-						this.$parent.stations.forEach((station, index) => {
+						this.stations.forEach((station, index) => {
 							if (station._id === this.editing._id) {
-								this.$parent.stations[
+								this.stations[
 									index
 								].blacklistedGenres = blacklistedGenres;
 								return blacklistedGenres;
@@ -642,9 +649,9 @@ export default {
 							this.station.partyMode = this.editing.partyMode;
 						// if (this.station)
 						// 	this.station.partyMode = this.editing.partyMode;
-						// this.$parent.stations.forEach((station, index) => {
+						// this.stations.forEach((station, index) => {
 						// 	if (station._id === this.editing._id) {
-						// 		this.$parent.stations[
+						// 		this.stations[
 						// 			index
 						// 		].partyMode = this.editing.partyMode;
 						// 		return this.editing.partyMode;

+ 16 - 5
frontend/components/Modals/IssuesModal.vue

@@ -62,11 +62,7 @@
 			</table>
 		</div>
 		<div slot="footer">
-			<a
-				class="button is-primary"
-				href="#"
-				@click="$parent.resolve(report._id)"
-			>
+			<a class="button is-primary" href="#" @click="resolve(report._id)">
 				<span>Resolve</span>
 			</a>
 			<a
@@ -95,6 +91,7 @@
 <script>
 import { mapActions, mapState } from "vuex";
 import { formatDistance } from "date-fns";
+import Toast from "toasters";
 
 import UserIdToUsername from "../UserIdToUsername.vue";
 import Modal from "./Modal.vue";
@@ -112,6 +109,20 @@ export default {
 	},
 	methods: {
 		formatDistance,
+		resolve(reportId) {
+			return this.resolveReport(reportId)
+				.then(res => {
+					if (res.status === "success")
+						this.closeModal({
+							sector: "admin",
+							modal: "viewReport"
+						});
+				})
+				.catch(
+					err => new Toast({ content: err.message, timeout: 5000 })
+				);
+		},
+		...mapActions("admin/reports", ["resolveReport"]),
 		...mapActions("modals", ["closeModal"])
 	},
 	components: { Modal, UserIdToUsername }

+ 75 - 53
frontend/components/Modals/Playlists/Edit.vue

@@ -57,7 +57,7 @@
 			<div class="control is-grouped">
 				<p class="control is-expanded">
 					<input
-						v-model="songQuery"
+						v-model="searchSongQuery"
 						class="input"
 						type="text"
 						placeholder="Search for Song to add"
@@ -92,6 +92,23 @@
 					</tr>
 				</tbody>
 			</table>
+			<div class="control is-grouped">
+				<p class="control is-expanded">
+					<input
+						v-model="directSongQuery"
+						class="input"
+						type="text"
+						placeholder="Enter a YouTube id or URL directly"
+						autofocus
+						@keyup.enter="addSong()"
+					/>
+				</p>
+				<p class="control">
+					<a class="button is-info" @click="addSong()" href="#"
+						>Add</a
+					>
+				</p>
+			</div>
 			<div class="control is-grouped">
 				<p class="control is-expanded">
 					<input
@@ -99,15 +116,27 @@
 						class="input"
 						type="text"
 						placeholder="YouTube Playlist URL"
-						@keyup.enter="importPlaylist()"
+						@keyup.enter="importPlaylist(false)"
 					/>
 				</p>
 				<p class="control">
-					<a class="button is-info" @click="importPlaylist()" href="#"
-						>Import</a
+					<a
+						class="button is-info"
+						@click="importPlaylist(true)"
+						href="#"
+						>Import music</a
+					>
+				</p>
+				<p class="control">
+					<a
+						class="button is-info"
+						@click="importPlaylist(false)"
+						href="#"
+						>Import all</a
 					>
 				</p>
 			</div>
+			<button class="button is-info" @click="shuffle()">Shuffle</button>
 			<h5>Edit playlist details:</h5>
 			<div class="control is-grouped">
 				<p class="control is-expanded">
@@ -141,14 +170,17 @@ import Toast from "toasters";
 import Modal from "../Modal.vue";
 import io from "../../../io";
 import validation from "../../../validation";
+import utils from "../../../js/utils";
 
 export default {
 	components: { Modal },
 	data() {
 		return {
+			utils,
 			playlist: { songs: [] },
 			songQueryResults: [],
-			songQuery: "",
+			searchSongQuery: "",
+			directSongQuery: "",
 			importQuery: ""
 		};
 	},
@@ -201,58 +233,15 @@ export default {
 		});
 	},
 	methods: {
-		formatTime(duration) {
-			if (duration <= 0) return "0 seconds";
-
-			const hours = Math.floor(duration / (60 * 60));
-			const formatHours = () => {
-				if (hours > 0) {
-					if (hours > 1) {
-						if (hours < 10) return `0${hours} hours `;
-						return `${hours} hours `;
-					}
-					return `0${hours} hour `;
-				}
-				return "";
-			};
-
-			const minutes = Math.floor((duration - hours * 60 * 60) / 60);
-			const formatMinutes = () => {
-				if (minutes > 0) {
-					if (minutes > 1) {
-						if (minutes < 10) return `0${minutes} minutes `;
-						return `${minutes} minutes `;
-					}
-					return `0${minutes} minute `;
-				}
-				return "";
-			};
-
-			const seconds = Math.floor(
-				duration - hours * 60 * 60 - minutes * 60
-			);
-			const formatSeconds = () => {
-				if (seconds > 0) {
-					if (seconds > 1) {
-						if (seconds < 10) return `0${seconds} seconds `;
-						return `${seconds} seconds `;
-					}
-					return `0${seconds} second `;
-				}
-				return "";
-			};
-
-			return formatHours() + formatMinutes() + formatSeconds();
-		},
 		totalLength() {
 			let length = 0;
 			this.playlist.songs.forEach(song => {
 				length += song.duration;
 			});
-			return this.formatTime(length);
+			return this.utils.formatTimeLong(length);
 		},
 		searchForSongs() {
-			let query = this.songQuery;
+			let query = this.searchSongQuery;
 			if (query.indexOf("&index=") !== -1) {
 				query = query.split("&index=");
 				query.pop();
@@ -282,6 +271,7 @@ export default {
 		addSongToPlaylist(id) {
 			this.socket.emit(
 				"playlists.addSongToPlaylist",
+				false,
 				id,
 				this.playlist._id,
 				res => {
@@ -289,7 +279,28 @@ export default {
 				}
 			);
 		},
-		importPlaylist() {
+		/* eslint-disable prefer-destructuring */
+		addSong() {
+			let id = "";
+
+			if (this.directSongQuery.length === 11) id = this.directSongQuery;
+			else {
+				const match = this.directSongQuery.match("v=([0-9A-Za-z_-]+)");
+				if (match.length > 0) id = match[1];
+			}
+
+			this.addSongToPlaylist(id);
+		},
+		/* eslint-enable prefer-destructuring */
+		shuffle() {
+			this.socket.emit("playlists.shuffle", this.playlist._id, res => {
+				new Toast({ content: res.message, timeout: 4000 });
+				if (res.status === "success") {
+					this.playlist = res.data;
+				}
+			});
+		},
+		importPlaylist(musicOnly) {
 			new Toast({
 				content:
 					"Starting to import your playlist. This can take some time to do.",
@@ -299,10 +310,21 @@ export default {
 				"playlists.addSetToPlaylist",
 				this.importQuery,
 				this.playlist._id,
+				musicOnly,
 				res => {
-					if (res.status === "success")
-						this.playlist.songs = res.data;
 					new Toast({ content: res.message, timeout: 4000 });
+					if (res.status === "success") {
+						new Toast({
+							content: `Successfully added ${res.stats.songsAddedSuccessfully} songs. Failed to add ${res.stats.songsFailedToAdd} songs.`,
+							timeout: 4000
+						});
+						if (musicOnly) {
+							new Toast({
+								content: `${res.stats.songsInPlaylistTotal} of the ${res.stats.videosInPlaylistTotal} videos in the playlist were songs.`,
+								timeout: 4000
+							});
+						}
+					}
 				}
 			);
 		},

+ 116 - 10
frontend/components/Modals/Register.vue

@@ -29,32 +29,59 @@
 				<label class="label">Email</label>
 				<p class="control">
 					<input
-						v-model="email"
+						v-model="email.value"
 						class="input"
 						type="email"
 						placeholder="Email..."
+						@blur="onInputBlur('email')"
 						autofocus
 					/>
 				</p>
+				<p
+					class="help"
+					v-if="email.entered"
+					:class="email.valid ? 'is-success' : 'is-danger'"
+				>
+					{{ email.message }}
+				</p>
+				<br />
 				<label class="label">Username</label>
 				<p class="control">
 					<input
-						v-model="username"
+						v-model="username.value"
 						class="input"
 						type="text"
 						placeholder="Username..."
+						@blur="onInputBlur('username')"
 					/>
 				</p>
+				<p
+					class="help"
+					v-if="username.entered"
+					:class="username.valid ? 'is-success' : 'is-danger'"
+				>
+					{{ username.message }}
+				</p>
+				<br />
 				<label class="label">Password</label>
 				<p class="control">
 					<input
-						v-model="password"
+						v-model="password.value"
 						class="input"
 						type="password"
 						placeholder="Password..."
+						@blur="onInputBlur('password')"
 						@keypress="$parent.submitOnEnter(submitModal, $event)"
 					/>
 				</p>
+				<p
+					class="help"
+					v-if="password.entered"
+					:class="password.valid ? 'is-success' : 'is-danger'"
+				>
+					{{ password.message }}
+				</p>
+				<br />
 				<p>
 					By logging in/registering you agree to our
 					<router-link to="/terms"> Terms of Service </router-link
@@ -86,12 +113,29 @@ import { mapActions } from "vuex";
 
 import Toast from "toasters";
 
+import validation from "../../validation";
+
 export default {
 	data() {
 		return {
-			username: "",
-			email: "",
-			password: "",
+			username: {
+				value: "",
+				valid: false,
+				entered: false,
+				message: "Please enter a valid username."
+			},
+			email: {
+				value: "",
+				valid: false,
+				entered: false,
+				message: "Please enter a valid email address."
+			},
+			password: {
+				value: "",
+				valid: false,
+				entered: false,
+				message: "Please enter a valid password."
+			},
 			recaptcha: {
 				key: "",
 				token: ""
@@ -99,6 +143,55 @@ export default {
 			serverDomain: ""
 		};
 	},
+	watch: {
+		// eslint-disable-next-line func-names
+		"username.value": function(value) {
+			if (!validation.isLength(value, 2, 32)) {
+				this.username.message =
+					"Username must have between 2 and 32 characters.";
+				this.username.valid = false;
+			} else if (!validation.regex.azAZ09_.test(value)) {
+				this.username.message =
+					"Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _.";
+				this.username.valid = false;
+			} else {
+				this.username.message = "Everything looks great!";
+				this.username.valid = true;
+			}
+		},
+		// eslint-disable-next-line func-names
+		"email.value": function(value) {
+			if (!validation.isLength(value, 3, 254)) {
+				this.email.message =
+					"Email must have between 3 and 254 characters.";
+				this.email.valid = false;
+			} else if (
+				value.indexOf("@") !== value.lastIndexOf("@") ||
+				!validation.regex.emailSimple.test(value)
+			) {
+				this.email.message = "Invalid Email format.";
+				this.email.valid = false;
+			} else {
+				this.email.message = "Everything looks great!";
+				this.email.valid = true;
+			}
+		},
+		// eslint-disable-next-line func-names
+		"password.value": function(value) {
+			if (!validation.isLength(value, 6, 200)) {
+				this.password.message =
+					"Password must have between 6 and 200 characters.";
+				this.password.valid = false;
+			} else if (!validation.regex.password.test(value)) {
+				this.password.message =
+					"Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character.";
+				this.password.valid = false;
+			} else {
+				this.password.message = "Everything looks great!";
+				this.password.valid = true;
+			}
+		}
+	},
 	mounted() {
 		lofig.get("serverDomain").then(serverDomain => {
 			this.serverDomain = serverDomain;
@@ -127,10 +220,20 @@ export default {
 	},
 	methods: {
 		submitModal() {
-			this.register({
-				username: this.username,
-				email: this.email,
-				password: this.password,
+			if (
+				!this.username.valid ||
+				!this.email.valid ||
+				!this.password.valid
+			)
+				return new Toast({
+					content: "Please ensure all fields are valid.",
+					timeout: 5000
+				});
+
+			return this.register({
+				username: this.username.value,
+				email: this.email.value,
+				password: this.password.value,
 				recaptchaToken: this.recaptcha.token
 			})
 				.then(res => {
@@ -140,6 +243,9 @@ export default {
 					err => new Toast({ content: err.message, timeout: 5000 })
 				);
 		},
+		onInputBlur(inputName) {
+			this[inputName].entered = true;
+		},
 		githubRedirect() {
 			localStorage.setItem("github_redirect", this.$route.path);
 		},

+ 9 - 10
frontend/components/Modals/Report.vue

@@ -118,7 +118,6 @@
 							class="textarea"
 							maxlength="400"
 							placeholder="Any other details..."
-							@keyup="updateCharactersRemaining()"
 						/>
 						<div class="textarea-counter">
 							{{ charactersRemaining }}
@@ -159,7 +158,6 @@ export default {
 	components: { Modal },
 	data() {
 		return {
-			charactersRemaining: 400,
 			isPreviousSongActive: false,
 			isCurrentSongActive: true,
 			report: {
@@ -207,10 +205,15 @@ export default {
 			]
 		};
 	},
-	computed: mapState({
-		currentSong: state => state.station.currentSong,
-		previousSong: state => state.station.previousSong
-	}),
+	computed: {
+		charactersRemaining() {
+			return 400 - this.report.description.length;
+		},
+		...mapState({
+			currentSong: state => state.station.currentSong,
+			previousSong: state => state.station.previousSong
+		})
+	},
 	mounted() {
 		io.getSocket(socket => {
 			this.socket = socket;
@@ -230,10 +233,6 @@ export default {
 					});
 			});
 		},
-		updateCharactersRemaining() {
-			this.charactersRemaining =
-				400 - document.getElementsByClassName("textarea").value.length;
-		},
 		highlight(type) {
 			if (type === "currentSong") {
 				this.report.songId = this.currentSong.songId;

+ 7 - 9
frontend/components/Sidebars/SongsList.vue

@@ -28,19 +28,15 @@
 					</div>
 				</div>
 				<div class="media-right">
-					{{ $parent.formatTime(currentSong.duration) }}
+					{{ utils.formatTime(currentSong.duration) }}
 				</div>
 			</article>
 			<p v-if="noSong" class="center">
 				There is currently no song playing.
 			</p>
+			<hr v-if="noSong" />
 
-			<article
-				v-else
-				v-for="(song, index) in songsList"
-				:key="index"
-				class="media"
-			>
+			<article v-for="song in songsList" :key="song.songId" class="media">
 				<div class="media-content">
 					<div
 						class="content"
@@ -74,7 +70,7 @@
 					</div>
 				</div>
 				<div class="media-right">
-					{{ $parent.formatTime(song.duration) }}
+					{{ utils.formatTime(song.duration) }}
 				</div>
 			</article>
 			<div
@@ -127,14 +123,16 @@
 
 <script>
 import { mapState, mapActions } from "vuex";
-
 import Toast from "toasters";
 
+import utils from "../../js/utils";
+
 import UserIdToUsername from "../UserIdToUsername.vue";
 
 export default {
 	data() {
 		return {
+			utils,
 			dismissedWarning: false
 		};
 	},

+ 177 - 94
frontend/components/Station/Station.vue

@@ -62,7 +62,12 @@
 					<a
 						href="#"
 						class="no-song"
-						@click="sidebars.playlist = true"
+						@click="
+							toggleSidebar({
+								sector: 'station',
+								sidebar: 'playlist'
+							})
+						"
 						>Play a private playlist</a
 					>
 				</h4>
@@ -131,7 +136,7 @@
 							</div>
 						</div>
 						<div class="media-right">
-							{{ formatTime(currentSong.duration) }}
+							{{ utils.formatTime(currentSong.duration) }}
 						</div>
 					</article>
 					<p v-if="noSong" class="center">
@@ -173,7 +178,7 @@
 							</div>
 						</div>
 						<div class="media-right">
-							{{ formatTime(song.duration) }}
+							{{ utils.formatTime(song.duration) }}
 						</div>
 					</article>
 					<a
@@ -198,7 +203,7 @@
 						<div class="column is-12-desktop">
 							<h4 id="time-display">
 								{{ timeElapsed }} /
-								{{ formatTime(currentSong.duration) }}
+								{{ utils.formatTime(currentSong.duration) }}
 							</h4>
 							<h3>{{ currentSong.title }}</h3>
 							<h4 class="thin" style="margin-left: 0">
@@ -224,11 +229,11 @@
 											>volume_down</i
 										>
 										<input
-											id="volumeSlider"
+											v-model="volumeSliderValue"
 											type="range"
 											min="0"
 											max="10000"
-											class="active"
+											class="volumeSlider active"
 											@change="changeVolume()"
 											@input="changeVolume()"
 										/>
@@ -321,7 +326,7 @@
 							</h4>
 							<h5>
 								{{ timeElapsed }} /
-								{{ formatTime(currentSong.duration) }}
+								{{ utils.formatTime(currentSong.duration) }}
 							</h5>
 							<div>
 								<form class="columns" action="#">
@@ -341,11 +346,11 @@
 											>volume_down</i
 										>
 										<input
-											id="volumeSlider"
+											v-model="volumeSliderValue"
 											type="range"
 											min="0"
 											max="10000"
-											class="active"
+											class="active volumeSlider"
 											@change="changeVolume()"
 											@input="changeVolume()"
 										/>
@@ -430,10 +435,13 @@ import UserIdToUsername from "../UserIdToUsername.vue";
 import Z404 from "../404.vue";
 
 import io from "../../io";
+import keyboardShortcuts from "../../keyboardShortcuts";
+import utils from "../../js/utils";
 
 export default {
 	data() {
 		return {
+			utils,
 			title: "Station",
 			loading: true,
 			ready: false,
@@ -445,33 +453,33 @@ export default {
 			timeElapsed: "0:00",
 			liked: false,
 			disliked: false,
-			sidebars: {
-				songslist: false,
-				users: false,
-				playlist: false
-			},
 			timeBeforePause: 0,
 			skipVotes: 0,
-			privatePlaylistQueueSelected: null,
 			automaticallyRequestedSongId: null,
 			systemDifference: 0,
 			attemptsToPlayVideo: 0,
 			canAutoplay: true,
 			lastTimeRequestedIfCanAutoplay: 0,
 			seeking: false,
-			playbackRate: 1
+			playbackRate: 1,
+			volumeSliderValue: 0
 		};
 	},
 	computed: {
 		...mapState("modals", {
 			modals: state => state.modals.station
 		}),
+		...mapState("sidebars", {
+			sidebars: state => state.sidebars.station
+		}),
 		...mapState("station", {
 			station: state => state.station,
 			currentSong: state => state.currentSong,
 			songsList: state => state.songsList,
 			paused: state => state.paused,
-			noSong: state => state.noSong
+			noSong: state => state.noSong,
+			privatePlaylistQueueSelected: state =>
+				state.privatePlaylistQueueSelected
 		}),
 		...mapState({
 			loggedIn: state => state.user.auth.loggedIn,
@@ -486,6 +494,9 @@ export default {
 		isAdminOnly() {
 			return this.loggedIn && this.role === "admin";
 		},
+		isOwnerOrAdmin() {
+			return this.isOwnerOnly() || this.isAdminOnly();
+		},
 		removeFromQueue(songId) {
 			window.socket.emit(
 				"stations.removeFromQueue",
@@ -502,12 +513,6 @@ export default {
 				}
 			);
 		},
-		toggleSidebar(type) {
-			Object.keys(this.sidebars).forEach(sidebar => {
-				if (sidebar !== type) this.sidebars[sidebar] = false;
-				else this.sidebars[type] = !this.sidebars[type];
-			});
-		},
 		youtubeReady() {
 			if (!this.player) {
 				this.player = new window.YT.Player("player", {
@@ -626,33 +631,6 @@ export default {
 				)}%`;
 			}
 		},
-		formatTime(originalDuration) {
-			if (originalDuration) {
-				if (originalDuration < 0) return "0:00";
-
-				let duration = originalDuration;
-				let hours = Math.floor(duration / (60 * 60));
-				duration -= hours * 60 * 60;
-				let minutes = Math.floor(duration / 60);
-				duration -= minutes * 60;
-				let seconds = Math.floor(duration);
-
-				if (hours === 0) {
-					hours = "";
-				}
-
-				if (hours > 0) {
-					if (minutes < 10) minutes = `0${minutes}`;
-				}
-
-				if (seconds < 10) {
-					seconds = `0${seconds}`;
-				}
-
-				return `${hours}${hours ? ":" : ""}${minutes}:${seconds}`;
-			}
-			return false;
-		},
 		calculateTimeElapsed() {
 			if (
 				this.playerReady &&
@@ -748,7 +726,7 @@ export default {
 			const songDuration = this.currentSong.duration;
 			if (songDuration <= duration) this.player.pauseVideo();
 			if (!this.paused && duration <= songDuration)
-				this.timeElapsed = this.formatTime(duration);
+				this.timeElapsed = utils.formatTime(duration);
 		},
 		toggleLock() {
 			window.socket.emit("stations.toggleLock", this.station._id, res => {
@@ -761,7 +739,7 @@ export default {
 			});
 		},
 		changeVolume() {
-			const volume = document.getElementById("volumeSlider").value;
+			const volume = this.volumeSliderValue;
 			localStorage.setItem("volume", volume / 100);
 			if (this.playerReady) {
 				this.player.setVolume(volume / 100);
@@ -857,7 +835,7 @@ export default {
 					this.player.getVolume() * 100 <= 0 ? previousVolume : 0;
 				this.muted = !this.muted;
 				localStorage.setItem("muted", this.muted);
-				document.getElementById("volumeSlider").value = volume * 100;
+				this.volumeSliderValue = volume * 100;
 				this.player.setVolume(volume);
 				if (!this.muted) localStorage.setItem("volume", volume);
 			}
@@ -871,7 +849,7 @@ export default {
 					localStorage.setItem("muted", false);
 				}
 				if (volume > 100) volume = 100;
-				document.getElementById("volumeSlider").value = volume * 100;
+				this.volumeSliderValue = volume * 100;
 				this.player.setVolume(volume);
 				localStorage.setItem("volume", volume);
 			}
@@ -943,47 +921,58 @@ export default {
 						this.privatePlaylistQueueSelected,
 						data => {
 							if (data.status === "success") {
-								if (data.song.duration < 15 * 60) {
-									this.automaticallyRequestedSongId =
-										data.song.songId;
-									this.socket.emit(
-										"stations.addToQueue",
-										this.station._id,
-										data.song.songId,
-										data2 => {
-											if (data2.status === "success") {
-												this.socket.emit(
-													"playlists.moveSongToBottom",
-													this
-														.privatePlaylistQueueSelected,
-													data.song.songId,
-													data3 => {
-														if (
-															data3.status ===
-															"success"
-														) {} // eslint-disable-line
-													}
-												);
+								if (data.song) {
+									if (data.song.duration < 15 * 60) {
+										this.automaticallyRequestedSongId =
+											data.song.songId;
+										this.socket.emit(
+											"stations.addToQueue",
+											this.station._id,
+											data.song.songId,
+											data2 => {
+												if (
+													data2.status === "success"
+												) {
+													this.socket.emit(
+														"playlists.moveSongToBottom",
+														this
+															.privatePlaylistQueueSelected,
+														data.song.songId,
+														data3 => {
+															if (
+																data3.status ===
+																"success"
+															) {} // eslint-disable-line
+														}
+													);
+												}
+											}
+										);
+									} else {
+										new Toast({
+											content: `Top song in playlist was too long to be added.`,
+											timeout: 3000
+										});
+										this.socket.emit(
+											"playlists.moveSongToBottom",
+											this.privatePlaylistQueueSelected,
+											data.song.songId,
+											data3 => {
+												if (
+													data3.status === "success"
+												) {
+													setTimeout(() => {
+														this.addFirstPrivatePlaylistSongToQueue();
+													}, 3000);
+												}
 											}
-										}
-									);
+										);
+									}
 								} else {
 									new Toast({
-										content: `Top song in playlist was too long to be added.`,
-										timeout: 3000
+										content: `Selected playlist has no songs.`,
+										timeout: 4000
 									});
-									this.socket.emit(
-										"playlists.moveSongToBottom",
-										this.privatePlaylistQueueSelected,
-										data.song.songId,
-										data3 => {
-											if (data3.status === "success") {
-												setTimeout(() => {
-													this.addFirstPrivatePlaylistSongToQueue();
-												}, 3000);
-											}
-										}
-									);
 								}
 							}
 						}
@@ -1059,6 +1048,85 @@ export default {
 						});
 					}
 
+					if (this.isOwnerOrAdmin()) {
+						keyboardShortcuts.registerShortcut(
+							"station.pauseResume",
+							{
+								keyCode: 32,
+								shift: false,
+								ctrl: true,
+								handler: () => {
+									if (this.paused) this.resumeStation();
+									else this.pauseStation();
+								}
+							}
+						);
+
+						keyboardShortcuts.registerShortcut(
+							"station.skipStation",
+							{
+								keyCode: 39,
+								shift: false,
+								ctrl: true,
+								handler: () => {
+									this.skipStation();
+								}
+							}
+						);
+					}
+
+					keyboardShortcuts.registerShortcut(
+						"station.lowerVolumeLarge",
+						{
+							keyCode: 40,
+							shift: false,
+							ctrl: true,
+							handler: () => {
+								this.volumeSliderValue -= 1000;
+								this.changeVolume();
+							}
+						}
+					);
+
+					keyboardShortcuts.registerShortcut(
+						"station.lowerVolumeSmall",
+						{
+							keyCode: 40,
+							shift: true,
+							ctrl: true,
+							handler: () => {
+								this.volumeSliderValue -= 100;
+								this.changeVolume();
+							}
+						}
+					);
+
+					keyboardShortcuts.registerShortcut(
+						"station.increaseVolumeLarge",
+						{
+							keyCode: 38,
+							shift: false,
+							ctrl: true,
+							handler: () => {
+								this.volumeSliderValue += 1000;
+								this.changeVolume();
+							}
+						}
+					);
+
+					keyboardShortcuts.registerShortcut(
+						"station.increaseVolumeSmall",
+						{
+							keyCode: 38,
+							shift: true,
+							ctrl: true,
+							handler: () => {
+								this.volumeSliderValue += 100;
+								this.changeVolume();
+							}
+						}
+					);
+
 					// UNIX client time before ping
 					const beforePing = Date.now();
 					this.socket.emit("apis.ping", pong => {
@@ -1086,6 +1154,7 @@ export default {
 				}
 			});
 		},
+		...mapActions("sidebars", ["toggleSidebar"]),
 		...mapActions("modals", ["openModal"]),
 		...mapActions("station", [
 			"joinStation",
@@ -1271,7 +1340,7 @@ export default {
 		if (JSON.parse(localStorage.getItem("muted"))) {
 			this.muted = true;
 			this.player.setVolume(0);
-			document.getElementById("volumeSlider").value = 0 * 100;
+			this.volumeSliderValue = 0 * 100;
 		} else {
 			let volume = parseFloat(localStorage.getItem("volume"));
 			volume =
@@ -1279,9 +1348,23 @@ export default {
 					? volume
 					: 20;
 			localStorage.setItem("volume", volume);
-			document.getElementById("volumeSlider").value = volume * 100;
+			this.volumeSliderValue = volume * 100;
 		}
 	},
+	beforeDestroy() {
+		const shortcutNames = [
+			"station.pauseResume",
+			"station.skipStation",
+			"station.lowerVolumeLarge",
+			"station.lowerVolumeSmall",
+			"station.increaseVolumeLarge",
+			"station.increaseVolumeSmall"
+		];
+
+		shortcutNames.forEach(shortcutName => {
+			keyboardShortcuts.unregisterShortcut(shortcutName);
+		});
+	},
 	components: {
 		StationHeader,
 		SongQueue: () => import("../Modals/AddSongToQueue.vue"),
@@ -1339,7 +1422,7 @@ export default {
 	text-align: center;
 }
 
-#volumeSlider {
+.volumeSlider {
 	padding: 0 15px;
 	background: transparent;
 }

+ 17 - 4
frontend/components/Station/StationHeader.vue

@@ -118,7 +118,7 @@
 						<span class="icon-purpose">Add song to queue</span>
 					</a>
 					<a
-						v-if="!isOwner() && !noSong"
+						v-if="!noSong"
 						class="sidebar-item"
 						href="#"
 						@click="$parent.voteSkipStation()"
@@ -174,7 +174,12 @@
 					"
 					class="sidebar-item"
 					href="#"
-					@click="$parent.toggleSidebar('songslist')"
+					@click="
+						toggleSidebar({
+							sector: 'station',
+							sidebar: 'songslist'
+						})
+					"
 				>
 					<span class="icon">
 						<i class="material-icons">queue_music</i>
@@ -184,7 +189,9 @@
 				<a
 					class="sidebar-item"
 					href="#"
-					@click="$parent.toggleSidebar('users')"
+					@click="
+						toggleSidebar({ sector: 'station', sidebar: 'users' })
+					"
 				>
 					<span class="icon">
 						<i class="material-icons">people</i>
@@ -197,7 +204,12 @@
 					v-if="loggedIn && station.type === 'community'"
 					class="sidebar-item"
 					href="#"
-					@click="$parent.toggleSidebar('playlist')"
+					@click="
+						toggleSidebar({
+							sector: 'station',
+							sidebar: 'playlist'
+						})
+					"
 				>
 					<span class="icon">
 						<i class="material-icons">library_music</i>
@@ -268,6 +280,7 @@ export default {
 			});
 		},
 		...mapActions("modals", ["openModal"]),
+		...mapActions("sidebars", ["toggleSidebar"]),
 		...mapActions("station", ["editStation"]),
 		...mapActions("user/auth", ["logout"])
 	}

+ 473 - 148
frontend/components/User/Settings.vue

@@ -3,151 +3,228 @@
 		<metadata title="Settings" />
 		<main-header />
 		<div class="container">
-			<!--Implement Validation-->
-			<label class="label">Username</label>
-			<div class="control is-grouped">
-				<p class="control is-expanded has-icon has-icon-right">
+			<div class="nav-links">
+				<router-link
+					:class="{ active: activeTab === 'profile' }"
+					to="#profile"
+				>
+					Profile
+				</router-link>
+				<router-link
+					:class="{ active: activeTab === 'account' }"
+					to="#account"
+				>
+					Account
+				</router-link>
+				<router-link
+					:class="{ active: activeTab === 'security' }"
+					to="#security"
+				>
+					Security
+				</router-link>
+				<router-link
+					:class="{ active: activeTab === 'preferences' }"
+					to="#preferences"
+				>
+					Preferences
+				</router-link>
+			</div>
+			<div class="content profile-tab" v-if="activeTab === 'profile'">
+				<p class="control is-expanded">
+					<label for="name">Name</label>
 					<input
-						v-model="user.username"
 						class="input"
+						id="name"
 						type="text"
-						placeholder="Change username"
+						placeholder="Name"
+						v-model="user.name"
 					/>
-					<!--Remove validation if it's their own without changing-->
-				</p>
-				<p class="control">
-					<button class="button is-success" @click="changeUsername()">
-						Save changes
-					</button>
 				</p>
-			</div>
-			<label class="label">Email</label>
-			<div v-if="user.email" class="control is-grouped">
-				<p class="control is-expanded has-icon has-icon-right">
+				<p class="control is-expanded">
+					<label for="location">Location</label>
 					<input
-						v-model="user.email.address"
 						class="input"
+						id="location"
 						type="text"
-						placeholder="Change email address"
+						placeholder="Location"
+						v-model="user.location"
 					/>
-					<!--Remove validation if it's their own without changing-->
 				</p>
 				<p class="control is-expanded">
-					<button class="button is-success" @click="changeEmail()">
-						Save changes
-					</button>
+					<label for="bio">Bio</label>
+					<textarea
+						class="textarea"
+						id="bio"
+						placeholder="Bio"
+						v-model="user.bio"
+					/>
 				</p>
+				<div class="control is-expanded avatar-select">
+					<label>Avatar</label>
+					<div class="select">
+						<select v-if="user.avatar" v-model="user.avatar.type">
+							<option value="gravatar">Using Gravatar</option>
+							<option value="initials">Based on initials</option>
+						</select>
+					</div>
+				</div>
+				<button
+					class="button is-primary"
+					@click="saveChangesToProfile()"
+				>
+					Save changes
+				</button>
 			</div>
-			<label v-if="password" class="label">Change Password</label>
-			<div v-if="password" class="control is-grouped">
-				<p class="control is-expanded has-icon has-icon-right">
+			<div class="content account-tab" v-if="activeTab === 'account'">
+				<p class="control is-expanded">
+					<label for="name">Username</label>
 					<input
-						v-model="newPassword"
 						class="input"
-						type="password"
-						placeholder="Change password"
+						id="username"
+						type="text"
+						placeholder="Username"
+						v-model="user.username"
 					/>
 				</p>
 				<p class="control is-expanded">
-					<button class="button is-success" @click="changePassword()">
-						Change password
-					</button>
+					<label for="location">Email</label>
+					<input
+						class="input"
+						id="email"
+						type="text"
+						placeholder="Email"
+						v-if="user.email"
+						v-model="user.email.address"
+					/>
 				</p>
+				<button
+					class="button is-primary"
+					@click="saveChangesToAccount()"
+				>
+					Save changes
+				</button>
 			</div>
+			<div class="content security-tab" v-if="activeTab === 'security'">
+				<label v-if="!password" class="label">Add password</label>
+				<div v-if="!password" class="control is-grouped">
+					<button
+						v-if="passwordStep === 1"
+						class="button is-success"
+						@click="requestPassword()"
+					>
+						Request password email
+					</button>
+					<br />
+
+					<p
+						v-if="passwordStep === 2"
+						class="control is-expanded has-icon has-icon-right"
+					>
+						<input
+							v-model="passwordCode"
+							class="input"
+							type="text"
+							placeholder="Code"
+						/>
+					</p>
+					<p v-if="passwordStep === 2" class="control is-expanded">
+						<button
+							class="button is-success"
+							v-on:click="verifyCode()"
+						>
+							Verify code
+						</button>
+					</p>
+
+					<p
+						v-if="passwordStep === 3"
+						class="control is-expanded has-icon has-icon-right"
+					>
+						<input
+							v-model="setNewPassword"
+							class="input"
+							type="password"
+							placeholder="New password"
+						/>
+					</p>
+					<p v-if="passwordStep === 3" class="control is-expanded">
+						<button
+							class="button is-success"
+							@click="setPassword()"
+						>
+							Set password
+						</button>
+					</p>
+				</div>
+				<a
+					v-if="passwordStep === 1 && !password"
+					href="#"
+					@click="passwordStep = 2"
+					>Skip this step</a
+				>
 
-			<label v-if="!password" class="label">Add password</label>
-			<div v-if="!password" class="control is-grouped">
+				<a
+					v-if="!github"
+					class="button is-github"
+					:href="`${serverDomain}/auth/github/link`"
+				>
+					<div class="icon">
+						<img class="invert" src="/assets/social/github.svg" />
+					</div>
+					&nbsp; Link GitHub to account
+				</a>
 				<button
-					v-if="passwordStep === 1"
-					class="button is-success"
-					@click="requestPassword()"
+					v-if="password && github"
+					class="button is-danger"
+					@click="unlinkPassword()"
 				>
-					Request password email
+					Remove logging in with password
+				</button>
+				<button
+					v-if="password && github"
+					class="button is-danger"
+					@click="unlinkGitHub()"
+				>
+					Remove logging in with GitHub
 				</button>
 				<br />
-
-				<p
-					v-if="passwordStep === 2"
-					class="control is-expanded has-icon has-icon-right"
+				<button
+					class="button is-warning"
+					style="margin-top: 30px;"
+					@click="removeSessions()"
 				>
+					Log out everywhere
+				</button>
+			</div>
+			<div
+				class="content preferences-tab"
+				v-if="activeTab === 'preferences'"
+			>
+				<p class="control is-expanded checkbox-control">
 					<input
-						v-model="passwordCode"
-						class="input"
-						type="text"
-						placeholder="Code"
+						type="checkbox"
+						id="nightmode"
+						v-model="localNightmode"
 					/>
+					<label for="nightmode">
+						<span></span>
+						<p>Use nightmode</p>
+					</label>
 				</p>
-				<p v-if="passwordStep === 2" class="control is-expanded">
-					<button class="button is-success" v-on:click="verifyCode()">
-						Verify code
-					</button>
-				</p>
-
-				<p
-					v-if="passwordStep === 3"
-					class="control is-expanded has-icon has-icon-right"
+				<button
+					class="button is-primary"
+					@click="saveChangesPreferences()"
 				>
-					<input
-						v-model="setNewPassword"
-						class="input"
-						type="password"
-						placeholder="New password"
-					/>
-				</p>
-				<p v-if="passwordStep === 3" class="control is-expanded">
-					<button class="button is-success" @click="setPassword()">
-						Set password
-					</button>
-				</p>
+					Save changes
+				</button>
 			</div>
-			<a
-				v-if="passwordStep === 1 && !password"
-				href="#"
-				@click="passwordStep = 2"
-				>Skip this step</a
-			>
-
-			<a
-				v-if="!github"
-				class="button is-github"
-				:href="`${serverDomain}/auth/github/link`"
-			>
-				<div class="icon">
-					<img class="invert" src="/assets/social/github.svg" />
-				</div>
-				&nbsp; Link GitHub to account
-			</a>
-
-			<button
-				v-if="password && github"
-				class="button is-danger"
-				@click="unlinkPassword()"
-			>
-				Remove logging in with password
-			</button>
-			<button
-				v-if="password && github"
-				class="button is-danger"
-				@click="unlinkGitHub()"
-			>
-				Remove logging in with GitHub
-			</button>
-
-			<br />
-			<button
-				class="button is-warning"
-				style="margin-top: 30px;"
-				@click="removeSessions()"
-			>
-				Log out everywhere
-			</button>
 		</div>
 		<main-footer />
 	</div>
 </template>
 
 <script>
-import { mapState } from "vuex";
+import { mapState, mapActions } from "vuex";
 
 import Toast from "toasters";
 
@@ -162,52 +239,87 @@ export default {
 	data() {
 		return {
 			user: {},
+			originalUser: {},
 			newPassword: "",
 			password: false,
 			github: false,
 			setNewPassword: "",
 			passwordStep: 1,
 			passwordCode: "",
-			serverDomain: ""
+			serverDomain: "",
+			activeTab: "",
+			localNightmode: false
 		};
 	},
 	computed: mapState({
-		userId: state => state.user.auth.userId
+		userId: state => state.user.auth.userId,
+		nightmode: state => state.user.preferences.nightmode
 	}),
 	mounted() {
-		lofig.get("serverDomain").then(serverDomain => {
-			this.serverDomain = serverDomain;
-		});
+		if (this.$route.hash === "") {
+			this.$router.push("#profile");
+		} else {
+			this.activeTab = this.$route.hash.replace("#", "");
+			this.localNightmode = this.nightmode;
 
-		io.getSocket(socket => {
-			this.socket = socket;
-			this.socket.emit("users.findBySession", res => {
-				if (res.status === "success") {
-					this.user = res.data;
-					this.password = this.user.password;
-					this.github = this.user.github;
-				} else {
-					new Toast({
-						content: "Your are currently not signed in",
-						timeout: 3000
-					});
-				}
-			});
-			this.socket.on("event:user.linkPassword", () => {
-				this.password = true;
-			});
-			this.socket.on("event:user.linkGitHub", () => {
-				this.github = true;
-			});
-			this.socket.on("event:user.unlinkPassword", () => {
-				this.password = false;
+			lofig.get("serverDomain").then(serverDomain => {
+				this.serverDomain = serverDomain;
 			});
-			this.socket.on("event:user.unlinkGitHub", () => {
-				this.github = false;
+
+			io.getSocket(socket => {
+				this.socket = socket;
+				this.socket.emit("users.findBySession", res => {
+					if (res.status === "success") {
+						this.user = res.data;
+						this.originalUser = JSON.parse(
+							JSON.stringify(this.user)
+						);
+						this.password = this.user.password;
+						this.github = this.user.github;
+					} else {
+						new Toast({
+							content: "Your are currently not signed in",
+							timeout: 3000
+						});
+					}
+				});
+				this.socket.on("event:user.linkPassword", () => {
+					this.password = true;
+				});
+				this.socket.on("event:user.linkGitHub", () => {
+					this.github = true;
+				});
+				this.socket.on("event:user.unlinkPassword", () => {
+					this.password = false;
+				});
+				this.socket.on("event:user.unlinkGitHub", () => {
+					this.github = false;
+				});
 			});
-		});
+		}
 	},
 	methods: {
+		// switchTab(tabName) {
+		// 	this.activeTab = tabName;
+		// },
+		saveChangesToProfile() {
+			if (this.user.name !== this.originalUser.name) this.changeName();
+			if (this.user.location !== this.originalUser.location)
+				this.changeLocation();
+			if (this.user.bio !== this.originalUser.bio) this.changeBio();
+			if (this.user.avatar.type !== this.originalUser.avatar.type)
+				this.changeAvatarType();
+		},
+		saveChangesToAccount() {
+			if (this.user.username !== this.originalUser.username)
+				this.changeUsername();
+			if (this.user.email.address !== this.originalUser.email.address)
+				this.changeEmail();
+		},
+		saveChangesPreferences() {
+			if (this.localNightmode !== this.nightmode)
+				this.changeNightmodeLocal();
+		},
 		changeEmail() {
 			const email = this.user.email.address;
 			if (!validation.isLength(email, 3, 254))
@@ -231,11 +343,13 @@ export default {
 				res => {
 					if (res.status !== "success")
 						new Toast({ content: res.message, timeout: 8000 });
-					else
+					else {
 						new Toast({
 							content: "Successfully changed email address",
 							timeout: 4000
 						});
+						this.originalUser.email.address = email;
+					}
 				}
 			);
 		},
@@ -260,11 +374,108 @@ export default {
 				res => {
 					if (res.status !== "success")
 						new Toast({ content: res.message, timeout: 8000 });
-					else
+					else {
 						new Toast({
 							content: "Successfully changed username",
 							timeout: 4000
 						});
+						this.originalUser.username = username;
+					}
+				}
+			);
+		},
+		changeName() {
+			const { name } = this.user;
+			if (!validation.isLength(name, 1, 64))
+				return new Toast({
+					content: "Name must have between 1 and 64 characters.",
+					timeout: 8000
+				});
+
+			return this.socket.emit(
+				"users.updateName",
+				this.userId,
+				name,
+				res => {
+					if (res.status !== "success")
+						new Toast({ content: res.message, timeout: 8000 });
+					else {
+						new Toast({
+							content: "Successfully changed name",
+							timeout: 4000
+						});
+						this.originalUser.name = name;
+					}
+				}
+			);
+		},
+		changeLocation() {
+			const { location } = this.user;
+			if (!validation.isLength(location, 0, 50))
+				return new Toast({
+					content: "Location must have between 0 and 50 characters.",
+					timeout: 8000
+				});
+
+			return this.socket.emit(
+				"users.updateLocation",
+				this.userId,
+				location,
+				res => {
+					if (res.status !== "success")
+						new Toast({ content: res.message, timeout: 8000 });
+					else {
+						new Toast({
+							content: "Successfully changed location",
+							timeout: 4000
+						});
+						this.originalUser.location = location;
+					}
+				}
+			);
+		},
+		changeBio() {
+			const { bio } = this.user;
+			if (!validation.isLength(bio, 0, 200))
+				return new Toast({
+					content: "Bio must have between 0 and 200 characters.",
+					timeout: 8000
+				});
+
+			return this.socket.emit(
+				"users.updateBio",
+				this.userId,
+				bio,
+				res => {
+					if (res.status !== "success")
+						new Toast({ content: res.message, timeout: 8000 });
+					else {
+						new Toast({
+							content: "Successfully changed bio",
+							timeout: 4000
+						});
+						this.originalUser.bio = bio;
+					}
+				}
+			);
+		},
+		changeAvatarType() {
+			const { type } = this.user.avatar;
+
+			return this.socket.emit(
+				"users.updateAvatarType",
+				this.userId,
+				type,
+				res => {
+					if (res.status !== "success")
+						new Toast({ content: res.message, timeout: 8000 });
+					else {
+						new Toast({
+							content: "Successfully updated avatar type",
+							timeout: 4000
+						});
+						this.originalUser.avatar.type = type;
+					}
 				}
 			);
 		},
@@ -358,7 +569,12 @@ export default {
 			this.socket.emit(`users.removeSessions`, this.userId, res => {
 				new Toast({ content: res.message, timeout: 4000 });
 			});
-		}
+		},
+		changeNightmodeLocal() {
+			localStorage.setItem("nightmode", this.localNightmode);
+			this.changeNightmode(this.localNightmode);
+		},
+		...mapActions("user/preferences", ["changeNightmode"])
 	}
 };
 </script>
@@ -366,17 +582,126 @@ export default {
 <style lang="scss" scoped>
 @import "styles/global.scss";
 
-.night-mode {
-	.label {
-		color: #ddd;
+.container {
+	width: 962px;
+	margin-left: auto;
+	margin-right: auto;
+	margin-top: 32px;
+	padding: 24px;
+	display: flex;
+
+	.nav-links {
+		height: 100%;
+		width: 250px;
+		margin-right: 64px;
+
+		a {
+			outline: none;
+			border: none;
+			box-shadow: none;
+			color: $musareBlue;
+			font-size: 22px;
+			line-height: 26px;
+			padding: 7px 0 7px 12px;
+			width: 100%;
+			text-align: left;
+			cursor: pointer;
+			border-radius: 5px;
+			background-color: transparent;
+			display: inline-block;
+
+			&.active {
+				color: $white;
+				background-color: $musareBlue;
+			}
+		}
+	}
+
+	.content {
+		width: 600px;
+
+		.control {
+			margin-bottom: 24px;
+		}
+
+		label {
+			font-size: 14px;
+			color: $dark-grey-2;
+			padding-bottom: 4px;
+		}
+
+		input {
+			height: 32px;
+		}
+
+		textarea {
+			height: 96px;
+		}
+
+		input,
+		textarea {
+			border-radius: 3px;
+			border: 1px solid $light-grey-2;
+		}
+
+		button {
+			width: 100%;
+		}
+
+		.checkbox-control {
+			input[type="checkbox"] {
+				opacity: 0;
+				position: absolute;
+			}
+
+			label {
+				display: flex;
+				flex-direction: row;
+				align-items: center;
+
+				span {
+					cursor: pointer;
+					width: 24px;
+					height: 24px;
+					background-color: $white;
+					display: inline-block;
+					border: 1px solid $dark-grey-2;
+					position: relative;
+					border-radius: 3px;
+				}
+
+				p {
+					margin-left: 10px;
+				}
+			}
+
+			input[type="checkbox"]:checked + label span::after {
+				content: "";
+				width: 18px;
+				height: 18px;
+				left: 2px;
+				top: 2px;
+				border-radius: 3px;
+				background-color: $musareBlue;
+				position: absolute;
+			}
+		}
 	}
 }
 
-.container {
-	padding: 25px;
+.avatar-select {
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+
+	.select:after {
+		border-color: $musareBlue;
+	}
 }
 
-a {
-	color: $primary-color !important;
+.night-mode {
+	label {
+		color: #ddd !important;
+	}
 }
 </style>

+ 700 - 101
frontend/components/User/Show.vue

@@ -1,89 +1,229 @@
 <template>
 	<div v-if="isUser">
 		<metadata v-bind:title="`Profile | ${user.username}`" />
+		<edit-playlist v-if="modals.editPlaylist" />
+		<create-playlist v-if="modals.createPlaylist" />
 		<main-header />
 		<div class="container">
-			<img class="avatar" src="/assets/notes.png" />
-			<h2 class="has-text-centered username">@{{ user.username }}</h2>
-			<h5>A member since {{ user.createdAt }}</h5>
-			<div
-				v-if="role === 'admin' && userId !== user._id"
-				class="admin-functionality"
-			>
-				<a
-					v-if="user.role == 'default'"
-					class="button is-small is-info is-outlined"
-					@click="changeRank('admin')"
-					>Promote to Admin</a
+			<div class="info-section">
+				<div class="picture-name-row">
+					<img
+						class="profile-picture"
+						:src="
+							user.avatar.url && user.avatar.type === 'gravatar'
+								? `${user.avatar.url}?d=${notes}&s=250`
+								: '/assets/notes.png'
+						"
+						onerror="this.src='/assets/notes.png'; this.onerror=''"
+					/>
+					<div>
+						<div class="name-role-row">
+							<p class="name">{{ user.name }}</p>
+							<span
+								class="role admin"
+								v-if="user.role === 'admin'"
+								>admin</span
+							>
+						</div>
+						<p class="username">@{{ user.username }}</p>
+					</div>
+				</div>
+				<div
+					class="buttons"
+					v-if="userId === user._id || role === 'admin'"
 				>
-				<a
-					v-if="user.role == 'admin'"
-					class="button is-small is-danger is-outlined"
-					@click="changeRank('default')"
-					>Demote to User</a
+					<router-link
+						:to="`/admin/users?userId=${user._id}`"
+						class="button is-primary"
+						v-if="role === 'admin'"
+					>
+						Edit
+					</router-link>
+					<router-link
+						to="/settings"
+						class="button is-primary"
+						v-if="userId === user._id"
+					>
+						Settings
+					</router-link>
+				</div>
+				<div class="bio-row" v-if="user.bio">
+					<i class="material-icons">notes</i>
+					<p>{{ user.bio }}</p>
+				</div>
+				<div
+					class="date-location-row"
+					v-if="user.createdAt || user.location"
 				>
-			</div>
-			<nav class="level">
-				<div class="level-item has-text-centered">
-					<p class="heading">
-						Rank
-					</p>
-					<p class="title role">
-						{{ user.role }}
-					</p>
+					<div class="date" v-if="user.createdAt">
+						<i class="material-icons">calendar_today</i>
+						<p>{{ user.createdAt }}</p>
+					</div>
+					<div class="location" v-if="user.location">
+						<i class="material-icons">location_on</i>
+						<p>{{ user.location }}</p>
+					</div>
 				</div>
-				<div class="level-item has-text-centered">
-					<p class="heading">
-						Songs Requested
-					</p>
-					<p class="title">
-						{{ user.statistics.songsRequested }}
-					</p>
+			</div>
+			<div class="bottom-section">
+				<div class="buttons">
+					<button
+						:class="{ active: activeTab === 'recentActivity' }"
+						@click="switchTab('recentActivity')"
+					>
+						Recent activity
+					</button>
+					<button
+						:class="{ active: activeTab === 'playlists' }"
+						@click="switchTab('playlists')"
+						v-if="user._id === userId"
+					>
+						Playlists
+					</button>
 				</div>
-				<div class="level-item has-text-centered">
-					<p class="heading">
-						Likes
-					</p>
-					<p class="title">
-						{{ user.liked.length }}
-					</p>
+				<div
+					class="content recent-activity-tab"
+					v-if="activeTab === 'recentActivity'"
+				>
+					<div v-if="activities.length > 0">
+						<div
+							class="item activity"
+							v-for="activity in sortedActivities"
+							:key="activity._id"
+						>
+							<div class="thumbnail">
+								<img :src="activity.thumbnail" alt="" />
+								<i class="material-icons activity-type-icon">{{
+									activity.icon
+								}}</i>
+							</div>
+							<div class="left-part">
+								<p
+									class="top-text"
+									v-html="activity.message"
+								></p>
+								<p class="bottom-text">
+									{{
+										formatDistance(
+											parseISO(activity.createdAt),
+											new Date(),
+											{ addSuffix: true }
+										)
+									}}
+								</p>
+							</div>
+							<div class="actions">
+								<a
+									class="hide-icon"
+									href="#"
+									@click="hideActivity(activity._id)"
+								>
+									<i class="material-icons">visibility_off</i>
+								</a>
+							</div>
+						</div>
+					</div>
+					<div v-else>
+						<h2>No recent activity.</h2>
+					</div>
 				</div>
-				<div class="level-item has-text-centered">
-					<p class="heading">
-						Dislikes
-					</p>
-					<p class="title">
-						{{ user.disliked.length }}
-					</p>
+				<div
+					class="content playlists-tab"
+					v-if="activeTab === 'playlists'"
+				>
+					<div
+						class="item playlist"
+						v-for="playlist in playlists"
+						:key="playlist._id"
+					>
+						<div class="left-part">
+							<p class="top-text">{{ playlist.displayName }}</p>
+							<p class="bottom-text">
+								{{ totalLength(playlist) }} •
+								{{ playlist.songs.length }}
+								{{
+									playlist.songs.length === 1
+										? "song"
+										: "songs"
+								}}
+							</p>
+						</div>
+						<div class="actions">
+							<button
+								class="button is-primary"
+								@click="editPlaylistClick(playlist._id)"
+							>
+								Edit
+							</button>
+						</div>
+					</div>
+					<button
+						class="button is-primary"
+						@click="
+							openModal({
+								sector: 'station',
+								modal: 'createPlaylist'
+							})
+						"
+					>
+						Create new playlist
+					</button>
 				</div>
-			</nav>
+			</div>
 		</div>
 		<main-footer />
 	</div>
 </template>
 
 <script>
-import { mapState } from "vuex";
+import { mapState, mapActions } from "vuex";
+import { format, formatDistance, parseISO } from "date-fns";
 import Toast from "toasters";
-import { format, parseISO } from "date-fns";
 
 import MainHeader from "../MainHeader.vue";
 import MainFooter from "../MainFooter.vue";
 import io from "../../io";
+import utils from "../../js/utils";
 
 export default {
-	components: { MainHeader, MainFooter },
+	components: {
+		MainHeader,
+		MainFooter,
+		CreatePlaylist: () => import("../Modals/Playlists/Create.vue"),
+		EditPlaylist: () => import("../Modals/Playlists/Edit.vue")
+	},
 	data() {
 		return {
+			utils,
 			user: {},
-			isUser: false
+			notes: "",
+			isUser: false,
+			activeTab: "recentActivity",
+			playlists: [],
+			activities: []
 		};
 	},
-	computed: mapState({
-		role: state => state.user.auth.role,
-		userId: state => state.user.auth.userId
-	}),
+	computed: {
+		...mapState({
+			role: state => state.user.auth.role,
+			userId: state => state.user.auth.userId,
+			...mapState("modals", {
+				modals: state => state.modals.station
+			})
+		}),
+		sortedActivities() {
+			const { activities } = this;
+			return activities.sort(
+				(x, y) => new Date(y.createdAt) - new Date(x.createdAt)
+			);
+		}
+	},
 	mounted() {
+		lofig.get("frontendDomain").then(frontendDomain => {
+			this.frontendDomain = frontendDomain;
+			this.notes = encodeURI(`${this.frontendDomain}/assets/notes.png`);
+		});
+
 		io.getSocket(socket => {
 			this.socket = socket;
 			this.socket.emit(
@@ -98,28 +238,283 @@ export default {
 							"MMMM do yyyy"
 						);
 						this.isUser = true;
+
+						if (this.user._id === this.userId) {
+							this.socket.emit("playlists.indexForUser", res => {
+								if (res.status === "success")
+									this.playlists = res.data;
+							});
+
+							this.socket.emit(
+								"activities.getSet",
+								this.userId,
+								1,
+								res => {
+									if (res.status === "success") {
+										for (
+											let a = 0;
+											a < res.data.length;
+											a += 1
+										) {
+											this.formatActivity(
+												res.data[a],
+												activity => {
+													this.activities.unshift(
+														activity
+													);
+												}
+											);
+										}
+									}
+								}
+							);
+
+							this.socket.on(
+								"event:activity.create",
+								activity => {
+									console.log(activity);
+									this.formatActivity(activity, activity => {
+										this.activities.unshift(activity);
+									});
+								}
+							);
+
+							this.socket.on(
+								"event:playlist.create",
+								playlist => {
+									this.playlists.push(playlist);
+								}
+							);
+
+							this.socket.on(
+								"event:playlist.delete",
+								playlistId => {
+									this.playlists.forEach(
+										(playlist, index) => {
+											if (playlist._id === playlistId) {
+												this.playlists.splice(index, 1);
+											}
+										}
+									);
+								}
+							);
+
+							this.socket.on("event:playlist.addSong", data => {
+								this.playlists.forEach((playlist, index) => {
+									if (playlist._id === data.playlistId) {
+										this.playlists[index].songs.push(
+											data.song
+										);
+									}
+								});
+							});
+
+							this.socket.on(
+								"event:playlist.removeSong",
+								data => {
+									this.playlists.forEach(
+										(playlist, index) => {
+											if (
+												playlist._id === data.playlistId
+											) {
+												this.playlists[
+													index
+												].songs.forEach(
+													(song, index2) => {
+														if (
+															song._id ===
+															data.songId
+														) {
+															this.playlists[
+																index
+															].songs.splice(
+																index2,
+																1
+															);
+														}
+													}
+												);
+											}
+										}
+									);
+								}
+							);
+
+							this.socket.on(
+								"event:playlist.updateDisplayName",
+								data => {
+									this.playlists.forEach(
+										(playlist, index) => {
+											if (
+												playlist._id === data.playlistId
+											) {
+												this.playlists[
+													index
+												].displayName =
+													data.displayName;
+											}
+										}
+									);
+								}
+							);
+						}
 					}
 				}
 			);
 		});
 	},
 	methods: {
-		changeRank(newRank) {
-			this.socket.emit(
-				"users.updateRole",
-				this.user._id,
-				newRank === "admin" ? "admin" : "default",
-				res => {
-					if (res.status === "error")
-						new Toast({ content: res.message, timeout: 2000 });
-					else this.user.role = newRank;
-					new Toast({
-						content: `User ${this.$route.params.username}'s rank has been changed to: ${newRank}`,
-						timeout: 2000
-					});
+		formatDistance,
+		parseISO,
+		switchTab(tabName) {
+			this.activeTab = tabName;
+		},
+		editPlaylistClick(playlistId) {
+			console.log(playlistId);
+			this.editPlaylist(playlistId);
+			this.openModal({ sector: "station", modal: "editPlaylist" });
+		},
+		totalLength(playlist) {
+			let length = 0;
+			playlist.songs.forEach(song => {
+				length += song.duration;
+			});
+			return this.utils.formatTimeLong(length);
+		},
+		hideActivity(activityId) {
+			this.socket.emit("activities.hideActivity", activityId, res => {
+				if (res.status === "success") {
+					this.activities = this.activities.filter(
+						activity => activity._id !== activityId
+					);
+				} else {
+					new Toast({ content: res.message, timeout: 3000 });
 				}
-			);
-		}
+			});
+		},
+		formatActivity(res, cb) {
+			console.log("activity", res);
+
+			const icons = {
+				created_account: "account_circle",
+				created_station: "radio",
+				deleted_station: "delete",
+				created_playlist: "playlist_add_check",
+				deleted_playlist: "delete_sweep",
+				liked_song: "favorite",
+				added_song_to_playlist: "playlist_add",
+				added_songs_to_playlist: "playlist_add"
+			};
+
+			const activity = {
+				...res,
+				thumbnail: "",
+				message: "",
+				icon: ""
+			};
+
+			const plural = activity.payload.length > 1;
+
+			activity.icon = icons[activity.activityType];
+
+			if (activity.activityType === "created_account") {
+				activity.message = "Welcome to Musare!";
+				return cb(activity);
+			}
+			if (activity.activityType === "created_station") {
+				this.socket.emit(
+					"stations.getStationForActivity",
+					activity.payload[0],
+					res => {
+						if (res.status === "success") {
+							activity.message = `Created the station <strong>${res.data.title}</strong>`;
+							activity.thumbnail = res.data.thumbnail;
+							return cb(activity);
+						}
+						activity.message = "Created a station";
+						return cb(activity);
+					}
+				);
+			}
+			if (activity.activityType === "deleted_station") {
+				activity.message = `Deleted a station`;
+				return cb(activity);
+			}
+			if (activity.activityType === "created_playlist") {
+				this.socket.emit(
+					"playlists.getPlaylistForActivity",
+					activity.payload[0],
+					res => {
+						if (res.status === "success") {
+							activity.message = `Created the playlist <strong>${res.data.title}</strong>`;
+							// activity.thumbnail = res.data.thumbnail;
+							return cb(activity);
+						}
+						activity.message = "Created a playlist";
+						return cb(activity);
+					}
+				);
+			}
+			if (activity.activityType === "deleted_playlist") {
+				activity.message = `Deleted a playlist`;
+				return cb(activity);
+			}
+			if (activity.activityType === "liked_song") {
+				if (plural) {
+					activity.message = `Liked ${activity.payload.length} songs.`;
+					return cb(activity);
+				}
+				this.socket.emit(
+					"songs.getSongForActivity",
+					activity.payload[0],
+					res => {
+						if (res.status === "success") {
+							activity.message = `Liked the song <strong>${res.data.title}</strong>`;
+							activity.thumbnail = res.data.thumbnail;
+							return cb(activity);
+						}
+						activity.message = "Liked a song";
+						return cb(activity);
+					}
+				);
+			}
+			if (activity.activityType === "added_song_to_playlist") {
+				this.socket.emit(
+					"songs.getSongForActivity",
+					activity.payload[0].songId,
+					song => {
+						console.log(song);
+						this.socket.emit(
+							"playlists.getPlaylistForActivity",
+							activity.payload[0].playlistId,
+							playlist => {
+								if (song.status === "success") {
+									if (playlist.status === "success")
+										activity.message = `Added the song <strong>${song.data.title}</strong> to the playlist <strong>${playlist.data.title}</strong>`;
+									else
+										activity.message = `Added the song <strong>${song.data.title}</strong> to a playlist`;
+									activity.thumbnail = song.data.thumbnail;
+									return cb(activity);
+								}
+								if (playlist.status === "success") {
+									activity.message = `Added a song to the playlist <strong>${playlist.data.title}</strong>`;
+									return cb(activity);
+								}
+								activity.message = "Added a song to a playlist";
+								return cb(activity);
+							}
+						);
+					}
+				);
+			}
+			if (activity.activityType === "added_songs_to_playlist") {
+				activity.message = `Added ${activity.payload.length} songs to a playlist`;
+				return cb(activity);
+			}
+			return false;
+		},
+		...mapActions("modals", ["openModal"]),
+		...mapActions("user/playlists", ["editPlaylist"])
 	}
 };
 </script>
@@ -127,46 +522,250 @@ export default {
 <style lang="scss" scoped>
 @import "styles/global.scss";
 
-.night-mode {
-	.level .title {
-		color: #ccc;
+.info-section {
+	width: 912px;
+	margin-left: auto;
+	margin-right: auto;
+	margin-top: 32px;
+	padding: 24px;
+
+	.picture-name-row {
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		margin-bottom: 24px;
 	}
-}
 
-.container {
-	padding: 25px;
-}
+	.profile-picture {
+		width: 100px;
+		height: 100px;
+		border-radius: 100%;
+		margin-right: 32px;
+	}
 
-.avatar {
-	border-radius: 50%;
-	width: 250px;
-	display: block;
-	margin: auto;
-}
+	.name-role-row {
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+	}
 
-h5 {
-	text-align: center;
-	margin-bottom: 25px;
-	font-size: 17px;
-}
+	.name {
+		font-size: 34px;
+		line-height: 40px;
+		color: $dark-grey-3;
+	}
 
-.role {
-	text-transform: capitalize;
-}
+	.role {
+		padding: 2px 24px;
+		color: $white;
+		text-transform: uppercase;
+		font-size: 12px;
+		line-height: 14px;
+		height: 18px;
+		border-radius: 5px;
+		margin-left: 12px;
+
+		&.admin {
+			background-color: $red;
+		}
+	}
 
-.level {
-	margin-top: 40px;
+	.username {
+		font-size: 24px;
+		line-height: 28px;
+		color: $dark-grey;
+	}
+
+	.buttons {
+		width: 388px;
+		display: flex;
+		flex-direction: row;
+		margin-left: auto;
+		margin-right: auto;
+		margin-bottom: 24px;
+
+		.button {
+			flex: 1;
+			font-size: 17px;
+			line-height: 20px;
+
+			&:nth-child(2) {
+				margin-left: 20px;
+			}
+		}
+	}
+
+	.bio-row,
+	.date-location-row {
+		i {
+			font-size: 24px;
+			color: $dark-grey-2;
+			margin-right: 12px;
+		}
+
+		p {
+			font-size: 17px;
+			line-height: 20px;
+			color: $dark-grey-2;
+			word-break: break-word;
+		}
+	}
+
+	.bio-row {
+		max-width: 608px;
+		margin-bottom: 24px;
+		margin-left: auto;
+		margin-right: auto;
+		display: flex;
+		width: max-content;
+	}
+
+	.date-location-row {
+		max-width: 608px;
+		margin-left: auto;
+		margin-right: auto;
+		margin-bottom: 24px;
+		display: flex;
+		width: max-content;
+		margin-bottom: 24px;
+
+		> div:nth-child(2) {
+			margin-left: 48px;
+		}
+	}
+
+	.date,
+	.location {
+		display: flex;
+	}
 }
 
-.admin-functionality {
-	text-align: center;
-	margin: 0 auto;
+.bottom-section {
+	width: 962px;
+	margin-left: auto;
+	margin-right: auto;
+	margin-top: 32px;
+	padding: 24px;
+	display: flex;
+
+	.buttons {
+		height: 100%;
+		width: 250px;
+		margin-right: 64px;
+
+		button {
+			outline: none;
+			border: none;
+			box-shadow: none;
+			color: $musareBlue;
+			font-size: 22px;
+			line-height: 26px;
+			padding: 7px 0 7px 12px;
+			width: 100%;
+			text-align: left;
+			cursor: pointer;
+			border-radius: 5px;
+			background-color: transparent;
+
+			&.active {
+				color: $white;
+				background-color: $musareBlue;
+			}
+		}
+	}
+
+	.content {
+		width: 600px;
+
+		.item {
+			width: 100%;
+			height: 72px;
+			border: 0.5px $light-grey-2 solid;
+			margin-bottom: 12px;
+			border-radius: 0 5px 5px 0;
+			display: flex;
+
+			.top-text {
+				color: $dark-grey-2;
+				font-size: 20px;
+				line-height: 23px;
+				margin-bottom: 0;
+			}
+
+			.bottom-text {
+				color: $dark-grey-2;
+				font-size: 16px;
+				line-height: 19px;
+				margin-bottom: 0;
+				margin-top: 6px;
+
+				&:first-letter {
+					text-transform: uppercase;
+				}
+			}
+
+			.thumbnail {
+				position: relative;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				width: 70.5px;
+				height: 70.5px;
+				background-color: #000;
+
+				img {
+					opacity: 0.4;
+				}
+
+				.activity-type-icon {
+					position: absolute;
+					color: #fff;
+				}
+			}
+
+			.left-part {
+				flex: 1;
+				padding: 12px;
+			}
+
+			.actions {
+				display: flex;
+				align-items: center;
+				padding: 12px;
+
+				.hide-icon {
+					border-bottom: 0;
+					display: flex;
+
+					i {
+						color: #bdbdbd;
+					}
+				}
+			}
+
+			button {
+				font-size: 17px;
+			}
+		}
+	}
+
+	.playlists-tab > button {
+		width: 100%;
+		font-size: 17px;
+	}
 }
 
-@media (max-width: 350px) {
-	.username {
-		font-size: 2.9rem;
-		word-wrap: break-all;
+.night-mode {
+	.name,
+	.username,
+	.bio-row i,
+	.bio-row p,
+	.date-location-row i,
+	.date-location-row p,
+	.item .left-part .top-text,
+	.item .left-part .bottom-text {
+		color: $light-grey;
 	}
 }
 </style>

+ 6 - 3
frontend/components/pages/Home.vue

@@ -7,7 +7,7 @@
 				<div class="group-title">
 					Stations&nbsp;
 					<a
-						v-if="$parent.loggedIn"
+						v-if="loggedIn"
 						href="#"
 						@click="
 							openModal({
@@ -115,6 +115,9 @@
 						<span v-else class="songTitle">No song</span>
 					</div>
 				</router-link>
+				<h4 v-if="stations.length === 0">
+					There are no stations to display
+				</h4>
 			</div>
 			<main-footer />
 		</div>
@@ -154,9 +157,9 @@ export default {
 			);
 		},
 		...mapState({
-			modals: state => state.modals.modals.home,
 			loggedIn: state => state.user.auth.loggedIn,
-			userId: state => state.user.auth.userId
+			userId: state => state.user.auth.userId,
+			modals: state => state.modals.modals.home
 		})
 	},
 	mounted() {

+ 4 - 0
frontend/components/pages/Team.vue

@@ -225,6 +225,10 @@ ul {
 	list-style: none;
 }
 
+.columns {
+	margin: 0;
+}
+
 .card-content .content {
 	font-size: 15px;
 }

+ 8 - 0
frontend/dist/index.css

@@ -1,3 +1,7 @@
+html {
+	background-color: inherit;
+}
+
 body {
 	background-color: rgb(245, 245, 245);
 }
@@ -105,3 +109,7 @@ a.nav-item.is-tab {
 	border-width: 0;
 	color: #fff;
 }
+
+strong {
+	color: inherit;
+}

+ 2 - 2
frontend/dist/index.tpl.html

@@ -8,7 +8,7 @@
 	<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no'>
 	<meta name='keywords' content='music, musare, listen, station, station, radio, edm, chill, community, official, rooms, room, party, good, mus, pop'>
 	<meta name='description' content='On Musare you can listen to lots of different songs, playing 24/7 in our official stations and in user-made community stations!'>
-	<meta name='copyright' content='© Copyright Musare 2015-2019 All Right Reserved'>
+	<meta name='copyright' content='© Copyright Musare 2015-2020 All Right Reserved'>
 
 	<link rel='apple-touch-icon' sizes='57x57' href='/assets/favicon/apple-touch-icon-57x57.png?v=06042016'>
 	<link rel='apple-touch-icon' sizes='60x60' href='/assets/favicon/apple-touch-icon-60x60.png?v=06042016'>
@@ -39,7 +39,7 @@
 	<script src='https://www.youtube.com/iframe_api'></script>
 	<script type='text/javascript' src='/vendor/can-autoplay.min.js'></script>
 	<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js" integrity="sha256-yr4fRk/GU1ehYJPAs8P4JlTgu0Hdsp4ZKrx8bDEDC3I=" crossorigin="anonymous"></script>
-	<script type='text/javascript' src='https://unpkg.com/lofig@1.2.1/dist/lofig.min.js'></script>
+	<script type='text/javascript' src='https://unpkg.com/lofig@1.2.2/dist/lofig.min.js'></script>
 </head>
 <body>
 	<div id="root"></div>

+ 10 - 0
frontend/io.js

@@ -1,3 +1,5 @@
+import Toast from "toasters";
+
 const callbacks = {
 	general: {
 		temp: [],
@@ -82,6 +84,14 @@ export default {
 			callbacks.onConnectError.persist.forEach(cb => cb());
 		});
 
+		this.socket.on("error", err => {
+			console.log("IO: SOCKET ERROR", err);
+			new Toast({
+				content: err,
+				timeout: 8000
+			});
+		});
+
 		this.ready = true;
 
 		callbacks.general.temp.forEach(callback => callback(this.socket));

+ 71 - 2
frontend/js/utils.js

@@ -1,5 +1,5 @@
 export default {
-	guid: () =>
+	guid: () => {
 		[1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1]
 			.map(b =>
 				b
@@ -8,5 +8,74 @@ export default {
 							.substring(1)
 					: "-"
 			)
-			.join("")
+			.join("");
+	},
+	formatTime: originalDuration => {
+		if (originalDuration) {
+			if (originalDuration < 0) return "0:00";
+
+			let duration = originalDuration;
+			let hours = Math.floor(duration / (60 * 60));
+			duration -= hours * 60 * 60;
+			let minutes = Math.floor(duration / 60);
+			duration -= minutes * 60;
+			let seconds = Math.floor(duration);
+
+			if (hours === 0) {
+				hours = "";
+			}
+
+			if (hours > 0) {
+				if (minutes < 10) minutes = `0${minutes}`;
+			}
+
+			if (seconds < 10) {
+				seconds = `0${seconds}`;
+			}
+
+			return `${hours}${hours ? ":" : ""}${minutes}:${seconds}`;
+		}
+		return false;
+	},
+	formatTimeLong: duration => {
+		if (duration <= 0) return "0 seconds";
+
+		const hours = Math.floor(duration / (60 * 60));
+		const formatHours = () => {
+			if (hours > 0) {
+				if (hours > 1) {
+					if (hours < 10) return `0${hours} hours `;
+					return `${hours} hours `;
+				}
+				return `0${hours} hour `;
+			}
+			return "";
+		};
+
+		const minutes = Math.floor((duration - hours * 60 * 60) / 60);
+		const formatMinutes = () => {
+			if (minutes > 0) {
+				if (minutes > 1) {
+					if (minutes < 10) return `0${minutes} minutes `;
+					return `${minutes} minutes `;
+				}
+				return `0${minutes} minute `;
+			}
+			return "";
+		};
+
+		const seconds = Math.floor(duration - hours * 60 * 60 - minutes * 60);
+		const formatSeconds = () => {
+			if (seconds > 0) {
+				if (seconds > 1) {
+					if (seconds < 10) return `0${seconds} seconds `;
+					return `${seconds} seconds `;
+				}
+				return `0${seconds} second `;
+			}
+			return "";
+		};
+
+		return formatHours() + formatMinutes() + formatSeconds();
+	}
 };

+ 32 - 0
frontend/keyboardShortcuts.js

@@ -0,0 +1,32 @@
+const shortcuts = {};
+
+let _shortcuts = [];
+
+const lib = {
+	handleKeyDown(keyCode, shift, ctrl) {
+		_shortcuts.forEach(shortcut => {
+			if (
+				shortcut.keyCode === keyCode &&
+				shortcut.shift === shift &&
+				shortcut.ctrl === ctrl
+			)
+				shortcut.handler();
+		});
+	},
+
+	registerShortcut(name, shortcut) {
+		shortcuts[name] = shortcut;
+		lib.remakeShortcutsArray();
+	},
+
+	unregisterShortcut: name => {
+		delete shortcuts[name];
+		lib.remakeShortcutsArray();
+	},
+
+	remakeShortcutsArray: () => {
+		_shortcuts = Object.keys(shortcuts).map(key => shortcuts[key]);
+	}
+};
+
+export default lib;

+ 13261 - 0
frontend/package-lock.json

@@ -0,0 +1,13261 @@
+{
+  "name": "musare-frontend",
+  "version": "2.1.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@babel/code-frame": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
+      "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
+      "dev": true,
+      "requires": {
+        "@babel/highlight": "^7.8.3"
+      }
+    },
+    "@babel/compat-data": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.0.tgz",
+      "integrity": "sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g==",
+      "dev": true,
+      "requires": {
+        "browserslist": "^4.9.1",
+        "invariant": "^2.2.4",
+        "semver": "^5.5.0"
+      }
+    },
+    "@babel/core": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz",
+      "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.8.3",
+        "@babel/generator": "^7.9.0",
+        "@babel/helper-module-transforms": "^7.9.0",
+        "@babel/helpers": "^7.9.0",
+        "@babel/parser": "^7.9.0",
+        "@babel/template": "^7.8.6",
+        "@babel/traverse": "^7.9.0",
+        "@babel/types": "^7.9.0",
+        "convert-source-map": "^1.7.0",
+        "debug": "^4.1.0",
+        "gensync": "^1.0.0-beta.1",
+        "json5": "^2.1.2",
+        "lodash": "^4.17.13",
+        "resolve": "^1.3.2",
+        "semver": "^5.4.1",
+        "source-map": "^0.5.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
+      }
+    },
+    "@babel/generator": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.5.tgz",
+      "integrity": "sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.9.5",
+        "jsesc": "^2.5.1",
+        "lodash": "^4.17.13",
+        "source-map": "^0.5.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
+      }
+    },
+    "@babel/helper-annotate-as-pure": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz",
+      "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-builder-binary-assignment-operator-visitor": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz",
+      "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-explode-assignable-expression": "^7.8.3",
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-compilation-targets": {
+      "version": "7.8.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz",
+      "integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==",
+      "dev": true,
+      "requires": {
+        "@babel/compat-data": "^7.8.6",
+        "browserslist": "^4.9.1",
+        "invariant": "^2.2.4",
+        "levenary": "^1.1.1",
+        "semver": "^5.5.0"
+      }
+    },
+    "@babel/helper-create-regexp-features-plugin": {
+      "version": "7.8.8",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz",
+      "integrity": "sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.8.3",
+        "@babel/helper-regex": "^7.8.3",
+        "regexpu-core": "^4.7.0"
+      }
+    },
+    "@babel/helper-define-map": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz",
+      "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-function-name": "^7.8.3",
+        "@babel/types": "^7.8.3",
+        "lodash": "^4.17.13"
+      }
+    },
+    "@babel/helper-explode-assignable-expression": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz",
+      "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==",
+      "dev": true,
+      "requires": {
+        "@babel/traverse": "^7.8.3",
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-function-name": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz",
+      "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-get-function-arity": "^7.8.3",
+        "@babel/template": "^7.8.3",
+        "@babel/types": "^7.9.5"
+      }
+    },
+    "@babel/helper-get-function-arity": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz",
+      "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-hoist-variables": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz",
+      "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-member-expression-to-functions": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz",
+      "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-module-imports": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz",
+      "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-module-transforms": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz",
+      "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-imports": "^7.8.3",
+        "@babel/helper-replace-supers": "^7.8.6",
+        "@babel/helper-simple-access": "^7.8.3",
+        "@babel/helper-split-export-declaration": "^7.8.3",
+        "@babel/template": "^7.8.6",
+        "@babel/types": "^7.9.0",
+        "lodash": "^4.17.13"
+      }
+    },
+    "@babel/helper-optimise-call-expression": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz",
+      "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-plugin-utils": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz",
+      "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==",
+      "dev": true
+    },
+    "@babel/helper-regex": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz",
+      "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==",
+      "dev": true,
+      "requires": {
+        "lodash": "^4.17.13"
+      }
+    },
+    "@babel/helper-remap-async-to-generator": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz",
+      "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.8.3",
+        "@babel/helper-wrap-function": "^7.8.3",
+        "@babel/template": "^7.8.3",
+        "@babel/traverse": "^7.8.3",
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-replace-supers": {
+      "version": "7.8.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz",
+      "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-member-expression-to-functions": "^7.8.3",
+        "@babel/helper-optimise-call-expression": "^7.8.3",
+        "@babel/traverse": "^7.8.6",
+        "@babel/types": "^7.8.6"
+      }
+    },
+    "@babel/helper-simple-access": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz",
+      "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==",
+      "dev": true,
+      "requires": {
+        "@babel/template": "^7.8.3",
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-split-export-declaration": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz",
+      "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==",
+      "dev": true,
+      "requires": {
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helper-validator-identifier": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz",
+      "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==",
+      "dev": true
+    },
+    "@babel/helper-wrap-function": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz",
+      "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-function-name": "^7.8.3",
+        "@babel/template": "^7.8.3",
+        "@babel/traverse": "^7.8.3",
+        "@babel/types": "^7.8.3"
+      }
+    },
+    "@babel/helpers": {
+      "version": "7.9.2",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz",
+      "integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==",
+      "dev": true,
+      "requires": {
+        "@babel/template": "^7.8.3",
+        "@babel/traverse": "^7.9.0",
+        "@babel/types": "^7.9.0"
+      }
+    },
+    "@babel/highlight": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz",
+      "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-validator-identifier": "^7.9.0",
+        "chalk": "^2.0.0",
+        "js-tokens": "^4.0.0"
+      }
+    },
+    "@babel/parser": {
+      "version": "7.9.4",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz",
+      "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==",
+      "dev": true
+    },
+    "@babel/plugin-proposal-async-generator-functions": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz",
+      "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-remap-async-to-generator": "^7.8.3",
+        "@babel/plugin-syntax-async-generators": "^7.8.0"
+      }
+    },
+    "@babel/plugin-proposal-dynamic-import": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz",
+      "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-dynamic-import": "^7.8.0"
+      }
+    },
+    "@babel/plugin-proposal-json-strings": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz",
+      "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-json-strings": "^7.8.0"
+      }
+    },
+    "@babel/plugin-proposal-nullish-coalescing-operator": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz",
+      "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0"
+      }
+    },
+    "@babel/plugin-proposal-numeric-separator": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz",
+      "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-numeric-separator": "^7.8.3"
+      }
+    },
+    "@babel/plugin-proposal-object-rest-spread": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz",
+      "integrity": "sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
+        "@babel/plugin-transform-parameters": "^7.9.5"
+      }
+    },
+    "@babel/plugin-proposal-optional-catch-binding": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz",
+      "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-optional-catch-binding": "^7.8.0"
+      }
+    },
+    "@babel/plugin-proposal-optional-chaining": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz",
+      "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-syntax-optional-chaining": "^7.8.0"
+      }
+    },
+    "@babel/plugin-proposal-unicode-property-regex": {
+      "version": "7.8.8",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz",
+      "integrity": "sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-create-regexp-features-plugin": "^7.8.8",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-syntax-async-generators": {
+      "version": "7.8.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+      "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-dynamic-import": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
+      "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-json-strings": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+      "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-nullish-coalescing-operator": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+      "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-numeric-separator": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz",
+      "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-syntax-object-rest-spread": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+      "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-optional-catch-binding": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+      "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-optional-chaining": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+      "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.0"
+      }
+    },
+    "@babel/plugin-syntax-top-level-await": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz",
+      "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-arrow-functions": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz",
+      "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-async-to-generator": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz",
+      "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-imports": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-remap-async-to-generator": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-block-scoped-functions": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz",
+      "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-block-scoping": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz",
+      "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "lodash": "^4.17.13"
+      }
+    },
+    "@babel/plugin-transform-classes": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz",
+      "integrity": "sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.8.3",
+        "@babel/helper-define-map": "^7.8.3",
+        "@babel/helper-function-name": "^7.9.5",
+        "@babel/helper-optimise-call-expression": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-replace-supers": "^7.8.6",
+        "@babel/helper-split-export-declaration": "^7.8.3",
+        "globals": "^11.1.0"
+      }
+    },
+    "@babel/plugin-transform-computed-properties": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz",
+      "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-destructuring": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz",
+      "integrity": "sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-dotall-regex": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz",
+      "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-create-regexp-features-plugin": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-duplicate-keys": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz",
+      "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-exponentiation-operator": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz",
+      "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-for-of": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz",
+      "integrity": "sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-function-name": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz",
+      "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-function-name": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-literals": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz",
+      "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-member-expression-literals": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz",
+      "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-modules-amd": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz",
+      "integrity": "sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-transforms": "^7.9.0",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "babel-plugin-dynamic-import-node": "^2.3.0"
+      }
+    },
+    "@babel/plugin-transform-modules-commonjs": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz",
+      "integrity": "sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-transforms": "^7.9.0",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-simple-access": "^7.8.3",
+        "babel-plugin-dynamic-import-node": "^2.3.0"
+      }
+    },
+    "@babel/plugin-transform-modules-systemjs": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz",
+      "integrity": "sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-hoist-variables": "^7.8.3",
+        "@babel/helper-module-transforms": "^7.9.0",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "babel-plugin-dynamic-import-node": "^2.3.0"
+      }
+    },
+    "@babel/plugin-transform-modules-umd": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz",
+      "integrity": "sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-transforms": "^7.9.0",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-named-capturing-groups-regex": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz",
+      "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-create-regexp-features-plugin": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-new-target": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz",
+      "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-object-super": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz",
+      "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-replace-supers": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-parameters": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz",
+      "integrity": "sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-get-function-arity": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-property-literals": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz",
+      "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-regenerator": {
+      "version": "7.8.7",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz",
+      "integrity": "sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA==",
+      "dev": true,
+      "requires": {
+        "regenerator-transform": "^0.14.2"
+      }
+    },
+    "@babel/plugin-transform-reserved-words": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz",
+      "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-runtime": {
+      "version": "7.9.0",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz",
+      "integrity": "sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-module-imports": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "resolve": "^1.8.1",
+        "semver": "^5.5.1"
+      }
+    },
+    "@babel/plugin-transform-shorthand-properties": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz",
+      "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-spread": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz",
+      "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-sticky-regex": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz",
+      "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/helper-regex": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-template-literals": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz",
+      "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-annotate-as-pure": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-typeof-symbol": {
+      "version": "7.8.4",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz",
+      "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/plugin-transform-unicode-regex": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz",
+      "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-create-regexp-features-plugin": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3"
+      }
+    },
+    "@babel/preset-env": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.5.tgz",
+      "integrity": "sha512-eWGYeADTlPJH+wq1F0wNfPbVS1w1wtmMJiYk55Td5Yu28AsdR9AsC97sZ0Qq8fHqQuslVSIYSGJMcblr345GfQ==",
+      "dev": true,
+      "requires": {
+        "@babel/compat-data": "^7.9.0",
+        "@babel/helper-compilation-targets": "^7.8.7",
+        "@babel/helper-module-imports": "^7.8.3",
+        "@babel/helper-plugin-utils": "^7.8.3",
+        "@babel/plugin-proposal-async-generator-functions": "^7.8.3",
+        "@babel/plugin-proposal-dynamic-import": "^7.8.3",
+        "@babel/plugin-proposal-json-strings": "^7.8.3",
+        "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
+        "@babel/plugin-proposal-numeric-separator": "^7.8.3",
+        "@babel/plugin-proposal-object-rest-spread": "^7.9.5",
+        "@babel/plugin-proposal-optional-catch-binding": "^7.8.3",
+        "@babel/plugin-proposal-optional-chaining": "^7.9.0",
+        "@babel/plugin-proposal-unicode-property-regex": "^7.8.3",
+        "@babel/plugin-syntax-async-generators": "^7.8.0",
+        "@babel/plugin-syntax-dynamic-import": "^7.8.0",
+        "@babel/plugin-syntax-json-strings": "^7.8.0",
+        "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0",
+        "@babel/plugin-syntax-numeric-separator": "^7.8.0",
+        "@babel/plugin-syntax-object-rest-spread": "^7.8.0",
+        "@babel/plugin-syntax-optional-catch-binding": "^7.8.0",
+        "@babel/plugin-syntax-optional-chaining": "^7.8.0",
+        "@babel/plugin-syntax-top-level-await": "^7.8.3",
+        "@babel/plugin-transform-arrow-functions": "^7.8.3",
+        "@babel/plugin-transform-async-to-generator": "^7.8.3",
+        "@babel/plugin-transform-block-scoped-functions": "^7.8.3",
+        "@babel/plugin-transform-block-scoping": "^7.8.3",
+        "@babel/plugin-transform-classes": "^7.9.5",
+        "@babel/plugin-transform-computed-properties": "^7.8.3",
+        "@babel/plugin-transform-destructuring": "^7.9.5",
+        "@babel/plugin-transform-dotall-regex": "^7.8.3",
+        "@babel/plugin-transform-duplicate-keys": "^7.8.3",
+        "@babel/plugin-transform-exponentiation-operator": "^7.8.3",
+        "@babel/plugin-transform-for-of": "^7.9.0",
+        "@babel/plugin-transform-function-name": "^7.8.3",
+        "@babel/plugin-transform-literals": "^7.8.3",
+        "@babel/plugin-transform-member-expression-literals": "^7.8.3",
+        "@babel/plugin-transform-modules-amd": "^7.9.0",
+        "@babel/plugin-transform-modules-commonjs": "^7.9.0",
+        "@babel/plugin-transform-modules-systemjs": "^7.9.0",
+        "@babel/plugin-transform-modules-umd": "^7.9.0",
+        "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3",
+        "@babel/plugin-transform-new-target": "^7.8.3",
+        "@babel/plugin-transform-object-super": "^7.8.3",
+        "@babel/plugin-transform-parameters": "^7.9.5",
+        "@babel/plugin-transform-property-literals": "^7.8.3",
+        "@babel/plugin-transform-regenerator": "^7.8.7",
+        "@babel/plugin-transform-reserved-words": "^7.8.3",
+        "@babel/plugin-transform-shorthand-properties": "^7.8.3",
+        "@babel/plugin-transform-spread": "^7.8.3",
+        "@babel/plugin-transform-sticky-regex": "^7.8.3",
+        "@babel/plugin-transform-template-literals": "^7.8.3",
+        "@babel/plugin-transform-typeof-symbol": "^7.8.4",
+        "@babel/plugin-transform-unicode-regex": "^7.8.3",
+        "@babel/preset-modules": "^0.1.3",
+        "@babel/types": "^7.9.5",
+        "browserslist": "^4.9.1",
+        "core-js-compat": "^3.6.2",
+        "invariant": "^2.2.2",
+        "levenary": "^1.1.1",
+        "semver": "^5.5.0"
+      }
+    },
+    "@babel/preset-modules": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz",
+      "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-plugin-utils": "^7.0.0",
+        "@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
+        "@babel/plugin-transform-dotall-regex": "^7.4.4",
+        "@babel/types": "^7.4.4",
+        "esutils": "^2.0.2"
+      }
+    },
+    "@babel/runtime": {
+      "version": "7.9.2",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
+      "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
+      "requires": {
+        "regenerator-runtime": "^0.13.4"
+      }
+    },
+    "@babel/template": {
+      "version": "7.8.6",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz",
+      "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.8.3",
+        "@babel/parser": "^7.8.6",
+        "@babel/types": "^7.8.6"
+      }
+    },
+    "@babel/traverse": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.5.tgz",
+      "integrity": "sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.8.3",
+        "@babel/generator": "^7.9.5",
+        "@babel/helper-function-name": "^7.9.5",
+        "@babel/helper-split-export-declaration": "^7.8.3",
+        "@babel/parser": "^7.9.0",
+        "@babel/types": "^7.9.5",
+        "debug": "^4.1.0",
+        "globals": "^11.1.0",
+        "lodash": "^4.17.13"
+      }
+    },
+    "@babel/types": {
+      "version": "7.9.5",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.5.tgz",
+      "integrity": "sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==",
+      "dev": true,
+      "requires": {
+        "@babel/helper-validator-identifier": "^7.9.5",
+        "lodash": "^4.17.13",
+        "to-fast-properties": "^2.0.0"
+      }
+    },
+    "@snyk/cli-interface": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-2.4.0.tgz",
+      "integrity": "sha512-FH3RpJV83SFmNWR7FXS4O4sAF7oZZxAWmN6vwhADGJNAdgJYW4Acc6677ZSMnRpIHiFBHrsxso3Au+NQuu73xA==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.9.3"
+      }
+    },
+    "@snyk/cocoapods-lockfile-parser": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.1.0.tgz",
+      "integrity": "sha512-9288PnbsBNW4Q+T8Q7qAYf0LoRXpBPzJ0ZyLKezmTaWynQ4bk4L4l5F3ENqRRPhwPKOv+eFemUggKQiBBtWD4w==",
+      "dev": true,
+      "requires": {
+        "@snyk/dep-graph": "^1.11.0",
+        "@snyk/ruby-semver": "^2.0.4",
+        "@types/js-yaml": "^3.12.1",
+        "core-js": "^3.2.0",
+        "js-yaml": "^3.13.1",
+        "source-map-support": "^0.5.7",
+        "tslib": "^1.10.0"
+      }
+    },
+    "@snyk/composer-lockfile-parser": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.2.0.tgz",
+      "integrity": "sha512-kZT+HTqgNcQMeoE5NM9M3jj463M8zI7ZxqZXLw9WoyVs5JTt9g0qFWxIG1cNwZdGVI+y7tzZbNWw9BlMD1vCCQ==",
+      "dev": true,
+      "requires": {
+        "lodash": "^4.17.13"
+      }
+    },
+    "@snyk/configstore": {
+      "version": "3.2.0-rc1",
+      "resolved": "https://registry.npmjs.org/@snyk/configstore/-/configstore-3.2.0-rc1.tgz",
+      "integrity": "sha512-CV3QggFY8BY3u8PdSSlUGLibqbqCG1zJRmGM2DhnhcxQDRRPTGTP//l7vJphOVsUP1Oe23+UQsj7KRWpRUZiqg==",
+      "dev": true,
+      "requires": {
+        "dot-prop": "^5.2.0",
+        "graceful-fs": "^4.1.2",
+        "make-dir": "^1.0.0",
+        "unique-string": "^1.0.0",
+        "write-file-atomic": "^2.0.0",
+        "xdg-basedir": "^3.0.0"
+      },
+      "dependencies": {
+        "make-dir": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+          "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+          "dev": true,
+          "requires": {
+            "pify": "^3.0.0"
+          }
+        },
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        }
+      }
+    },
+    "@snyk/dep-graph": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/@snyk/dep-graph/-/dep-graph-1.16.1.tgz",
+      "integrity": "sha512-2RbstN/z5A3iTmgDQr0vfpWpqsjcJY54PXXP0gVXTf137QLdvgPEsZikjlofF4qoNO1io5t1VGvf69v9E8UrOw==",
+      "dev": true,
+      "requires": {
+        "graphlib": "^2.1.5",
+        "lodash": "^4.7.14",
+        "object-hash": "^1.3.1",
+        "semver": "^6.0.0",
+        "source-map-support": "^0.5.11",
+        "tslib": "^1.10.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
+      }
+    },
+    "@snyk/gemfile": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@snyk/gemfile/-/gemfile-1.2.0.tgz",
+      "integrity": "sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA==",
+      "dev": true
+    },
+    "@snyk/java-call-graph-builder": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.3.4.tgz",
+      "integrity": "sha512-3KXA5hTqvn6EZBEyMhA5zj0QCzu1WBPVBeUbQ4sNfftbVgfy/OFc9I/NPi39ALA2+tFYl1aKVIy1ECRea4Mc1w==",
+      "dev": true,
+      "requires": {
+        "ci-info": "^2.0.0",
+        "debug": "^4.1.1",
+        "glob": "^7.1.6",
+        "graphlib": "^2.1.8",
+        "jszip": "^3.2.2",
+        "lodash": "^4.17.11",
+        "needle": "^2.3.3",
+        "progress": "^2.0.3",
+        "snyk-config": "^3.0.0",
+        "source-map-support": "^0.5.7",
+        "temp-dir": "^2.0.0",
+        "tslib": "^1.9.3"
+      },
+      "dependencies": {
+        "ci-info": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
+          "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
+          "dev": true
+        },
+        "snyk-config": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/snyk-config/-/snyk-config-3.0.0.tgz",
+          "integrity": "sha512-sXjd7gUqPTmgkhtLowFkFU5J4xyS7tDIRUbHmpW/dvTjgmiH0ujobJzSdaim4W6pbiIf4snkGJsvHM3/wmdR5w==",
+          "dev": true,
+          "requires": {
+            "debug": "^4.1.1",
+            "lodash": "^4.17.15",
+            "nconf": "^0.10.0"
+          }
+        }
+      }
+    },
+    "@snyk/ruby-semver": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@snyk/ruby-semver/-/ruby-semver-2.1.2.tgz",
+      "integrity": "sha512-hYiNnwzNJ5DYM9JydeK3d2rd0lupgB/r5Nh154uazsV6EJn+lcba49X8+pdMMQIBhGQNvLtWLqYpeGfG07WcGQ==",
+      "dev": true,
+      "requires": {
+        "lodash": "^4.17.14"
+      }
+    },
+    "@snyk/snyk-cocoapods-plugin": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.1.1.tgz",
+      "integrity": "sha512-bUo69YaZfBD/vPookJjdskcnUNGNzPbqgBLzjyDqzmG9zCArW1LEU4A8pwfw1mxjp8NzBreex6K74tC8ssIN4g==",
+      "dev": true,
+      "requires": {
+        "@snyk/cli-interface": "1.5.0",
+        "@snyk/cocoapods-lockfile-parser": "3.1.0",
+        "@snyk/dep-graph": "^1.13.1",
+        "source-map-support": "^0.5.7",
+        "tslib": "^1.10.0"
+      },
+      "dependencies": {
+        "@snyk/cli-interface": {
+          "version": "1.5.0",
+          "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-1.5.0.tgz",
+          "integrity": "sha512-+Qo+IO3YOXWgazlo+CKxOuWFLQQdaNCJ9cSfhFQd687/FuesaIxWdInaAdfpsLScq0c6M1ieZslXgiZELSzxbg==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.3"
+          }
+        }
+      }
+    },
+    "@snyk/update-notifier": {
+      "version": "2.5.1-rc2",
+      "resolved": "https://registry.npmjs.org/@snyk/update-notifier/-/update-notifier-2.5.1-rc2.tgz",
+      "integrity": "sha512-dlled3mfpnAt3cQb5hxkFiqfPCj4Yk0xV8Yl5P8PeVv1pUmO7vI4Ka4Mjs4r6CYM5f9kZhviFPQQcWOIDlMRcw==",
+      "dev": true,
+      "requires": {
+        "@snyk/configstore": "3.2.0-rc1",
+        "boxen": "^1.3.0",
+        "chalk": "^2.3.2",
+        "import-lazy": "^2.1.0",
+        "is-ci": "^1.0.10",
+        "is-installed-globally": "^0.1.0",
+        "is-npm": "^1.0.0",
+        "latest-version": "^3.1.0",
+        "semver-diff": "^2.0.0",
+        "xdg-basedir": "^3.0.0"
+      }
+    },
+    "@types/agent-base": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/@types/agent-base/-/agent-base-4.2.0.tgz",
+      "integrity": "sha512-8mrhPstU+ZX0Ugya8tl5DsDZ1I5ZwQzbL/8PA0z8Gj0k9nql7nkaMzmPVLj+l/nixWaliXi+EBiLA8bptw3z7Q==",
+      "dev": true,
+      "requires": {
+        "@types/events": "*",
+        "@types/node": "*"
+      }
+    },
+    "@types/bunyan": {
+      "version": "1.8.6",
+      "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.6.tgz",
+      "integrity": "sha512-YiozPOOsS6bIuz31ilYqR5SlLif4TBWsousN2aCWLi5233nZSX19tFbcQUPdR7xJ8ypPyxkCGNxg0CIV5n9qxQ==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/color-name": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
+      "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
+      "dev": true
+    },
+    "@types/debug": {
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz",
+      "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==",
+      "dev": true
+    },
+    "@types/events": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
+      "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
+      "dev": true
+    },
+    "@types/glob": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
+      "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
+      "dev": true,
+      "requires": {
+        "@types/events": "*",
+        "@types/minimatch": "*",
+        "@types/node": "*"
+      }
+    },
+    "@types/js-yaml": {
+      "version": "3.12.3",
+      "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.3.tgz",
+      "integrity": "sha512-otRe77JNNWzoVGLKw8TCspKswRoQToys4tuL6XYVBFxjgeM0RUrx7m3jkaTdxILxeGry3zM8mGYkGXMeQ02guA==",
+      "dev": true
+    },
+    "@types/minimatch": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
+      "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
+      "dev": true
+    },
+    "@types/node": {
+      "version": "13.13.2",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz",
+      "integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==",
+      "dev": true
+    },
+    "@types/restify": {
+      "version": "4.3.6",
+      "resolved": "https://registry.npmjs.org/@types/restify/-/restify-4.3.6.tgz",
+      "integrity": "sha512-4l4f0EXnleXQttlhRCXtTuJ8UelsKiAKIK2AAEd2epBHu41aEbM0U2z6E5tUrNwlbxz7qaNBISduGMeg+G3PaA==",
+      "dev": true,
+      "requires": {
+        "@types/bunyan": "*",
+        "@types/node": "*"
+      }
+    },
+    "@types/semver": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz",
+      "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==",
+      "dev": true
+    },
+    "@types/xml2js": {
+      "version": "0.4.3",
+      "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.3.tgz",
+      "integrity": "sha512-Pv2HGRE4gWLs31In7nsyXEH4uVVsd0HNV9i2dyASvtDIlOtSTr1eczPLDpdEuyv5LWH5LT20GIXwPjkshKWI1g==",
+      "dev": true,
+      "requires": {
+        "@types/events": "*",
+        "@types/node": "*"
+      }
+    },
+    "@vue/component-compiler-utils": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.1.2.tgz",
+      "integrity": "sha512-QLq9z8m79mCinpaEeSURhnNCN6djxpHw0lpP/bodMlt5kALfONpryMthvnrQOlTcIKoF+VoPi+lPHUYeDFPXug==",
+      "requires": {
+        "consolidate": "^0.15.1",
+        "hash-sum": "^1.0.2",
+        "lru-cache": "^4.1.2",
+        "merge-source-map": "^1.1.0",
+        "postcss": "^7.0.14",
+        "postcss-selector-parser": "^6.0.2",
+        "prettier": "^1.18.2",
+        "source-map": "~0.6.1",
+        "vue-template-es2015-compiler": "^1.9.0"
+      },
+      "dependencies": {
+        "prettier": {
+          "version": "1.19.1",
+          "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
+          "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
+          "optional": true
+        }
+      }
+    },
+    "@webassemblyjs/ast": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz",
+      "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/helper-module-context": "1.9.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+        "@webassemblyjs/wast-parser": "1.9.0"
+      }
+    },
+    "@webassemblyjs/floating-point-hex-parser": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz",
+      "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-api-error": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz",
+      "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-buffer": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz",
+      "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-code-frame": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz",
+      "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/wast-printer": "1.9.0"
+      }
+    },
+    "@webassemblyjs/helper-fsm": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz",
+      "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-module-context": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz",
+      "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0"
+      }
+    },
+    "@webassemblyjs/helper-wasm-bytecode": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz",
+      "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==",
+      "dev": true
+    },
+    "@webassemblyjs/helper-wasm-section": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz",
+      "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/helper-buffer": "1.9.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+        "@webassemblyjs/wasm-gen": "1.9.0"
+      }
+    },
+    "@webassemblyjs/ieee754": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz",
+      "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==",
+      "dev": true,
+      "requires": {
+        "@xtuc/ieee754": "^1.2.0"
+      }
+    },
+    "@webassemblyjs/leb128": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz",
+      "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==",
+      "dev": true,
+      "requires": {
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "@webassemblyjs/utf8": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz",
+      "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==",
+      "dev": true
+    },
+    "@webassemblyjs/wasm-edit": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz",
+      "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/helper-buffer": "1.9.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+        "@webassemblyjs/helper-wasm-section": "1.9.0",
+        "@webassemblyjs/wasm-gen": "1.9.0",
+        "@webassemblyjs/wasm-opt": "1.9.0",
+        "@webassemblyjs/wasm-parser": "1.9.0",
+        "@webassemblyjs/wast-printer": "1.9.0"
+      }
+    },
+    "@webassemblyjs/wasm-gen": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz",
+      "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+        "@webassemblyjs/ieee754": "1.9.0",
+        "@webassemblyjs/leb128": "1.9.0",
+        "@webassemblyjs/utf8": "1.9.0"
+      }
+    },
+    "@webassemblyjs/wasm-opt": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz",
+      "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/helper-buffer": "1.9.0",
+        "@webassemblyjs/wasm-gen": "1.9.0",
+        "@webassemblyjs/wasm-parser": "1.9.0"
+      }
+    },
+    "@webassemblyjs/wasm-parser": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz",
+      "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/helper-api-error": "1.9.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
+        "@webassemblyjs/ieee754": "1.9.0",
+        "@webassemblyjs/leb128": "1.9.0",
+        "@webassemblyjs/utf8": "1.9.0"
+      }
+    },
+    "@webassemblyjs/wast-parser": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz",
+      "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/floating-point-hex-parser": "1.9.0",
+        "@webassemblyjs/helper-api-error": "1.9.0",
+        "@webassemblyjs/helper-code-frame": "1.9.0",
+        "@webassemblyjs/helper-fsm": "1.9.0",
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "@webassemblyjs/wast-printer": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz",
+      "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/wast-parser": "1.9.0",
+        "@xtuc/long": "4.2.2"
+      }
+    },
+    "@xtuc/ieee754": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+      "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+      "dev": true
+    },
+    "@xtuc/long": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+      "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+      "dev": true
+    },
+    "@yarnpkg/lockfile": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
+      "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
+      "dev": true
+    },
+    "abbrev": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+      "dev": true
+    },
+    "accepts": {
+      "version": "1.3.7",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+      "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+      "dev": true,
+      "requires": {
+        "mime-types": "~2.1.24",
+        "negotiator": "0.6.2"
+      }
+    },
+    "acorn": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
+      "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
+      "dev": true
+    },
+    "acorn-jsx": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
+      "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
+      "dev": true
+    },
+    "acorn-walk": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz",
+      "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==",
+      "dev": true
+    },
+    "agent-base": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
+      "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
+      "dev": true,
+      "requires": {
+        "es6-promisify": "^5.0.0"
+      }
+    },
+    "ajv": {
+      "version": "6.12.2",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
+      "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
+      "dev": true,
+      "requires": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "ajv-errors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
+      "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
+      "dev": true
+    },
+    "ajv-keywords": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
+      "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
+      "dev": true
+    },
+    "amdefine": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+      "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
+      "dev": true
+    },
+    "ansi-align": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz",
+      "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=",
+      "dev": true,
+      "requires": {
+        "string-width": "^2.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+          "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+          "dev": true,
+          "requires": {
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^4.0.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^3.0.0"
+          }
+        }
+      }
+    },
+    "ansi-colors": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
+      "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==",
+      "dev": true
+    },
+    "ansi-escapes": {
+      "version": "4.3.1",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
+      "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
+      "dev": true,
+      "requires": {
+        "type-fest": "^0.11.0"
+      },
+      "dependencies": {
+        "type-fest": {
+          "version": "0.11.0",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
+          "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==",
+          "dev": true
+        }
+      }
+    },
+    "ansi-html": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
+      "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=",
+      "dev": true
+    },
+    "ansi-regex": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+      "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+    },
+    "ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "requires": {
+        "color-convert": "^1.9.0"
+      }
+    },
+    "ansicolors": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz",
+      "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=",
+      "dev": true
+    },
+    "anymatch": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+      "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+      "dev": true,
+      "requires": {
+        "micromatch": "^3.1.4",
+        "normalize-path": "^2.1.1"
+      },
+      "dependencies": {
+        "normalize-path": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+          "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+          "dev": true,
+          "requires": {
+            "remove-trailing-separator": "^1.0.1"
+          }
+        }
+      }
+    },
+    "aproba": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+      "dev": true
+    },
+    "archy": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
+      "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=",
+      "dev": true
+    },
+    "are-we-there-yet": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
+      "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+      "dev": true,
+      "requires": {
+        "delegates": "^1.0.0",
+        "readable-stream": "^2.0.6"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "dev": true,
+      "requires": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "arr-diff": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+      "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+      "dev": true
+    },
+    "arr-flatten": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+      "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+      "dev": true
+    },
+    "arr-union": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+      "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+      "dev": true
+    },
+    "array-find-index": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+      "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+      "dev": true
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
+      "dev": true
+    },
+    "array-includes": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz",
+      "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0",
+        "is-string": "^1.0.5"
+      }
+    },
+    "array-union": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+      "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+      "dev": true,
+      "requires": {
+        "array-uniq": "^1.0.1"
+      }
+    },
+    "array-uniq": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+      "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+      "dev": true
+    },
+    "array-unique": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+      "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+      "dev": true
+    },
+    "array.prototype.flat": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz",
+      "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1"
+      }
+    },
+    "asap": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
+      "dev": true
+    },
+    "asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "dev": true,
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
+    "asn1.js": {
+      "version": "4.10.1",
+      "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
+      "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "assert": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
+      "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
+      "dev": true,
+      "requires": {
+        "object-assign": "^4.1.1",
+        "util": "0.10.3"
+      },
+      "dependencies": {
+        "inherits": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+          "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
+          "dev": true
+        },
+        "util": {
+          "version": "0.10.3",
+          "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+          "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+          "dev": true,
+          "requires": {
+            "inherits": "2.0.1"
+          }
+        }
+      }
+    },
+    "assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+      "dev": true
+    },
+    "assign-symbols": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+      "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+      "dev": true
+    },
+    "ast-types": {
+      "version": "0.13.3",
+      "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.3.tgz",
+      "integrity": "sha512-XTZ7xGML849LkQP86sWdQzfhwbt3YwIO6MqbX9mUNYY98VKaaVZP7YNNm70IpwecbkkxmfC5IYAzOQ/2p29zRA==",
+      "dev": true
+    },
+    "astral-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+      "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+      "dev": true
+    },
+    "async": {
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+      "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+      "dev": true
+    },
+    "async-each": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
+      "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
+      "dev": true
+    },
+    "async-foreach": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
+      "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=",
+      "dev": true
+    },
+    "async-limiter": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+      "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
+      "dev": true
+    },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+      "dev": true
+    },
+    "atob": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+      "dev": true
+    },
+    "aws-sign2": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+      "dev": true
+    },
+    "aws4": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
+      "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
+      "dev": true
+    },
+    "babel-eslint": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
+      "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.0.0",
+        "@babel/parser": "^7.7.0",
+        "@babel/traverse": "^7.7.0",
+        "@babel/types": "^7.7.0",
+        "eslint-visitor-keys": "^1.0.0",
+        "resolve": "^1.12.0"
+      }
+    },
+    "babel-loader": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz",
+      "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==",
+      "dev": true,
+      "requires": {
+        "find-cache-dir": "^2.1.0",
+        "loader-utils": "^1.4.0",
+        "mkdirp": "^0.5.3",
+        "pify": "^4.0.1",
+        "schema-utils": "^2.6.5"
+      },
+      "dependencies": {
+        "big.js": {
+          "version": "5.2.2",
+          "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+          "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+          "dev": true
+        },
+        "emojis-list": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+          "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+          "dev": true
+        },
+        "json5": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+          "dev": true,
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        },
+        "loader-utils": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+          "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^1.0.1"
+          }
+        }
+      }
+    },
+    "babel-plugin-dynamic-import-node": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz",
+      "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==",
+      "dev": true,
+      "requires": {
+        "object.assign": "^4.1.0"
+      }
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "base": {
+      "version": "0.11.2",
+      "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+      "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+      "dev": true,
+      "requires": {
+        "cache-base": "^1.0.1",
+        "class-utils": "^0.3.5",
+        "component-emitter": "^1.2.1",
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.1",
+        "mixin-deep": "^1.2.0",
+        "pascalcase": "^0.1.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "base64-js": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
+      "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
+      "dev": true
+    },
+    "batch": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
+      "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
+      "dev": true
+    },
+    "bcrypt-pbkdf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "dev": true,
+      "requires": {
+        "tweetnacl": "^0.14.3"
+      }
+    },
+    "bfj": {
+      "version": "6.1.2",
+      "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz",
+      "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^3.5.5",
+        "check-types": "^8.0.3",
+        "hoopy": "^0.1.4",
+        "tryer": "^1.0.1"
+      }
+    },
+    "big.js": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz",
+      "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q=="
+    },
+    "binary-extensions": {
+      "version": "1.13.1",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
+      "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
+      "dev": true
+    },
+    "bindings": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "file-uri-to-path": "1.0.0"
+      }
+    },
+    "biskviit": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/biskviit/-/biskviit-1.0.1.tgz",
+      "integrity": "sha1-A3oM1LcbnjMf2QoRIt4X3EnkIKc=",
+      "dev": true,
+      "requires": {
+        "psl": "^1.1.7"
+      }
+    },
+    "bl": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
+      "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
+      "dev": true,
+      "requires": {
+        "buffer": "^5.5.0",
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.4.0"
+      }
+    },
+    "block-stream": {
+      "version": "0.0.9",
+      "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
+      "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
+      "dev": true,
+      "requires": {
+        "inherits": "~2.0.0"
+      }
+    },
+    "bluebird": {
+      "version": "3.7.2",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
+    },
+    "bn.js": {
+      "version": "4.11.8",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+      "dev": true
+    },
+    "body-parser": {
+      "version": "1.19.0",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+      "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+      "dev": true,
+      "requires": {
+        "bytes": "3.1.0",
+        "content-type": "~1.0.4",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "http-errors": "1.7.2",
+        "iconv-lite": "0.4.24",
+        "on-finished": "~2.3.0",
+        "qs": "6.7.0",
+        "raw-body": "2.4.0",
+        "type-is": "~1.6.17"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "http-errors": {
+          "version": "1.7.2",
+          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+          "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+          "dev": true,
+          "requires": {
+            "depd": "~1.1.2",
+            "inherits": "2.0.3",
+            "setprototypeof": "1.1.1",
+            "statuses": ">= 1.5.0 < 2",
+            "toidentifier": "1.0.0"
+          }
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        },
+        "qs": {
+          "version": "6.7.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+          "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
+          "dev": true
+        },
+        "raw-body": {
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+          "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+          "dev": true,
+          "requires": {
+            "bytes": "3.1.0",
+            "http-errors": "1.7.2",
+            "iconv-lite": "0.4.24",
+            "unpipe": "1.0.0"
+          }
+        }
+      }
+    },
+    "bonjour": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz",
+      "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
+      "dev": true,
+      "requires": {
+        "array-flatten": "^2.1.0",
+        "deep-equal": "^1.0.1",
+        "dns-equal": "^1.0.0",
+        "dns-txt": "^2.0.2",
+        "multicast-dns": "^6.0.1",
+        "multicast-dns-service-types": "^1.1.0"
+      },
+      "dependencies": {
+        "array-flatten": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
+          "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==",
+          "dev": true
+        }
+      }
+    },
+    "boolbase": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+      "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
+    },
+    "boxen": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz",
+      "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==",
+      "dev": true,
+      "requires": {
+        "ansi-align": "^2.0.0",
+        "camelcase": "^4.0.0",
+        "chalk": "^2.0.1",
+        "cli-boxes": "^1.0.0",
+        "string-width": "^2.0.0",
+        "term-size": "^1.2.0",
+        "widest-line": "^2.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "camelcase": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+          "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+          "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+          "dev": true,
+          "requires": {
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^4.0.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^3.0.0"
+          }
+        }
+      }
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "braces": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+      "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+      "dev": true,
+      "requires": {
+        "arr-flatten": "^1.1.0",
+        "array-unique": "^0.3.2",
+        "extend-shallow": "^2.0.1",
+        "fill-range": "^4.0.0",
+        "isobject": "^3.0.1",
+        "repeat-element": "^1.1.2",
+        "snapdragon": "^0.8.1",
+        "snapdragon-node": "^2.0.1",
+        "split-string": "^3.0.2",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "brorand": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+      "dev": true
+    },
+    "browserify-aes": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+      "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+      "dev": true,
+      "requires": {
+        "buffer-xor": "^1.0.3",
+        "cipher-base": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.3",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "browserify-cipher": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+      "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+      "dev": true,
+      "requires": {
+        "browserify-aes": "^1.0.4",
+        "browserify-des": "^1.0.0",
+        "evp_bytestokey": "^1.0.0"
+      }
+    },
+    "browserify-des": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+      "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+      "dev": true,
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "des.js": "^1.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "browserify-rsa": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+      "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "randombytes": "^2.0.1"
+      }
+    },
+    "browserify-sign": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
+      "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.1",
+        "browserify-rsa": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "create-hmac": "^1.1.2",
+        "elliptic": "^6.0.0",
+        "inherits": "^2.0.1",
+        "parse-asn1": "^5.0.0"
+      }
+    },
+    "browserify-zlib": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+      "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+      "dev": true,
+      "requires": {
+        "pako": "~1.0.5"
+      }
+    },
+    "browserslist": {
+      "version": "4.12.0",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz",
+      "integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==",
+      "dev": true,
+      "requires": {
+        "caniuse-lite": "^1.0.30001043",
+        "electron-to-chromium": "^1.3.413",
+        "node-releases": "^1.1.53",
+        "pkg-up": "^2.0.0"
+      }
+    },
+    "buffer": {
+      "version": "5.6.0",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
+      "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
+      "dev": true,
+      "requires": {
+        "base64-js": "^1.0.2",
+        "ieee754": "^1.1.4"
+      }
+    },
+    "buffer-from": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+      "dev": true
+    },
+    "buffer-indexof": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
+      "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==",
+      "dev": true
+    },
+    "buffer-xor": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+      "dev": true
+    },
+    "builtin-status-codes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+      "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+      "dev": true
+    },
+    "bytes": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+      "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
+      "dev": true
+    },
+    "cacache": {
+      "version": "12.0.4",
+      "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
+      "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^3.5.5",
+        "chownr": "^1.1.1",
+        "figgy-pudding": "^3.5.1",
+        "glob": "^7.1.4",
+        "graceful-fs": "^4.1.15",
+        "infer-owner": "^1.0.3",
+        "lru-cache": "^5.1.1",
+        "mississippi": "^3.0.0",
+        "mkdirp": "^0.5.1",
+        "move-concurrently": "^1.0.1",
+        "promise-inflight": "^1.0.1",
+        "rimraf": "^2.6.3",
+        "ssri": "^6.0.1",
+        "unique-filename": "^1.1.1",
+        "y18n": "^4.0.0"
+      },
+      "dependencies": {
+        "lru-cache": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+          "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+          "dev": true,
+          "requires": {
+            "yallist": "^3.0.2"
+          }
+        },
+        "y18n": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+          "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+          "dev": true
+        },
+        "yallist": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+          "dev": true
+        }
+      }
+    },
+    "cache-base": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+      "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+      "dev": true,
+      "requires": {
+        "collection-visit": "^1.0.0",
+        "component-emitter": "^1.2.1",
+        "get-value": "^2.0.6",
+        "has-value": "^1.0.0",
+        "isobject": "^3.0.1",
+        "set-value": "^2.0.0",
+        "to-object-path": "^0.3.0",
+        "union-value": "^1.0.0",
+        "unset-value": "^1.0.0"
+      }
+    },
+    "callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true
+    },
+    "camel-case": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
+      "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
+      "requires": {
+        "no-case": "^2.2.0",
+        "upper-case": "^1.1.1"
+      }
+    },
+    "camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "dev": true
+    },
+    "camelcase-keys": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+      "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+      "dev": true,
+      "requires": {
+        "camelcase": "^2.0.0",
+        "map-obj": "^1.0.0"
+      },
+      "dependencies": {
+        "camelcase": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+          "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+          "dev": true
+        }
+      }
+    },
+    "caniuse-lite": {
+      "version": "1.0.30001046",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001046.tgz",
+      "integrity": "sha512-CsGjBRYWG6FvgbyGy+hBbaezpwiqIOLkxQPY4A4Ea49g1eNsnQuESB+n4QM0BKii1j80MyJ26Ir5ywTQkbRE4g==",
+      "dev": true
+    },
+    "capture-stack-trace": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz",
+      "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==",
+      "dev": true
+    },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+      "dev": true
+    },
+    "chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      },
+      "dependencies": {
+        "supports-color": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        }
+      }
+    },
+    "chardet": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+      "dev": true
+    },
+    "charenc": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
+      "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
+    },
+    "chart.js": {
+      "version": "2.9.3",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz",
+      "integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==",
+      "requires": {
+        "chartjs-color": "^2.1.0",
+        "moment": "^2.10.2"
+      }
+    },
+    "chartjs-adapter-date-fns": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-0.1.2.tgz",
+      "integrity": "sha512-Q/k6OmGtcJcPG7YHAF2TmKD0egsDd4d1fVKMCRtQkzBDhcAPVKraVGk2Th1JzT4j5cQ8wS6VTtnbTsq+6M2fag=="
+    },
+    "chartjs-color": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz",
+      "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==",
+      "requires": {
+        "chartjs-color-string": "^0.6.0",
+        "color-convert": "^1.9.3"
+      }
+    },
+    "chartjs-color-string": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
+      "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
+      "requires": {
+        "color-name": "^1.0.0"
+      }
+    },
+    "check-types": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz",
+      "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==",
+      "dev": true
+    },
+    "chokidar": {
+      "version": "2.1.8",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
+      "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
+      "dev": true,
+      "requires": {
+        "anymatch": "^2.0.0",
+        "async-each": "^1.0.1",
+        "braces": "^2.3.2",
+        "fsevents": "^1.2.7",
+        "glob-parent": "^3.1.0",
+        "inherits": "^2.0.3",
+        "is-binary-path": "^1.0.0",
+        "is-glob": "^4.0.0",
+        "normalize-path": "^3.0.0",
+        "path-is-absolute": "^1.0.0",
+        "readdirp": "^2.2.1",
+        "upath": "^1.1.1"
+      },
+      "dependencies": {
+        "fsevents": {
+          "version": "1.2.12",
+          "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz",
+          "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==",
+          "dev": true,
+          "optional": true,
+          "requires": {
+            "bindings": "^1.5.0",
+            "nan": "^2.12.1",
+            "node-pre-gyp": "*"
+          },
+          "dependencies": {
+            "abbrev": {
+              "version": "1.1.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "ansi-regex": {
+              "version": "2.1.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "aproba": {
+              "version": "1.2.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "are-we-there-yet": {
+              "version": "1.1.5",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "delegates": "^1.0.0",
+                "readable-stream": "^2.0.6"
+              }
+            },
+            "balanced-match": {
+              "version": "1.0.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "brace-expansion": {
+              "version": "1.1.11",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
+              }
+            },
+            "chownr": {
+              "version": "1.1.4",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "code-point-at": {
+              "version": "1.1.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "concat-map": {
+              "version": "0.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "console-control-strings": {
+              "version": "1.1.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "core-util-is": {
+              "version": "1.0.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "debug": {
+              "version": "3.2.6",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "ms": "^2.1.1"
+              }
+            },
+            "deep-extend": {
+              "version": "0.6.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "delegates": {
+              "version": "1.0.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "detect-libc": {
+              "version": "1.0.3",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "fs-minipass": {
+              "version": "1.2.7",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "minipass": "^2.6.0"
+              }
+            },
+            "fs.realpath": {
+              "version": "1.0.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "gauge": {
+              "version": "2.7.4",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "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"
+              }
+            },
+            "glob": {
+              "version": "7.1.6",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "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"
+              }
+            },
+            "has-unicode": {
+              "version": "2.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "iconv-lite": {
+              "version": "0.4.24",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "safer-buffer": ">= 2.1.2 < 3"
+              }
+            },
+            "ignore-walk": {
+              "version": "3.0.3",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "minimatch": "^3.0.4"
+              }
+            },
+            "inflight": {
+              "version": "1.0.6",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "once": "^1.3.0",
+                "wrappy": "1"
+              }
+            },
+            "inherits": {
+              "version": "2.0.4",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "ini": {
+              "version": "1.3.5",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "is-fullwidth-code-point": {
+              "version": "1.0.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "number-is-nan": "^1.0.0"
+              }
+            },
+            "isarray": {
+              "version": "1.0.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "minimatch": {
+              "version": "3.0.4",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "brace-expansion": "^1.1.7"
+              }
+            },
+            "minimist": {
+              "version": "1.2.5",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "minipass": {
+              "version": "2.9.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "safe-buffer": "^5.1.2",
+                "yallist": "^3.0.0"
+              }
+            },
+            "minizlib": {
+              "version": "1.3.3",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "minipass": "^2.9.0"
+              }
+            },
+            "mkdirp": {
+              "version": "0.5.3",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "minimist": "^1.2.5"
+              }
+            },
+            "ms": {
+              "version": "2.1.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "needle": {
+              "version": "2.3.3",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "debug": "^3.2.6",
+                "iconv-lite": "^0.4.4",
+                "sax": "^1.2.4"
+              }
+            },
+            "node-pre-gyp": {
+              "version": "0.14.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "detect-libc": "^1.0.2",
+                "mkdirp": "^0.5.1",
+                "needle": "^2.2.1",
+                "nopt": "^4.0.1",
+                "npm-packlist": "^1.1.6",
+                "npmlog": "^4.0.2",
+                "rc": "^1.2.7",
+                "rimraf": "^2.6.1",
+                "semver": "^5.3.0",
+                "tar": "^4.4.2"
+              }
+            },
+            "nopt": {
+              "version": "4.0.3",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "abbrev": "1",
+                "osenv": "^0.1.4"
+              }
+            },
+            "npm-bundled": {
+              "version": "1.1.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "npm-normalize-package-bin": "^1.0.1"
+              }
+            },
+            "npm-normalize-package-bin": {
+              "version": "1.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "npm-packlist": {
+              "version": "1.4.8",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "ignore-walk": "^3.0.1",
+                "npm-bundled": "^1.0.1",
+                "npm-normalize-package-bin": "^1.0.1"
+              }
+            },
+            "npmlog": {
+              "version": "4.1.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "are-we-there-yet": "~1.1.2",
+                "console-control-strings": "~1.1.0",
+                "gauge": "~2.7.3",
+                "set-blocking": "~2.0.0"
+              }
+            },
+            "number-is-nan": {
+              "version": "1.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "object-assign": {
+              "version": "4.1.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "once": {
+              "version": "1.4.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "wrappy": "1"
+              }
+            },
+            "os-homedir": {
+              "version": "1.0.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "os-tmpdir": {
+              "version": "1.0.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "osenv": {
+              "version": "0.1.5",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "os-homedir": "^1.0.0",
+                "os-tmpdir": "^1.0.0"
+              }
+            },
+            "path-is-absolute": {
+              "version": "1.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "process-nextick-args": {
+              "version": "2.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "rc": {
+              "version": "1.2.8",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "deep-extend": "^0.6.0",
+                "ini": "~1.3.0",
+                "minimist": "^1.2.0",
+                "strip-json-comments": "~2.0.1"
+              }
+            },
+            "readable-stream": {
+              "version": "2.3.7",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "core-util-is": "~1.0.0",
+                "inherits": "~2.0.3",
+                "isarray": "~1.0.0",
+                "process-nextick-args": "~2.0.0",
+                "safe-buffer": "~5.1.1",
+                "string_decoder": "~1.1.1",
+                "util-deprecate": "~1.0.1"
+              }
+            },
+            "rimraf": {
+              "version": "2.7.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "glob": "^7.1.3"
+              }
+            },
+            "safe-buffer": {
+              "version": "5.1.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "safer-buffer": {
+              "version": "2.1.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "sax": {
+              "version": "1.2.4",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "semver": {
+              "version": "5.7.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "set-blocking": {
+              "version": "2.0.0",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "signal-exit": {
+              "version": "3.0.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "string-width": {
+              "version": "1.0.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "code-point-at": "^1.0.0",
+                "is-fullwidth-code-point": "^1.0.0",
+                "strip-ansi": "^3.0.0"
+              }
+            },
+            "string_decoder": {
+              "version": "1.1.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "safe-buffer": "~5.1.0"
+              }
+            },
+            "strip-ansi": {
+              "version": "3.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "ansi-regex": "^2.0.0"
+              }
+            },
+            "strip-json-comments": {
+              "version": "2.0.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "tar": {
+              "version": "4.4.13",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "chownr": "^1.1.1",
+                "fs-minipass": "^1.2.5",
+                "minipass": "^2.8.6",
+                "minizlib": "^1.2.1",
+                "mkdirp": "^0.5.0",
+                "safe-buffer": "^5.1.2",
+                "yallist": "^3.0.3"
+              }
+            },
+            "util-deprecate": {
+              "version": "1.0.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "wide-align": {
+              "version": "1.1.3",
+              "bundled": true,
+              "dev": true,
+              "optional": true,
+              "requires": {
+                "string-width": "^1.0.2 || 2"
+              }
+            },
+            "wrappy": {
+              "version": "1.0.2",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            },
+            "yallist": {
+              "version": "3.1.1",
+              "bundled": true,
+              "dev": true,
+              "optional": true
+            }
+          }
+        },
+        "glob-parent": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+          "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+          "dev": true,
+          "requires": {
+            "is-glob": "^3.1.0",
+            "path-dirname": "^1.0.0"
+          },
+          "dependencies": {
+            "is-glob": {
+              "version": "3.1.0",
+              "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+              "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+              "dev": true,
+              "requires": {
+                "is-extglob": "^2.1.0"
+              }
+            }
+          }
+        }
+      }
+    },
+    "chownr": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+      "dev": true
+    },
+    "chrome-trace-event": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz",
+      "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "ci-info": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
+      "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
+      "dev": true
+    },
+    "cipher-base": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+      "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "class-utils": {
+      "version": "0.3.6",
+      "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+      "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+      "dev": true,
+      "requires": {
+        "arr-union": "^3.1.0",
+        "define-property": "^0.2.5",
+        "isobject": "^3.0.0",
+        "static-extend": "^0.1.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        }
+      }
+    },
+    "clean-css": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
+      "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==",
+      "requires": {
+        "source-map": "~0.6.0"
+      }
+    },
+    "cli-boxes": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
+      "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=",
+      "dev": true
+    },
+    "cli-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
+      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
+      "dev": true,
+      "requires": {
+        "restore-cursor": "^3.1.0"
+      }
+    },
+    "cli-spinner": {
+      "version": "0.2.10",
+      "resolved": "https://registry.npmjs.org/cli-spinner/-/cli-spinner-0.2.10.tgz",
+      "integrity": "sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q==",
+      "dev": true
+    },
+    "cli-width": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
+      "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
+      "dev": true
+    },
+    "cliui": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
+      "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+      "dev": true,
+      "requires": {
+        "string-width": "^1.0.1",
+        "strip-ansi": "^3.0.1",
+        "wrap-ansi": "^2.0.0"
+      },
+      "dependencies": {
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "dev": true,
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "dev": true,
+          "requires": {
+            "code-point-at": "^1.0.0",
+            "is-fullwidth-code-point": "^1.0.0",
+            "strip-ansi": "^3.0.0"
+          }
+        }
+      }
+    },
+    "clone-deep": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+      "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
+      "dev": true,
+      "requires": {
+        "is-plain-object": "^2.0.4",
+        "kind-of": "^6.0.2",
+        "shallow-clone": "^3.0.0"
+      }
+    },
+    "co": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+      "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+      "dev": true
+    },
+    "code-point-at": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+      "dev": true
+    },
+    "collection-visit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+      "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+      "dev": true,
+      "requires": {
+        "map-visit": "^1.0.0",
+        "object-visit": "^1.0.0"
+      }
+    },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "requires": {
+        "color-name": "1.1.3"
+      },
+      "dependencies": {
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+        }
+      }
+    },
+    "color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+    },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dev": true,
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
+    "commander": {
+      "version": "2.17.1",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
+      "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
+    },
+    "commondir": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+      "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+      "dev": true
+    },
+    "component-emitter": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+      "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
+      "dev": true
+    },
+    "compressible": {
+      "version": "2.0.18",
+      "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+      "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+      "dev": true,
+      "requires": {
+        "mime-db": ">= 1.43.0 < 2"
+      }
+    },
+    "compression": {
+      "version": "1.7.4",
+      "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
+      "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
+      "dev": true,
+      "requires": {
+        "accepts": "~1.3.5",
+        "bytes": "3.0.0",
+        "compressible": "~2.0.16",
+        "debug": "2.6.9",
+        "on-headers": "~1.0.2",
+        "safe-buffer": "5.1.2",
+        "vary": "~1.1.2"
+      },
+      "dependencies": {
+        "bytes": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+          "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
+          "dev": true
+        },
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        }
+      }
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "concat-stream": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.2.2",
+        "typedarray": "^0.0.6"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "config": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/config/-/config-3.3.1.tgz",
+      "integrity": "sha512-+2/KaaaAzdwUBE3jgZON11L1ggLLhpf2FsGrfqYFHZW22ySGv/HqYIXrBwKKvn+XZh1UBUjHwAcrfsSkSygT+Q==",
+      "requires": {
+        "json5": "^2.1.1"
+      }
+    },
+    "confusing-browser-globals": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz",
+      "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw=="
+    },
+    "connect-history-api-fallback": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz",
+      "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
+      "dev": true
+    },
+    "console-browserify": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
+      "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
+      "dev": true
+    },
+    "console-control-strings": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+      "dev": true
+    },
+    "consolidate": {
+      "version": "0.15.1",
+      "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz",
+      "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==",
+      "requires": {
+        "bluebird": "^3.1.1"
+      }
+    },
+    "constants-browserify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+      "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+      "dev": true
+    },
+    "contains-path": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
+      "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
+      "dev": true
+    },
+    "content-disposition": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+      "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "5.1.2"
+      },
+      "dependencies": {
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        }
+      }
+    },
+    "content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+      "dev": true
+    },
+    "convert-source-map": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
+      "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "~5.1.1"
+      },
+      "dependencies": {
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        }
+      }
+    },
+    "cookie": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+      "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
+      "dev": true
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
+      "dev": true
+    },
+    "copy-concurrently": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
+      "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.1.1",
+        "fs-write-stream-atomic": "^1.0.8",
+        "iferr": "^0.1.5",
+        "mkdirp": "^0.5.1",
+        "rimraf": "^2.5.4",
+        "run-queue": "^1.0.0"
+      }
+    },
+    "copy-descriptor": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+      "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+      "dev": true
+    },
+    "core-js": {
+      "version": "3.6.5",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz",
+      "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==",
+      "dev": true
+    },
+    "core-js-compat": {
+      "version": "3.6.5",
+      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz",
+      "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==",
+      "dev": true,
+      "requires": {
+        "browserslist": "^4.8.5",
+        "semver": "7.0.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "7.0.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
+          "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
+          "dev": true
+        }
+      }
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+      "dev": true
+    },
+    "create-ecdh": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
+      "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "elliptic": "^6.0.0"
+      }
+    },
+    "create-error-class": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
+      "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=",
+      "dev": true,
+      "requires": {
+        "capture-stack-trace": "^1.0.0"
+      }
+    },
+    "create-hash": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+      "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+      "dev": true,
+      "requires": {
+        "cipher-base": "^1.0.1",
+        "inherits": "^2.0.1",
+        "md5.js": "^1.3.4",
+        "ripemd160": "^2.0.1",
+        "sha.js": "^2.4.0"
+      }
+    },
+    "create-hmac": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+      "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+      "dev": true,
+      "requires": {
+        "cipher-base": "^1.0.3",
+        "create-hash": "^1.1.0",
+        "inherits": "^2.0.1",
+        "ripemd160": "^2.0.0",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "cross-spawn": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+      "dev": true,
+      "requires": {
+        "nice-try": "^1.0.4",
+        "path-key": "^2.0.1",
+        "semver": "^5.5.0",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      }
+    },
+    "crypt": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
+      "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
+    },
+    "crypto-browserify": {
+      "version": "3.12.0",
+      "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+      "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+      "dev": true,
+      "requires": {
+        "browserify-cipher": "^1.0.0",
+        "browserify-sign": "^4.0.0",
+        "create-ecdh": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "create-hmac": "^1.1.0",
+        "diffie-hellman": "^5.0.0",
+        "inherits": "^2.0.1",
+        "pbkdf2": "^3.0.3",
+        "public-encrypt": "^4.0.0",
+        "randombytes": "^2.0.0",
+        "randomfill": "^1.0.3"
+      }
+    },
+    "crypto-random-string": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
+      "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=",
+      "dev": true
+    },
+    "css-loader": {
+      "version": "3.5.3",
+      "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.5.3.tgz",
+      "integrity": "sha512-UEr9NH5Lmi7+dguAm+/JSPovNjYbm2k3TK58EiwQHzOHH5Jfq1Y+XoP2bQO6TMn7PptMd0opxxedAWcaSTRKHw==",
+      "dev": true,
+      "requires": {
+        "camelcase": "^5.3.1",
+        "cssesc": "^3.0.0",
+        "icss-utils": "^4.1.1",
+        "loader-utils": "^1.2.3",
+        "normalize-path": "^3.0.0",
+        "postcss": "^7.0.27",
+        "postcss-modules-extract-imports": "^2.0.0",
+        "postcss-modules-local-by-default": "^3.0.2",
+        "postcss-modules-scope": "^2.2.0",
+        "postcss-modules-values": "^3.0.0",
+        "postcss-value-parser": "^4.0.3",
+        "schema-utils": "^2.6.6",
+        "semver": "^6.3.0"
+      },
+      "dependencies": {
+        "big.js": {
+          "version": "5.2.2",
+          "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+          "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+          "dev": true
+        },
+        "emojis-list": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+          "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+          "dev": true
+        },
+        "json5": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+          "dev": true,
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        },
+        "loader-utils": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+          "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^1.0.1"
+          }
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
+      }
+    },
+    "css-select": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
+      "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
+      "requires": {
+        "boolbase": "~1.0.0",
+        "css-what": "2.1",
+        "domutils": "1.5.1",
+        "nth-check": "~1.0.1"
+      }
+    },
+    "css-what": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
+      "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg=="
+    },
+    "cssesc": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+      "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
+    },
+    "currently-unhandled": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+      "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+      "dev": true,
+      "requires": {
+        "array-find-index": "^1.0.1"
+      }
+    },
+    "cyclist": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
+      "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
+      "dev": true
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "data-uri-to-buffer": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz",
+      "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==",
+      "dev": true
+    },
+    "date-fns": {
+      "version": "2.12.0",
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.12.0.tgz",
+      "integrity": "sha512-qJgn99xxKnFgB1qL4jpxU7Q2t0LOn1p8KMIveef3UZD7kqjT3tpFNNdXJelEHhE+rUgffriXriw/sOSU+cS1Hw=="
+    },
+    "de-indent": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
+      "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
+      "dev": true
+    },
+    "debug": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+      "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+      "dev": true,
+      "requires": {
+        "ms": "^2.1.1"
+      }
+    },
+    "decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+      "dev": true
+    },
+    "decode-uri-component": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+      "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+      "dev": true
+    },
+    "deep-equal": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
+      "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
+      "dev": true,
+      "requires": {
+        "is-arguments": "^1.0.4",
+        "is-date-object": "^1.0.1",
+        "is-regex": "^1.0.4",
+        "object-is": "^1.0.1",
+        "object-keys": "^1.1.1",
+        "regexp.prototype.flags": "^1.2.0"
+      }
+    },
+    "deep-extend": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+      "dev": true
+    },
+    "deep-is": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+      "dev": true
+    },
+    "default-gateway": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz",
+      "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==",
+      "dev": true,
+      "requires": {
+        "execa": "^1.0.0",
+        "ip-regex": "^2.1.0"
+      },
+      "dependencies": {
+        "execa": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+          "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+          "dev": true,
+          "requires": {
+            "cross-spawn": "^6.0.0",
+            "get-stream": "^4.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"
+          }
+        },
+        "get-stream": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+          "dev": true,
+          "requires": {
+            "pump": "^3.0.0"
+          }
+        }
+      }
+    },
+    "define-properties": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "requires": {
+        "object-keys": "^1.0.12"
+      }
+    },
+    "define-property": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+      "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+      "dev": true,
+      "requires": {
+        "is-descriptor": "^1.0.2",
+        "isobject": "^3.0.1"
+      },
+      "dependencies": {
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "degenerator": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz",
+      "integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=",
+      "dev": true,
+      "requires": {
+        "ast-types": "0.x.x",
+        "escodegen": "1.x.x",
+        "esprima": "3.x.x"
+      },
+      "dependencies": {
+        "esprima": {
+          "version": "3.1.3",
+          "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
+          "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
+          "dev": true
+        }
+      }
+    },
+    "del": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz",
+      "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==",
+      "dev": true,
+      "requires": {
+        "@types/glob": "^7.1.1",
+        "globby": "^6.1.0",
+        "is-path-cwd": "^2.0.0",
+        "is-path-in-cwd": "^2.0.0",
+        "p-map": "^2.0.0",
+        "pify": "^4.0.1",
+        "rimraf": "^2.6.3"
+      }
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+      "dev": true
+    },
+    "delegates": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+      "dev": true
+    },
+    "depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+      "dev": true
+    },
+    "des.js": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
+      "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
+      "dev": true
+    },
+    "detect-file": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
+      "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
+      "dev": true
+    },
+    "detect-node": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
+      "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
+      "dev": true
+    },
+    "diff": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+      "dev": true
+    },
+    "diffie-hellman": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+      "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "miller-rabin": "^4.0.0",
+        "randombytes": "^2.0.0"
+      }
+    },
+    "dns-equal": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
+      "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
+      "dev": true
+    },
+    "dns-packet": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
+      "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
+      "dev": true,
+      "requires": {
+        "ip": "^1.1.0",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "dns-txt": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
+      "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
+      "dev": true,
+      "requires": {
+        "buffer-indexof": "^1.0.0"
+      }
+    },
+    "dockerfile-ast": {
+      "version": "0.0.19",
+      "resolved": "https://registry.npmjs.org/dockerfile-ast/-/dockerfile-ast-0.0.19.tgz",
+      "integrity": "sha512-iDRNFeAB2j4rh/Ecc2gh3fjciVifCMsszfCfHlYF5Wv8yybjZLiRDZUBt/pS3xrAz8uWT8fCHLq4pOQMmwCDwA==",
+      "dev": true,
+      "requires": {
+        "vscode-languageserver-types": "^3.5.0"
+      }
+    },
+    "doctrine": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+      "dev": true,
+      "requires": {
+        "esutils": "^2.0.2"
+      }
+    },
+    "dom-converter": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
+      "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==",
+      "requires": {
+        "utila": "~0.4"
+      }
+    },
+    "dom-serializer": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
+      "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
+      "requires": {
+        "domelementtype": "^2.0.1",
+        "entities": "^2.0.0"
+      },
+      "dependencies": {
+        "domelementtype": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
+          "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
+        }
+      }
+    },
+    "domain-browser": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+      "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+      "dev": true
+    },
+    "domelementtype": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+      "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
+    },
+    "domhandler": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
+      "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
+      "requires": {
+        "domelementtype": "1"
+      }
+    },
+    "domutils": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+      "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
+      "requires": {
+        "dom-serializer": "0",
+        "domelementtype": "1"
+      }
+    },
+    "dot-prop": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
+      "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==",
+      "dev": true,
+      "requires": {
+        "is-obj": "^2.0.0"
+      }
+    },
+    "dotnet-deps-parser": {
+      "version": "4.9.0",
+      "resolved": "https://registry.npmjs.org/dotnet-deps-parser/-/dotnet-deps-parser-4.9.0.tgz",
+      "integrity": "sha512-V0O+7pI7Ei+iL5Kgy6nYq1UTwzrpqci5K/zf8cXyP5RWBSQBUl/JOE9I67zLUkKiwOdfPhbMQgcRj/yGA+NL1A==",
+      "dev": true,
+      "requires": {
+        "@types/xml2js": "0.4.3",
+        "lodash": "^4.17.11",
+        "source-map-support": "^0.5.7",
+        "tslib": "^1.10.0",
+        "xml2js": "0.4.19"
+      },
+      "dependencies": {
+        "xml2js": {
+          "version": "0.4.19",
+          "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
+          "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
+          "dev": true,
+          "requires": {
+            "sax": ">=0.6.0",
+            "xmlbuilder": "~9.0.1"
+          }
+        }
+      }
+    },
+    "duplexer": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
+      "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
+      "dev": true
+    },
+    "duplexer3": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
+      "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
+      "dev": true
+    },
+    "duplexify": {
+      "version": "3.7.1",
+      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+      "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.0.0",
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0",
+        "stream-shift": "^1.0.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "ecc-jsbn": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "dev": true,
+      "requires": {
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.1.0"
+      }
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
+      "dev": true
+    },
+    "ejs": {
+      "version": "2.7.4",
+      "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz",
+      "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==",
+      "dev": true
+    },
+    "electron-to-chromium": {
+      "version": "1.3.418",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.418.tgz",
+      "integrity": "sha512-i2QrQtHes5fK/F9QGG5XacM5WKEuR322fxTYF9e8O9Gu0mc0WmjjwGpV8c7Htso6Zf2Di18lc3SIPxmMeRFBug==",
+      "dev": true
+    },
+    "elliptic": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
+      "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.4.0",
+        "brorand": "^1.0.1",
+        "hash.js": "^1.0.0",
+        "hmac-drbg": "^1.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.0"
+      }
+    },
+    "email-validator": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/email-validator/-/email-validator-2.0.4.tgz",
+      "integrity": "sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==",
+      "dev": true
+    },
+    "emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "dev": true
+    },
+    "emojis-list": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+      "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+      "dev": true
+    },
+    "encoding": {
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+      "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+      "dev": true,
+      "requires": {
+        "iconv-lite": "~0.4.13"
+      }
+    },
+    "end-of-stream": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+      "dev": true,
+      "requires": {
+        "once": "^1.4.0"
+      }
+    },
+    "enhanced-resolve": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz",
+      "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "memory-fs": "^0.5.0",
+        "tapable": "^1.0.0"
+      },
+      "dependencies": {
+        "memory-fs": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
+          "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==",
+          "dev": true,
+          "requires": {
+            "errno": "^0.1.3",
+            "readable-stream": "^2.0.1"
+          }
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "entities": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
+      "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
+    },
+    "errno": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
+      "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+      "dev": true,
+      "requires": {
+        "prr": "~1.0.1"
+      }
+    },
+    "error-ex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "dev": true,
+      "requires": {
+        "is-arrayish": "^0.2.1"
+      }
+    },
+    "es-abstract": {
+      "version": "1.17.5",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz",
+      "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==",
+      "requires": {
+        "es-to-primitive": "^1.2.1",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.1",
+        "is-callable": "^1.1.5",
+        "is-regex": "^1.0.5",
+        "object-inspect": "^1.7.0",
+        "object-keys": "^1.1.1",
+        "object.assign": "^4.1.0",
+        "string.prototype.trimleft": "^2.1.1",
+        "string.prototype.trimright": "^2.1.1"
+      }
+    },
+    "es-to-primitive": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+      "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+      "requires": {
+        "is-callable": "^1.1.4",
+        "is-date-object": "^1.0.1",
+        "is-symbol": "^1.0.2"
+      }
+    },
+    "es6-promise": {
+      "version": "4.2.8",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
+      "dev": true
+    },
+    "es6-promisify": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+      "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
+      "dev": true,
+      "requires": {
+        "es6-promise": "^4.0.3"
+      }
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
+      "dev": true
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
+    },
+    "escodegen": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz",
+      "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==",
+      "dev": true,
+      "requires": {
+        "esprima": "^4.0.1",
+        "estraverse": "^4.2.0",
+        "esutils": "^2.0.2",
+        "optionator": "^0.8.1",
+        "source-map": "~0.6.1"
+      }
+    },
+    "eslint": {
+      "version": "6.8.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz",
+      "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.0.0",
+        "ajv": "^6.10.0",
+        "chalk": "^2.1.0",
+        "cross-spawn": "^6.0.5",
+        "debug": "^4.0.1",
+        "doctrine": "^3.0.0",
+        "eslint-scope": "^5.0.0",
+        "eslint-utils": "^1.4.3",
+        "eslint-visitor-keys": "^1.1.0",
+        "espree": "^6.1.2",
+        "esquery": "^1.0.1",
+        "esutils": "^2.0.2",
+        "file-entry-cache": "^5.0.1",
+        "functional-red-black-tree": "^1.0.1",
+        "glob-parent": "^5.0.0",
+        "globals": "^12.1.0",
+        "ignore": "^4.0.6",
+        "import-fresh": "^3.0.0",
+        "imurmurhash": "^0.1.4",
+        "inquirer": "^7.0.0",
+        "is-glob": "^4.0.0",
+        "js-yaml": "^3.13.1",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.3.0",
+        "lodash": "^4.17.14",
+        "minimatch": "^3.0.4",
+        "mkdirp": "^0.5.1",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.8.3",
+        "progress": "^2.0.0",
+        "regexpp": "^2.0.1",
+        "semver": "^6.1.2",
+        "strip-ansi": "^5.2.0",
+        "strip-json-comments": "^3.0.1",
+        "table": "^5.2.3",
+        "text-table": "^0.2.0",
+        "v8-compile-cache": "^2.0.3"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        },
+        "globals": {
+          "version": "12.4.0",
+          "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
+          "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
+          "dev": true,
+          "requires": {
+            "type-fest": "^0.8.1"
+          }
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        }
+      }
+    },
+    "eslint-config-airbnb-base": {
+      "version": "13.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.2.0.tgz",
+      "integrity": "sha512-1mg/7eoB4AUeB0X1c/ho4vb2gYkNH8Trr/EgCT/aGmKhhG+F6vF5s8+iRBlWAzFIAphxIdp3YfEKgEl0f9Xg+w==",
+      "requires": {
+        "confusing-browser-globals": "^1.0.5",
+        "object.assign": "^4.1.0",
+        "object.entries": "^1.1.0"
+      }
+    },
+    "eslint-config-prettier": {
+      "version": "6.11.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz",
+      "integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==",
+      "dev": true,
+      "requires": {
+        "get-stdin": "^6.0.0"
+      }
+    },
+    "eslint-import-resolver-node": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz",
+      "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==",
+      "dev": true,
+      "requires": {
+        "debug": "^2.6.9",
+        "resolve": "^1.13.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-loader": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-2.2.1.tgz",
+      "integrity": "sha512-RLgV9hoCVsMLvOxCuNjdqOrUqIj9oJg8hF44vzJaYqsAHuY9G2YAeN3joQ9nxP0p5Th9iFSIpKo+SD8KISxXRg==",
+      "dev": true,
+      "requires": {
+        "loader-fs-cache": "^1.0.0",
+        "loader-utils": "^1.0.2",
+        "object-assign": "^4.0.1",
+        "object-hash": "^1.1.4",
+        "rimraf": "^2.6.1"
+      },
+      "dependencies": {
+        "big.js": {
+          "version": "5.2.2",
+          "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+          "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+          "dev": true
+        },
+        "emojis-list": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+          "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+          "dev": true
+        },
+        "json5": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+          "dev": true,
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        },
+        "loader-utils": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+          "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^1.0.1"
+          }
+        }
+      }
+    },
+    "eslint-module-utils": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz",
+      "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==",
+      "dev": true,
+      "requires": {
+        "debug": "^2.6.9",
+        "pkg-dir": "^2.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        },
+        "pkg-dir": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+          "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
+          "dev": true,
+          "requires": {
+            "find-up": "^2.1.0"
+          }
+        }
+      }
+    },
+    "eslint-plugin-import": {
+      "version": "2.20.2",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz",
+      "integrity": "sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg==",
+      "dev": true,
+      "requires": {
+        "array-includes": "^3.0.3",
+        "array.prototype.flat": "^1.2.1",
+        "contains-path": "^0.1.0",
+        "debug": "^2.6.9",
+        "doctrine": "1.5.0",
+        "eslint-import-resolver-node": "^0.3.2",
+        "eslint-module-utils": "^2.4.1",
+        "has": "^1.0.3",
+        "minimatch": "^3.0.4",
+        "object.values": "^1.1.0",
+        "read-pkg-up": "^2.0.0",
+        "resolve": "^1.12.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "doctrine": {
+          "version": "1.5.0",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
+          "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2",
+            "isarray": "^1.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-plugin-prettier": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz",
+      "integrity": "sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ==",
+      "dev": true,
+      "requires": {
+        "prettier-linter-helpers": "^1.0.0"
+      }
+    },
+    "eslint-plugin-vue": {
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-5.2.3.tgz",
+      "integrity": "sha512-mGwMqbbJf0+VvpGR5Lllq0PMxvTdrZ/ZPjmhkacrCHbubJeJOt+T6E3HUzAifa2Mxi7RSdJfC9HFpOeSYVMMIw==",
+      "dev": true,
+      "requires": {
+        "vue-eslint-parser": "^5.0.0"
+      }
+    },
+    "eslint-scope": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
+      "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
+      "dev": true,
+      "requires": {
+        "esrecurse": "^4.1.0",
+        "estraverse": "^4.1.1"
+      }
+    },
+    "eslint-utils": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
+      "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
+      "dev": true,
+      "requires": {
+        "eslint-visitor-keys": "^1.1.0"
+      }
+    },
+    "eslint-visitor-keys": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
+      "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
+      "dev": true
+    },
+    "espree": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
+      "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
+      "dev": true,
+      "requires": {
+        "acorn": "^7.1.1",
+        "acorn-jsx": "^5.2.0",
+        "eslint-visitor-keys": "^1.1.0"
+      }
+    },
+    "esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+      "dev": true
+    },
+    "esquery": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
+      "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^5.1.0"
+      },
+      "dependencies": {
+        "estraverse": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz",
+          "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==",
+          "dev": true
+        }
+      }
+    },
+    "esrecurse": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+      "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^4.1.0"
+      }
+    },
+    "estraverse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+      "dev": true
+    },
+    "event-loop-spinner": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/event-loop-spinner/-/event-loop-spinner-1.1.0.tgz",
+      "integrity": "sha512-YVFs6dPpZIgH665kKckDktEVvSBccSYJmoZUfhNUdv5d3Xv+Q+SKF4Xis1jolq9aBzuW1ZZhQh/m/zU/TPdDhw==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.10.0"
+      }
+    },
+    "eventemitter3": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
+      "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==",
+      "dev": true
+    },
+    "events": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz",
+      "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==",
+      "dev": true
+    },
+    "eventsource": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz",
+      "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==",
+      "dev": true,
+      "requires": {
+        "original": "^1.0.0"
+      }
+    },
+    "evp_bytestokey": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+      "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+      "dev": true,
+      "requires": {
+        "md5.js": "^1.3.4",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "execa": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
+      "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
+      "dev": true,
+      "requires": {
+        "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"
+      },
+      "dependencies": {
+        "cross-spawn": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+          "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^4.0.1",
+            "shebang-command": "^1.2.0",
+            "which": "^1.2.9"
+          }
+        }
+      }
+    },
+    "expand-brackets": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+      "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+      "dev": true,
+      "requires": {
+        "debug": "^2.3.3",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "posix-character-classes": "^0.1.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "expand-tilde": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
+      "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=",
+      "dev": true,
+      "requires": {
+        "homedir-polyfill": "^1.0.1"
+      }
+    },
+    "express": {
+      "version": "4.17.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+      "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+      "dev": true,
+      "requires": {
+        "accepts": "~1.3.7",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.19.0",
+        "content-disposition": "0.5.3",
+        "content-type": "~1.0.4",
+        "cookie": "0.4.0",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "~1.1.2",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "~1.1.2",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "~2.0.5",
+        "qs": "6.7.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.1.2",
+        "send": "0.17.1",
+        "serve-static": "1.14.1",
+        "setprototypeof": "1.1.1",
+        "statuses": "~1.5.0",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        },
+        "qs": {
+          "version": "6.7.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+          "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
+          "dev": true
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        }
+      }
+    },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+      "dev": true
+    },
+    "extend-shallow": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+      "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+      "dev": true,
+      "requires": {
+        "assign-symbols": "^1.0.0",
+        "is-extendable": "^1.0.1"
+      },
+      "dependencies": {
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
+      }
+    },
+    "external-editor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+      "dev": true,
+      "requires": {
+        "chardet": "^0.7.0",
+        "iconv-lite": "^0.4.24",
+        "tmp": "^0.0.33"
+      }
+    },
+    "extglob": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+      "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+      "dev": true,
+      "requires": {
+        "array-unique": "^0.3.2",
+        "define-property": "^1.0.0",
+        "expand-brackets": "^2.1.4",
+        "extend-shallow": "^2.0.1",
+        "fragment-cache": "^0.2.1",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "extsprintf": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+      "dev": true
+    },
+    "fast-deep-equal": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+      "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
+      "dev": true
+    },
+    "fast-diff": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
+      "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
+      "dev": true
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+      "dev": true
+    },
+    "faye-websocket": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
+      "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
+      "dev": true,
+      "requires": {
+        "websocket-driver": ">=0.5.1"
+      }
+    },
+    "fetch": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/fetch/-/fetch-1.1.0.tgz",
+      "integrity": "sha1-CoJ58Gvjf58Ou1Z1YKMKSA2lmi4=",
+      "dev": true,
+      "requires": {
+        "biskviit": "1.0.1",
+        "encoding": "0.1.12"
+      }
+    },
+    "figgy-pudding": {
+      "version": "3.5.2",
+      "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
+      "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
+      "dev": true
+    },
+    "figures": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+      "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+      "dev": true,
+      "requires": {
+        "escape-string-regexp": "^1.0.5"
+      }
+    },
+    "file-entry-cache": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+      "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+      "dev": true,
+      "requires": {
+        "flat-cache": "^2.0.1"
+      }
+    },
+    "file-uri-to-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+      "dev": true
+    },
+    "filesize": {
+      "version": "3.6.1",
+      "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
+      "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==",
+      "dev": true
+    },
+    "fill-range": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+      "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^2.0.1",
+        "is-number": "^3.0.0",
+        "repeat-string": "^1.6.1",
+        "to-regex-range": "^2.1.0"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "finalhandler": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+      "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+      "dev": true,
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "on-finished": "~2.3.0",
+        "parseurl": "~1.3.3",
+        "statuses": "~1.5.0",
+        "unpipe": "~1.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "find-cache-dir": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+      "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+      "dev": true,
+      "requires": {
+        "commondir": "^1.0.1",
+        "make-dir": "^2.0.0",
+        "pkg-dir": "^3.0.0"
+      }
+    },
+    "find-up": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+      "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+      "dev": true,
+      "requires": {
+        "locate-path": "^2.0.0"
+      }
+    },
+    "findup-sync": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
+      "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==",
+      "dev": true,
+      "requires": {
+        "detect-file": "^1.0.0",
+        "is-glob": "^4.0.0",
+        "micromatch": "^3.0.4",
+        "resolve-dir": "^1.0.1"
+      }
+    },
+    "flat-cache": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+      "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+      "dev": true,
+      "requires": {
+        "flatted": "^2.0.0",
+        "rimraf": "2.6.3",
+        "write": "1.0.3"
+      }
+    },
+    "flatted": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
+      "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
+      "dev": true
+    },
+    "flush-write-stream": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
+      "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.3.6"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "follow-redirects": {
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz",
+      "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "for-in": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+      "dev": true
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+      "dev": true
+    },
+    "form-data": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "dev": true,
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
+      "dev": true
+    },
+    "fragment-cache": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+      "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+      "dev": true,
+      "requires": {
+        "map-cache": "^0.2.2"
+      }
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+      "dev": true
+    },
+    "from2": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+      "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "fs-constants": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+      "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+      "dev": true
+    },
+    "fs-write-stream-atomic": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
+      "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "iferr": "^0.1.5",
+        "imurmurhash": "^0.1.4",
+        "readable-stream": "1 || 2"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+      "dev": true
+    },
+    "fstream": {
+      "version": "1.0.12",
+      "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
+      "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "inherits": "~2.0.0",
+        "mkdirp": ">=0.5 0",
+        "rimraf": "2"
+      }
+    },
+    "ftp": {
+      "version": "0.3.10",
+      "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz",
+      "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=",
+      "dev": true,
+      "requires": {
+        "readable-stream": "1.1.x",
+        "xregexp": "2.0.0"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "1.1.14",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.1",
+            "isarray": "0.0.1",
+            "string_decoder": "~0.10.x"
+          }
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+          "dev": true
+        }
+      }
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+    },
+    "functional-red-black-tree": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+      "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+      "dev": true
+    },
+    "gauge": {
+      "version": "2.7.4",
+      "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+      "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+      "dev": true,
+      "requires": {
+        "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"
+      },
+      "dependencies": {
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "dev": true,
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "dev": true,
+          "requires": {
+            "code-point-at": "^1.0.0",
+            "is-fullwidth-code-point": "^1.0.0",
+            "strip-ansi": "^3.0.0"
+          }
+        }
+      }
+    },
+    "gaze": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
+      "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
+      "dev": true,
+      "requires": {
+        "globule": "^1.0.0"
+      }
+    },
+    "gensync": {
+      "version": "1.0.0-beta.1",
+      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
+      "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==",
+      "dev": true
+    },
+    "get-caller-file": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+      "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
+      "dev": true
+    },
+    "get-stdin": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
+      "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
+      "dev": true
+    },
+    "get-stream": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+      "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+      "dev": true
+    },
+    "get-uri": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.4.tgz",
+      "integrity": "sha512-v7LT/s8kVjs+Tx0ykk1I+H/rbpzkHvuIq87LmeXptcf5sNWm9uQiwjNAt94SJPA1zOlCntmnOlJvVWKmzsxG8Q==",
+      "dev": true,
+      "requires": {
+        "data-uri-to-buffer": "1",
+        "debug": "2",
+        "extend": "~3.0.2",
+        "file-uri-to-path": "1",
+        "ftp": "~0.3.10",
+        "readable-stream": "2"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "get-value": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+      "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+      "dev": true
+    },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "git-up": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/git-up/-/git-up-4.0.1.tgz",
+      "integrity": "sha512-LFTZZrBlrCrGCG07/dm1aCjjpL1z9L3+5aEeI9SBhAqSc+kiA9Or1bgZhQFNppJX6h/f5McrvJt1mQXTFm6Qrw==",
+      "dev": true,
+      "requires": {
+        "is-ssh": "^1.3.0",
+        "parse-url": "^5.0.0"
+      }
+    },
+    "git-url-parse": {
+      "version": "11.1.2",
+      "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.1.2.tgz",
+      "integrity": "sha512-gZeLVGY8QVKMIkckncX+iCq2/L8PlwncvDFKiWkBn9EtCfYDbliRTTp6qzyQ1VMdITUfq7293zDzfpjdiGASSQ==",
+      "dev": true,
+      "requires": {
+        "git-up": "^4.0.0"
+      }
+    },
+    "glob": {
+      "version": "7.1.6",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+      "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+      "dev": true,
+      "requires": {
+        "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-parent": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
+      "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+      "dev": true,
+      "requires": {
+        "is-glob": "^4.0.1"
+      }
+    },
+    "global-dirs": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
+      "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=",
+      "dev": true,
+      "requires": {
+        "ini": "^1.3.4"
+      }
+    },
+    "global-modules": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
+      "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==",
+      "dev": true,
+      "requires": {
+        "global-prefix": "^3.0.0"
+      },
+      "dependencies": {
+        "global-prefix": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz",
+          "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==",
+          "dev": true,
+          "requires": {
+            "ini": "^1.3.5",
+            "kind-of": "^6.0.2",
+            "which": "^1.3.1"
+          }
+        }
+      }
+    },
+    "global-prefix": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
+      "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=",
+      "dev": true,
+      "requires": {
+        "expand-tilde": "^2.0.2",
+        "homedir-polyfill": "^1.0.1",
+        "ini": "^1.3.4",
+        "is-windows": "^1.0.1",
+        "which": "^1.2.14"
+      }
+    },
+    "globals": {
+      "version": "11.12.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+      "dev": true
+    },
+    "globby": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+      "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
+      "dev": true,
+      "requires": {
+        "array-union": "^1.0.1",
+        "glob": "^7.0.3",
+        "object-assign": "^4.0.1",
+        "pify": "^2.0.0",
+        "pinkie-promise": "^2.0.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
+        }
+      }
+    },
+    "globule": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz",
+      "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==",
+      "dev": true,
+      "requires": {
+        "glob": "~7.1.1",
+        "lodash": "~4.17.12",
+        "minimatch": "~3.0.2"
+      }
+    },
+    "got": {
+      "version": "6.7.1",
+      "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz",
+      "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=",
+      "dev": true,
+      "requires": {
+        "create-error-class": "^3.0.0",
+        "duplexer3": "^0.1.4",
+        "get-stream": "^3.0.0",
+        "is-redirect": "^1.0.0",
+        "is-retry-allowed": "^1.0.0",
+        "is-stream": "^1.0.0",
+        "lowercase-keys": "^1.0.0",
+        "safe-buffer": "^5.0.1",
+        "timed-out": "^4.0.0",
+        "unzip-response": "^2.0.1",
+        "url-parse-lax": "^1.0.0"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
+      "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
+      "dev": true
+    },
+    "graphlib": {
+      "version": "2.1.8",
+      "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz",
+      "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==",
+      "dev": true,
+      "requires": {
+        "lodash": "^4.17.15"
+      }
+    },
+    "gzip-size": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz",
+      "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==",
+      "dev": true,
+      "requires": {
+        "duplexer": "^0.1.1",
+        "pify": "^4.0.1"
+      }
+    },
+    "handle-thing": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
+      "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==",
+      "dev": true
+    },
+    "har-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+      "dev": true
+    },
+    "har-validator": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+      "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.5.5",
+        "har-schema": "^2.0.0"
+      }
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-ansi": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+      "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^2.0.0"
+      }
+    },
+    "has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
+    },
+    "has-symbols": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+      "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
+    },
+    "has-unicode": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+      "dev": true
+    },
+    "has-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+      "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+      "dev": true,
+      "requires": {
+        "get-value": "^2.0.6",
+        "has-values": "^1.0.0",
+        "isobject": "^3.0.0"
+      }
+    },
+    "has-values": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+      "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+      "dev": true,
+      "requires": {
+        "is-number": "^3.0.0",
+        "kind-of": "^4.0.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "hash-base": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
+      "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "hash-sum": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
+      "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ="
+    },
+    "hash.js": {
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+      "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.3",
+        "minimalistic-assert": "^1.0.1"
+      }
+    },
+    "he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
+    },
+    "hmac-drbg": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+      "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+      "dev": true,
+      "requires": {
+        "hash.js": "^1.0.3",
+        "minimalistic-assert": "^1.0.0",
+        "minimalistic-crypto-utils": "^1.0.1"
+      }
+    },
+    "homedir-polyfill": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
+      "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
+      "dev": true,
+      "requires": {
+        "parse-passwd": "^1.0.0"
+      }
+    },
+    "hoopy": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
+      "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==",
+      "dev": true
+    },
+    "hosted-git-info": {
+      "version": "2.8.8",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
+      "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
+      "dev": true
+    },
+    "hpack.js": {
+      "version": "2.1.6",
+      "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
+      "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "obuf": "^1.0.0",
+        "readable-stream": "^2.0.1",
+        "wbuf": "^1.1.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "html-entities": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz",
+      "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==",
+      "dev": true
+    },
+    "html-minifier": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz",
+      "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==",
+      "requires": {
+        "camel-case": "3.0.x",
+        "clean-css": "4.2.x",
+        "commander": "2.17.x",
+        "he": "1.2.x",
+        "param-case": "2.1.x",
+        "relateurl": "0.2.x",
+        "uglify-js": "3.4.x"
+      }
+    },
+    "html-webpack-plugin": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
+      "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=",
+      "requires": {
+        "html-minifier": "^3.2.3",
+        "loader-utils": "^0.2.16",
+        "lodash": "^4.17.3",
+        "pretty-error": "^2.0.2",
+        "tapable": "^1.0.0",
+        "toposort": "^1.0.0",
+        "util.promisify": "1.0.0"
+      }
+    },
+    "htmlparser2": {
+      "version": "3.10.1",
+      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
+      "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
+      "requires": {
+        "domelementtype": "^1.3.1",
+        "domhandler": "^2.3.0",
+        "domutils": "^1.5.1",
+        "entities": "^1.1.1",
+        "inherits": "^2.0.1",
+        "readable-stream": "^3.1.1"
+      },
+      "dependencies": {
+        "entities": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
+          "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
+        }
+      }
+    },
+    "http-deceiver": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
+      "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
+      "dev": true
+    },
+    "http-errors": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
+      "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
+      "dev": true,
+      "requires": {
+        "depd": "~1.1.2",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.1.1",
+        "statuses": ">= 1.5.0 < 2",
+        "toidentifier": "1.0.0"
+      }
+    },
+    "http-parser-js": {
+      "version": "0.4.10",
+      "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz",
+      "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=",
+      "dev": true
+    },
+    "http-proxy": {
+      "version": "1.18.0",
+      "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
+      "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
+      "dev": true,
+      "requires": {
+        "eventemitter3": "^4.0.0",
+        "follow-redirects": "^1.0.0",
+        "requires-port": "^1.0.0"
+      }
+    },
+    "http-proxy-agent": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
+      "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
+      "dev": true,
+      "requires": {
+        "agent-base": "4",
+        "debug": "3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "http-proxy-middleware": {
+      "version": "0.19.1",
+      "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
+      "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
+      "dev": true,
+      "requires": {
+        "http-proxy": "^1.17.0",
+        "is-glob": "^4.0.0",
+        "lodash": "^4.17.11",
+        "micromatch": "^3.1.10"
+      }
+    },
+    "http-signature": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "jsprim": "^1.2.2",
+        "sshpk": "^1.7.0"
+      }
+    },
+    "https-browserify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+      "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
+      "dev": true
+    },
+    "https-proxy-agent": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz",
+      "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==",
+      "dev": true,
+      "requires": {
+        "agent-base": "^4.3.0",
+        "debug": "^3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "dev": true,
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "icss-utils": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz",
+      "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==",
+      "dev": true,
+      "requires": {
+        "postcss": "^7.0.14"
+      }
+    },
+    "ieee754": {
+      "version": "1.1.13",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+      "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
+      "dev": true
+    },
+    "iferr": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+      "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
+      "dev": true
+    },
+    "ignore": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+      "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+      "dev": true
+    },
+    "immediate": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+      "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=",
+      "dev": true
+    },
+    "import-fresh": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
+      "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
+      "dev": true,
+      "requires": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      }
+    },
+    "import-lazy": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
+      "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=",
+      "dev": true
+    },
+    "import-local": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
+      "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
+      "dev": true,
+      "requires": {
+        "pkg-dir": "^3.0.0",
+        "resolve-cwd": "^2.0.0"
+      }
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+      "dev": true
+    },
+    "in-publish": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz",
+      "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==",
+      "dev": true
+    },
+    "indent-string": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+      "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+      "dev": true,
+      "requires": {
+        "repeating": "^2.0.0"
+      }
+    },
+    "indexes-of": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
+      "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc="
+    },
+    "infer-owner": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+      "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dev": true,
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+    },
+    "ini": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+      "dev": true
+    },
+    "inquirer": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz",
+      "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==",
+      "dev": true,
+      "requires": {
+        "ansi-escapes": "^4.2.1",
+        "chalk": "^3.0.0",
+        "cli-cursor": "^3.1.0",
+        "cli-width": "^2.0.0",
+        "external-editor": "^3.0.3",
+        "figures": "^3.0.0",
+        "lodash": "^4.17.15",
+        "mute-stream": "0.0.8",
+        "run-async": "^2.4.0",
+        "rxjs": "^6.5.3",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0",
+        "through": "^2.3.6"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+          "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+          "dev": true
+        },
+        "ansi-styles": {
+          "version": "4.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
+          "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
+          "dev": true,
+          "requires": {
+            "@types/color-name": "^1.1.1",
+            "color-convert": "^2.0.1"
+          }
+        },
+        "chalk": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+          "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^5.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "7.1.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+          "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
+        }
+      }
+    },
+    "internal-ip": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz",
+      "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==",
+      "dev": true,
+      "requires": {
+        "default-gateway": "^4.2.0",
+        "ipaddr.js": "^1.9.0"
+      }
+    },
+    "interpret": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
+      "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==",
+      "dev": true
+    },
+    "invariant": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+      "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+      "dev": true,
+      "requires": {
+        "loose-envify": "^1.0.0"
+      }
+    },
+    "invert-kv": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
+      "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
+      "dev": true
+    },
+    "ip": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
+      "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
+      "dev": true
+    },
+    "ip-regex": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
+      "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=",
+      "dev": true
+    },
+    "ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "dev": true
+    },
+    "is-absolute-url": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz",
+      "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==",
+      "dev": true
+    },
+    "is-accessor-descriptor": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+      "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-arguments": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
+      "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==",
+      "dev": true
+    },
+    "is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+      "dev": true
+    },
+    "is-binary-path": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+      "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+      "dev": true,
+      "requires": {
+        "binary-extensions": "^1.0.0"
+      }
+    },
+    "is-buffer": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
+    },
+    "is-callable": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
+      "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q=="
+    },
+    "is-ci": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz",
+      "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==",
+      "dev": true,
+      "requires": {
+        "ci-info": "^1.5.0"
+      }
+    },
+    "is-data-descriptor": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+      "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-date-object": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
+      "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g=="
+    },
+    "is-descriptor": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+      "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+      "dev": true,
+      "requires": {
+        "is-accessor-descriptor": "^0.1.6",
+        "is-data-descriptor": "^0.1.4",
+        "kind-of": "^5.0.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+          "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+          "dev": true
+        }
+      }
+    },
+    "is-extendable": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+      "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+      "dev": true
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "dev": true
+    },
+    "is-finite": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz",
+      "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==",
+      "dev": true
+    },
+    "is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "dev": true
+    },
+    "is-glob": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+      "dev": true,
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-installed-globally": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
+      "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=",
+      "dev": true,
+      "requires": {
+        "global-dirs": "^0.1.0",
+        "is-path-inside": "^1.0.0"
+      }
+    },
+    "is-npm": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
+      "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=",
+      "dev": true
+    },
+    "is-number": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+      "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-obj": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
+      "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
+      "dev": true
+    },
+    "is-path-cwd": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz",
+      "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==",
+      "dev": true
+    },
+    "is-path-in-cwd": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz",
+      "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==",
+      "dev": true,
+      "requires": {
+        "is-path-inside": "^2.1.0"
+      },
+      "dependencies": {
+        "is-path-inside": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz",
+          "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==",
+          "dev": true,
+          "requires": {
+            "path-is-inside": "^1.0.2"
+          }
+        }
+      }
+    },
+    "is-path-inside": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
+      "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
+      "dev": true,
+      "requires": {
+        "path-is-inside": "^1.0.1"
+      }
+    },
+    "is-plain-object": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
+    "is-promise": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+      "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+      "dev": true
+    },
+    "is-redirect": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
+      "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=",
+      "dev": true
+    },
+    "is-regex": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
+      "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
+      "requires": {
+        "has": "^1.0.3"
+      }
+    },
+    "is-retry-allowed": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
+      "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==",
+      "dev": true
+    },
+    "is-ssh": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz",
+      "integrity": "sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg==",
+      "dev": true,
+      "requires": {
+        "protocols": "^1.1.0"
+      }
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "dev": true
+    },
+    "is-string": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
+      "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==",
+      "dev": true
+    },
+    "is-symbol": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
+      "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
+      "requires": {
+        "has-symbols": "^1.0.1"
+      }
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+      "dev": true
+    },
+    "is-utf8": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+      "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+      "dev": true
+    },
+    "is-windows": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+      "dev": true
+    },
+    "is-wsl": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
+      "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
+      "dev": true
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+      "dev": true
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "isobject": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+      "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+      "dev": true
+    },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+      "dev": true
+    },
+    "js-base64": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz",
+      "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==",
+      "dev": true
+    },
+    "js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true
+    },
+    "js-yaml": {
+      "version": "3.13.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+      "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+      "dev": true,
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+      "dev": true
+    },
+    "jsesc": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+      "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+      "dev": true
+    },
+    "json-parse-better-errors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+      "dev": true
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+      "dev": true
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+      "dev": true
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+      "dev": true
+    },
+    "json3": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz",
+      "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==",
+      "dev": true
+    },
+    "json5": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
+      "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
+      "requires": {
+        "minimist": "^1.2.5"
+      }
+    },
+    "jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
+    "jszip": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.4.0.tgz",
+      "integrity": "sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg==",
+      "dev": true,
+      "requires": {
+        "lie": "~3.3.0",
+        "pako": "~1.0.2",
+        "readable-stream": "~2.3.6",
+        "set-immediate-shim": "~1.0.1"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "killable": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz",
+      "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==",
+      "dev": true
+    },
+    "kind-of": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+      "dev": true
+    },
+    "latest-version": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz",
+      "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=",
+      "dev": true,
+      "requires": {
+        "package-json": "^4.0.0"
+      }
+    },
+    "lcid": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
+      "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
+      "dev": true,
+      "requires": {
+        "invert-kv": "^1.0.0"
+      }
+    },
+    "leven": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+      "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+      "dev": true
+    },
+    "levenary": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz",
+      "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==",
+      "dev": true,
+      "requires": {
+        "leven": "^3.1.0"
+      }
+    },
+    "levn": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2"
+      }
+    },
+    "lie": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+      "dev": true,
+      "requires": {
+        "immediate": "~3.0.5"
+      }
+    },
+    "load-json-file": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+      "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "parse-json": "^2.2.0",
+        "pify": "^2.0.0",
+        "strip-bom": "^3.0.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
+        }
+      }
+    },
+    "loader-fs-cache": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz",
+      "integrity": "sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA==",
+      "dev": true,
+      "requires": {
+        "find-cache-dir": "^0.1.1",
+        "mkdirp": "^0.5.1"
+      },
+      "dependencies": {
+        "find-cache-dir": {
+          "version": "0.1.1",
+          "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz",
+          "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=",
+          "dev": true,
+          "requires": {
+            "commondir": "^1.0.1",
+            "mkdirp": "^0.5.1",
+            "pkg-dir": "^1.0.0"
+          }
+        },
+        "find-up": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+          "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+          "dev": true,
+          "requires": {
+            "path-exists": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "path-exists": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+          "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+          "dev": true,
+          "requires": {
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "pkg-dir": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz",
+          "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=",
+          "dev": true,
+          "requires": {
+            "find-up": "^1.0.0"
+          }
+        }
+      }
+    },
+    "loader-runner": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
+      "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==",
+      "dev": true
+    },
+    "loader-utils": {
+      "version": "0.2.17",
+      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
+      "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
+      "requires": {
+        "big.js": "^3.1.3",
+        "emojis-list": "^2.0.0",
+        "json5": "^0.5.0",
+        "object-assign": "^4.0.1"
+      },
+      "dependencies": {
+        "json5": {
+          "version": "0.5.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
+          "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE="
+        }
+      }
+    },
+    "locate-path": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+      "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+      "dev": true,
+      "requires": {
+        "p-locate": "^2.0.0",
+        "path-exists": "^3.0.0"
+      }
+    },
+    "lodash": {
+      "version": "4.17.15",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+    },
+    "lodash.assign": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
+      "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
+      "dev": true
+    },
+    "lodash.assignin": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
+      "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=",
+      "dev": true
+    },
+    "lodash.clone": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz",
+      "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=",
+      "dev": true
+    },
+    "lodash.clonedeep": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+      "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+      "dev": true
+    },
+    "lodash.flatten": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
+      "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
+      "dev": true
+    },
+    "lodash.get": {
+      "version": "4.4.2",
+      "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+      "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
+      "dev": true
+    },
+    "lodash.set": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
+      "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=",
+      "dev": true
+    },
+    "loglevel": {
+      "version": "1.6.8",
+      "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz",
+      "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==",
+      "dev": true
+    },
+    "loose-envify": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+      "dev": true,
+      "requires": {
+        "js-tokens": "^3.0.0 || ^4.0.0"
+      }
+    },
+    "loud-rejection": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+      "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+      "dev": true,
+      "requires": {
+        "currently-unhandled": "^0.4.1",
+        "signal-exit": "^3.0.0"
+      }
+    },
+    "lower-case": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
+      "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw="
+    },
+    "lowercase-keys": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
+      "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
+      "dev": true
+    },
+    "lru-cache": {
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+      "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+      "requires": {
+        "pseudomap": "^1.0.2",
+        "yallist": "^2.1.2"
+      }
+    },
+    "macos-release": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz",
+      "integrity": "sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==",
+      "dev": true
+    },
+    "make-dir": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+      "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+      "dev": true,
+      "requires": {
+        "pify": "^4.0.1",
+        "semver": "^5.6.0"
+      }
+    },
+    "map-age-cleaner": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
+      "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
+      "dev": true,
+      "requires": {
+        "p-defer": "^1.0.0"
+      }
+    },
+    "map-cache": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+      "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+      "dev": true
+    },
+    "map-obj": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+      "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+      "dev": true
+    },
+    "map-visit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+      "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+      "dev": true,
+      "requires": {
+        "object-visit": "^1.0.0"
+      }
+    },
+    "md5": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
+      "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=",
+      "requires": {
+        "charenc": "~0.0.1",
+        "crypt": "~0.0.1",
+        "is-buffer": "~1.1.1"
+      }
+    },
+    "md5.js": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
+      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+      "dev": true,
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+      "dev": true
+    },
+    "mem": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
+      "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
+      "dev": true,
+      "requires": {
+        "map-age-cleaner": "^0.1.1",
+        "mimic-fn": "^2.0.0",
+        "p-is-promise": "^2.0.0"
+      }
+    },
+    "memory-fs": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
+      "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
+      "dev": true,
+      "requires": {
+        "errno": "^0.1.3",
+        "readable-stream": "^2.0.1"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "meow": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+      "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+      "dev": true,
+      "requires": {
+        "camelcase-keys": "^2.0.0",
+        "decamelize": "^1.1.2",
+        "loud-rejection": "^1.0.0",
+        "map-obj": "^1.0.1",
+        "minimist": "^1.1.3",
+        "normalize-package-data": "^2.3.4",
+        "object-assign": "^4.0.1",
+        "read-pkg-up": "^1.0.1",
+        "redent": "^1.0.0",
+        "trim-newlines": "^1.0.0"
+      },
+      "dependencies": {
+        "find-up": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+          "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+          "dev": true,
+          "requires": {
+            "path-exists": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "load-json-file": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+          "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "parse-json": "^2.2.0",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0",
+            "strip-bom": "^2.0.0"
+          }
+        },
+        "path-exists": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+          "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+          "dev": true,
+          "requires": {
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "path-type": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+          "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
+        },
+        "read-pkg": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+          "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+          "dev": true,
+          "requires": {
+            "load-json-file": "^1.0.0",
+            "normalize-package-data": "^2.3.2",
+            "path-type": "^1.0.0"
+          }
+        },
+        "read-pkg-up": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+          "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+          "dev": true,
+          "requires": {
+            "find-up": "^1.0.0",
+            "read-pkg": "^1.0.0"
+          }
+        },
+        "strip-bom": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+          "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+          "dev": true,
+          "requires": {
+            "is-utf8": "^0.2.0"
+          }
+        }
+      }
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
+      "dev": true
+    },
+    "merge-source-map": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
+      "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
+      "requires": {
+        "source-map": "^0.6.1"
+      }
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+      "dev": true
+    },
+    "micromatch": {
+      "version": "3.1.10",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+      "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+      "dev": true,
+      "requires": {
+        "arr-diff": "^4.0.0",
+        "array-unique": "^0.3.2",
+        "braces": "^2.3.1",
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "extglob": "^2.0.4",
+        "fragment-cache": "^0.2.1",
+        "kind-of": "^6.0.2",
+        "nanomatch": "^1.2.9",
+        "object.pick": "^1.3.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.2"
+      }
+    },
+    "miller-rabin": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+      "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.0.0",
+        "brorand": "^1.0.1"
+      }
+    },
+    "mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "dev": true
+    },
+    "mime-db": {
+      "version": "1.44.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
+      "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
+      "dev": true
+    },
+    "mime-types": {
+      "version": "2.1.27",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
+      "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
+      "dev": true,
+      "requires": {
+        "mime-db": "1.44.0"
+      }
+    },
+    "mimic-fn": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+      "dev": true
+    },
+    "minimalistic-assert": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+      "dev": true
+    },
+    "minimalistic-crypto-utils": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+      "dev": true
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+    },
+    "mississippi": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
+      "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
+      "dev": true,
+      "requires": {
+        "concat-stream": "^1.5.0",
+        "duplexify": "^3.4.2",
+        "end-of-stream": "^1.1.0",
+        "flush-write-stream": "^1.0.0",
+        "from2": "^2.1.0",
+        "parallel-transform": "^1.1.0",
+        "pump": "^3.0.0",
+        "pumpify": "^1.3.3",
+        "stream-each": "^1.1.0",
+        "through2": "^2.0.0"
+      }
+    },
+    "mixin-deep": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+      "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
+      "dev": true,
+      "requires": {
+        "for-in": "^1.0.2",
+        "is-extendable": "^1.0.1"
+      },
+      "dependencies": {
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
+      }
+    },
+    "mkdirp": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.5"
+      }
+    },
+    "moment": {
+      "version": "2.24.0",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
+      "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
+    },
+    "move-concurrently": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+      "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.1.1",
+        "copy-concurrently": "^1.0.0",
+        "fs-write-stream-atomic": "^1.0.8",
+        "mkdirp": "^0.5.1",
+        "rimraf": "^2.5.4",
+        "run-queue": "^1.0.3"
+      }
+    },
+    "ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "multicast-dns": {
+      "version": "6.2.3",
+      "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz",
+      "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==",
+      "dev": true,
+      "requires": {
+        "dns-packet": "^1.3.1",
+        "thunky": "^1.0.2"
+      }
+    },
+    "multicast-dns-service-types": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
+      "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
+      "dev": true
+    },
+    "mute-stream": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
+      "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
+      "dev": true
+    },
+    "nan": {
+      "version": "2.14.1",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
+      "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
+      "dev": true
+    },
+    "nanomatch": {
+      "version": "1.2.13",
+      "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+      "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+      "dev": true,
+      "requires": {
+        "arr-diff": "^4.0.0",
+        "array-unique": "^0.3.2",
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "fragment-cache": "^0.2.1",
+        "is-windows": "^1.0.2",
+        "kind-of": "^6.0.2",
+        "object.pick": "^1.3.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      }
+    },
+    "natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+      "dev": true
+    },
+    "nconf": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.10.0.tgz",
+      "integrity": "sha512-fKiXMQrpP7CYWJQzKkPPx9hPgmq+YLDyxcG9N8RpiE9FoCkCbzD0NyW0YhE3xn3Aupe7nnDeIx4PFzYehpHT9Q==",
+      "dev": true,
+      "requires": {
+        "async": "^1.4.0",
+        "ini": "^1.3.0",
+        "secure-keys": "^1.0.0",
+        "yargs": "^3.19.0"
+      },
+      "dependencies": {
+        "camelcase": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+          "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "dev": true,
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "dev": true,
+          "requires": {
+            "code-point-at": "^1.0.0",
+            "is-fullwidth-code-point": "^1.0.0",
+            "strip-ansi": "^3.0.0"
+          }
+        },
+        "yargs": {
+          "version": "3.32.0",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz",
+          "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=",
+          "dev": true,
+          "requires": {
+            "camelcase": "^2.0.1",
+            "cliui": "^3.0.3",
+            "decamelize": "^1.1.1",
+            "os-locale": "^1.4.0",
+            "string-width": "^1.0.1",
+            "window-size": "^0.1.4",
+            "y18n": "^3.2.0"
+          }
+        }
+      }
+    },
+    "needle": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz",
+      "integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.2.6",
+        "iconv-lite": "^0.4.4",
+        "sax": "^1.2.4"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "negotiator": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+      "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+      "dev": true
+    },
+    "neo-async": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
+      "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
+      "dev": true
+    },
+    "netmask": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz",
+      "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=",
+      "dev": true
+    },
+    "nice-try": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+      "dev": true
+    },
+    "no-case": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
+      "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
+      "requires": {
+        "lower-case": "^1.1.1"
+      }
+    },
+    "node-forge": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
+      "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
+      "dev": true
+    },
+    "node-gyp": {
+      "version": "3.8.0",
+      "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
+      "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
+      "dev": true,
+      "requires": {
+        "fstream": "^1.0.0",
+        "glob": "^7.0.3",
+        "graceful-fs": "^4.1.2",
+        "mkdirp": "^0.5.0",
+        "nopt": "2 || 3",
+        "npmlog": "0 || 1 || 2 || 3 || 4",
+        "osenv": "0",
+        "request": "^2.87.0",
+        "rimraf": "2",
+        "semver": "~5.3.0",
+        "tar": "^2.0.0",
+        "which": "1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+          "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+          "dev": true
+        }
+      }
+    },
+    "node-libs-browser": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
+      "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==",
+      "dev": true,
+      "requires": {
+        "assert": "^1.1.1",
+        "browserify-zlib": "^0.2.0",
+        "buffer": "^4.3.0",
+        "console-browserify": "^1.1.0",
+        "constants-browserify": "^1.0.0",
+        "crypto-browserify": "^3.11.0",
+        "domain-browser": "^1.1.1",
+        "events": "^3.0.0",
+        "https-browserify": "^1.0.0",
+        "os-browserify": "^0.3.0",
+        "path-browserify": "0.0.1",
+        "process": "^0.11.10",
+        "punycode": "^1.2.4",
+        "querystring-es3": "^0.2.0",
+        "readable-stream": "^2.3.3",
+        "stream-browserify": "^2.0.1",
+        "stream-http": "^2.7.2",
+        "string_decoder": "^1.0.0",
+        "timers-browserify": "^2.0.4",
+        "tty-browserify": "0.0.0",
+        "url": "^0.11.0",
+        "util": "^0.11.0",
+        "vm-browserify": "^1.0.1"
+      },
+      "dependencies": {
+        "buffer": {
+          "version": "4.9.2",
+          "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
+          "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
+          "dev": true,
+          "requires": {
+            "base64-js": "^1.0.2",
+            "ieee754": "^1.1.4",
+            "isarray": "^1.0.0"
+          }
+        },
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          },
+          "dependencies": {
+            "string_decoder": {
+              "version": "1.1.1",
+              "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+              "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+              "dev": true,
+              "requires": {
+                "safe-buffer": "~5.1.0"
+              }
+            }
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        }
+      }
+    },
+    "node-releases": {
+      "version": "1.1.53",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.53.tgz",
+      "integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==",
+      "dev": true
+    },
+    "node-sass": {
+      "version": "4.14.0",
+      "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.0.tgz",
+      "integrity": "sha512-AxqU+DFpk0lEz95sI6jO0hU0Rwyw7BXVEv6o9OItoXLyeygPeaSpiV4rwQb10JiTghHaa0gZeD21sz+OsQluaw==",
+      "dev": true,
+      "requires": {
+        "async-foreach": "^0.1.3",
+        "chalk": "^1.1.1",
+        "cross-spawn": "^3.0.0",
+        "gaze": "^1.0.0",
+        "get-stdin": "^4.0.1",
+        "glob": "^7.0.3",
+        "in-publish": "^2.0.0",
+        "lodash": "^4.17.15",
+        "meow": "^3.7.0",
+        "mkdirp": "^0.5.1",
+        "nan": "^2.13.2",
+        "node-gyp": "^3.8.0",
+        "npmlog": "^4.0.0",
+        "request": "^2.88.0",
+        "sass-graph": "^2.2.4",
+        "stdout-stream": "^1.4.0",
+        "true-case-path": "^1.0.2"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+          "dev": true
+        },
+        "chalk": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+          "dev": true,
+          "requires": {
+            "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"
+          }
+        },
+        "cross-spawn": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
+          "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^4.0.1",
+            "which": "^1.2.9"
+          }
+        },
+        "get-stdin": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+          "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+          "dev": true
+        }
+      }
+    },
+    "nopt": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+      "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+      "dev": true,
+      "requires": {
+        "abbrev": "1"
+      }
+    },
+    "normalize-package-data": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.1.4",
+        "resolve": "^1.10.0",
+        "semver": "2 || 3 || 4 || 5",
+        "validate-npm-package-license": "^3.0.1"
+      }
+    },
+    "normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true
+    },
+    "normalize-url": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz",
+      "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==",
+      "dev": true
+    },
+    "npm-run-path": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+      "dev": true,
+      "requires": {
+        "path-key": "^2.0.0"
+      }
+    },
+    "npmlog": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+      "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+      "dev": true,
+      "requires": {
+        "are-we-there-yet": "~1.1.2",
+        "console-control-strings": "~1.1.0",
+        "gauge": "~2.7.3",
+        "set-blocking": "~2.0.0"
+      }
+    },
+    "nth-check": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
+      "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
+      "requires": {
+        "boolbase": "~1.0.0"
+      }
+    },
+    "number-is-nan": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+      "dev": true
+    },
+    "oauth-sign": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+      "dev": true
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+    },
+    "object-copy": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+      "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+      "dev": true,
+      "requires": {
+        "copy-descriptor": "^0.1.0",
+        "define-property": "^0.2.5",
+        "kind-of": "^3.0.3"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "object-hash": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz",
+      "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==",
+      "dev": true
+    },
+    "object-inspect": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
+      "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw=="
+    },
+    "object-is": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz",
+      "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5"
+      }
+    },
+    "object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
+    },
+    "object-visit": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+      "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.0"
+      }
+    },
+    "object.assign": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+      "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+      "requires": {
+        "define-properties": "^1.1.2",
+        "function-bind": "^1.1.1",
+        "has-symbols": "^1.0.0",
+        "object-keys": "^1.0.11"
+      }
+    },
+    "object.entries": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz",
+      "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==",
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3"
+      }
+    },
+    "object.getownpropertydescriptors": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz",
+      "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==",
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1"
+      }
+    },
+    "object.pick": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+      "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
+    "object.values": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz",
+      "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3"
+      }
+    },
+    "obuf": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
+      "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
+      "dev": true
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "dev": true,
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "on-headers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+      "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+      "dev": true
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dev": true,
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "onetime": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
+      "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
+      "dev": true,
+      "requires": {
+        "mimic-fn": "^2.1.0"
+      }
+    },
+    "opener": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz",
+      "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==",
+      "dev": true
+    },
+    "opn": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz",
+      "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==",
+      "dev": true,
+      "requires": {
+        "is-wsl": "^1.1.0"
+      }
+    },
+    "optionator": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+      "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+      "dev": true,
+      "requires": {
+        "deep-is": "~0.1.3",
+        "fast-levenshtein": "~2.0.6",
+        "levn": "~0.3.0",
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2",
+        "word-wrap": "~1.2.3"
+      }
+    },
+    "original": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
+      "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
+      "dev": true,
+      "requires": {
+        "url-parse": "^1.4.3"
+      }
+    },
+    "os-browserify": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+      "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
+      "dev": true
+    },
+    "os-homedir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+      "dev": true
+    },
+    "os-locale": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+      "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
+      "dev": true,
+      "requires": {
+        "lcid": "^1.0.0"
+      }
+    },
+    "os-name": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz",
+      "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==",
+      "dev": true,
+      "requires": {
+        "macos-release": "^2.2.0",
+        "windows-release": "^3.1.0"
+      }
+    },
+    "os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+      "dev": true
+    },
+    "osenv": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+      "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+      "dev": true,
+      "requires": {
+        "os-homedir": "^1.0.0",
+        "os-tmpdir": "^1.0.0"
+      }
+    },
+    "p-defer": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
+      "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
+      "dev": true
+    },
+    "p-finally": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+      "dev": true
+    },
+    "p-is-promise": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
+      "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
+      "dev": true
+    },
+    "p-limit": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+      "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+      "dev": true,
+      "requires": {
+        "p-try": "^1.0.0"
+      }
+    },
+    "p-locate": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+      "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+      "dev": true,
+      "requires": {
+        "p-limit": "^1.1.0"
+      }
+    },
+    "p-map": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+      "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+      "dev": true
+    },
+    "p-retry": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz",
+      "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==",
+      "dev": true,
+      "requires": {
+        "retry": "^0.12.0"
+      }
+    },
+    "p-try": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+      "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+      "dev": true
+    },
+    "pac-proxy-agent": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-3.0.1.tgz",
+      "integrity": "sha512-44DUg21G/liUZ48dJpUSjZnFfZro/0K5JTyFYLBcmh9+T6Ooi4/i4efwUiEy0+4oQusCBqWdhv16XohIj1GqnQ==",
+      "dev": true,
+      "requires": {
+        "agent-base": "^4.2.0",
+        "debug": "^4.1.1",
+        "get-uri": "^2.0.0",
+        "http-proxy-agent": "^2.1.0",
+        "https-proxy-agent": "^3.0.0",
+        "pac-resolver": "^3.0.0",
+        "raw-body": "^2.2.0",
+        "socks-proxy-agent": "^4.0.1"
+      }
+    },
+    "pac-resolver": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz",
+      "integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==",
+      "dev": true,
+      "requires": {
+        "co": "^4.6.0",
+        "degenerator": "^1.0.4",
+        "ip": "^1.1.5",
+        "netmask": "^1.0.6",
+        "thunkify": "^2.1.2"
+      }
+    },
+    "package-json": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz",
+      "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=",
+      "dev": true,
+      "requires": {
+        "got": "^6.7.1",
+        "registry-auth-token": "^3.0.1",
+        "registry-url": "^3.0.3",
+        "semver": "^5.1.0"
+      }
+    },
+    "pako": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+      "dev": true
+    },
+    "parallel-transform": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
+      "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
+      "dev": true,
+      "requires": {
+        "cyclist": "^1.0.1",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.1.5"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "param-case": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
+      "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
+      "requires": {
+        "no-case": "^2.2.0"
+      }
+    },
+    "parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "requires": {
+        "callsites": "^3.0.0"
+      }
+    },
+    "parse-asn1": {
+      "version": "5.1.5",
+      "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz",
+      "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==",
+      "dev": true,
+      "requires": {
+        "asn1.js": "^4.0.0",
+        "browserify-aes": "^1.0.0",
+        "create-hash": "^1.1.0",
+        "evp_bytestokey": "^1.0.0",
+        "pbkdf2": "^3.0.3",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "parse-json": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+      "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+      "dev": true,
+      "requires": {
+        "error-ex": "^1.2.0"
+      }
+    },
+    "parse-passwd": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
+      "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
+      "dev": true
+    },
+    "parse-path": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.1.tgz",
+      "integrity": "sha512-d7yhga0Oc+PwNXDvQ0Jv1BuWkLVPXcAoQ/WREgd6vNNoKYaW52KI+RdOFjI63wjkmps9yUE8VS4veP+AgpQ/hA==",
+      "dev": true,
+      "requires": {
+        "is-ssh": "^1.3.0",
+        "protocols": "^1.4.0"
+      }
+    },
+    "parse-url": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-5.0.1.tgz",
+      "integrity": "sha512-flNUPP27r3vJpROi0/R3/2efgKkyXqnXwyP1KQ2U0SfFRgdizOdWfvrrvJg1LuOoxs7GQhmxJlq23IpQ/BkByg==",
+      "dev": true,
+      "requires": {
+        "is-ssh": "^1.3.0",
+        "normalize-url": "^3.3.0",
+        "parse-path": "^4.0.0",
+        "protocols": "^1.4.0"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "dev": true
+    },
+    "pascalcase": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+      "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+      "dev": true
+    },
+    "path-browserify": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz",
+      "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==",
+      "dev": true
+    },
+    "path-dirname": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+      "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+      "dev": true
+    },
+    "path-exists": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+      "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+      "dev": true
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "dev": true
+    },
+    "path-is-inside": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+      "dev": true
+    },
+    "path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "dev": true
+    },
+    "path-parse": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+      "dev": true
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
+      "dev": true
+    },
+    "path-type": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+      "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+      "dev": true,
+      "requires": {
+        "pify": "^2.0.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
+        }
+      }
+    },
+    "pbkdf2": {
+      "version": "3.0.17",
+      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
+      "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+      "dev": true,
+      "requires": {
+        "create-hash": "^1.1.2",
+        "create-hmac": "^1.1.4",
+        "ripemd160": "^2.0.1",
+        "safe-buffer": "^5.0.1",
+        "sha.js": "^2.4.8"
+      }
+    },
+    "performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
+      "dev": true
+    },
+    "pify": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+      "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+      "dev": true
+    },
+    "pinkie": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+      "dev": true
+    },
+    "pinkie-promise": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+      "dev": true,
+      "requires": {
+        "pinkie": "^2.0.0"
+      }
+    },
+    "pkg-dir": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+      "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+      "dev": true,
+      "requires": {
+        "find-up": "^3.0.0"
+      },
+      "dependencies": {
+        "find-up": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+          "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+          "dev": true,
+          "requires": {
+            "locate-path": "^3.0.0"
+          }
+        },
+        "locate-path": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+          "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+          "dev": true,
+          "requires": {
+            "p-locate": "^3.0.0",
+            "path-exists": "^3.0.0"
+          }
+        },
+        "p-limit": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+          "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+          "dev": true,
+          "requires": {
+            "p-try": "^2.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+          "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+          "dev": true,
+          "requires": {
+            "p-limit": "^2.0.0"
+          }
+        },
+        "p-try": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+          "dev": true
+        }
+      }
+    },
+    "pkg-up": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz",
+      "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=",
+      "dev": true,
+      "requires": {
+        "find-up": "^2.1.0"
+      }
+    },
+    "portfinder": {
+      "version": "1.0.25",
+      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
+      "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==",
+      "dev": true,
+      "requires": {
+        "async": "^2.6.2",
+        "debug": "^3.1.1",
+        "mkdirp": "^0.5.1"
+      },
+      "dependencies": {
+        "async": {
+          "version": "2.6.3",
+          "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+          "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+          "dev": true,
+          "requires": {
+            "lodash": "^4.17.14"
+          }
+        },
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "posix-character-classes": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+      "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+      "dev": true
+    },
+    "postcss": {
+      "version": "7.0.27",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz",
+      "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==",
+      "requires": {
+        "chalk": "^2.4.2",
+        "source-map": "^0.6.1",
+        "supports-color": "^6.1.0"
+      }
+    },
+    "postcss-modules-extract-imports": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz",
+      "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==",
+      "dev": true,
+      "requires": {
+        "postcss": "^7.0.5"
+      }
+    },
+    "postcss-modules-local-by-default": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz",
+      "integrity": "sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==",
+      "dev": true,
+      "requires": {
+        "icss-utils": "^4.1.1",
+        "postcss": "^7.0.16",
+        "postcss-selector-parser": "^6.0.2",
+        "postcss-value-parser": "^4.0.0"
+      }
+    },
+    "postcss-modules-scope": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz",
+      "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==",
+      "dev": true,
+      "requires": {
+        "postcss": "^7.0.6",
+        "postcss-selector-parser": "^6.0.0"
+      }
+    },
+    "postcss-modules-values": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz",
+      "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==",
+      "dev": true,
+      "requires": {
+        "icss-utils": "^4.0.0",
+        "postcss": "^7.0.6"
+      }
+    },
+    "postcss-selector-parser": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz",
+      "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==",
+      "requires": {
+        "cssesc": "^3.0.0",
+        "indexes-of": "^1.0.1",
+        "uniq": "^1.0.1"
+      }
+    },
+    "postcss-value-parser": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz",
+      "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==",
+      "dev": true
+    },
+    "prelude-ls": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+      "dev": true
+    },
+    "prepend-http": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
+      "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
+      "dev": true
+    },
+    "prettier": {
+      "version": "1.18.2",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz",
+      "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==",
+      "dev": true
+    },
+    "prettier-linter-helpers": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+      "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+      "dev": true,
+      "requires": {
+        "fast-diff": "^1.1.2"
+      }
+    },
+    "pretty-error": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz",
+      "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=",
+      "requires": {
+        "renderkid": "^2.0.1",
+        "utila": "~0.4"
+      }
+    },
+    "private": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz",
+      "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
+      "dev": true
+    },
+    "process": {
+      "version": "0.11.10",
+      "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+      "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+      "dev": true
+    },
+    "process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+      "dev": true
+    },
+    "progress": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+      "dev": true
+    },
+    "promise": {
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+      "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+      "dev": true,
+      "requires": {
+        "asap": "~2.0.3"
+      }
+    },
+    "promise-inflight": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+      "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
+      "dev": true
+    },
+    "protocols": {
+      "version": "1.4.7",
+      "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.7.tgz",
+      "integrity": "sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg==",
+      "dev": true
+    },
+    "proxy-addr": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
+      "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
+      "dev": true,
+      "requires": {
+        "forwarded": "~0.1.2",
+        "ipaddr.js": "1.9.1"
+      }
+    },
+    "proxy-agent": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.1.1.tgz",
+      "integrity": "sha512-WudaR0eTsDx33O3EJE16PjBRZWcX8GqCEeERw1W3hZJgH/F2a46g7jty6UGty6NeJ4CKQy8ds2CJPMiyeqaTvw==",
+      "dev": true,
+      "requires": {
+        "agent-base": "^4.2.0",
+        "debug": "4",
+        "http-proxy-agent": "^2.1.0",
+        "https-proxy-agent": "^3.0.0",
+        "lru-cache": "^5.1.1",
+        "pac-proxy-agent": "^3.0.1",
+        "proxy-from-env": "^1.0.0",
+        "socks-proxy-agent": "^4.0.1"
+      },
+      "dependencies": {
+        "lru-cache": {
+          "version": "5.1.1",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+          "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+          "dev": true,
+          "requires": {
+            "yallist": "^3.0.2"
+          }
+        },
+        "yallist": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+          "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+          "dev": true
+        }
+      }
+    },
+    "proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+      "dev": true
+    },
+    "prr": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+      "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
+      "dev": true
+    },
+    "pseudomap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
+    },
+    "psl": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+      "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
+      "dev": true
+    },
+    "public-encrypt": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
+      "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==",
+      "dev": true,
+      "requires": {
+        "bn.js": "^4.1.0",
+        "browserify-rsa": "^4.0.0",
+        "create-hash": "^1.1.0",
+        "parse-asn1": "^5.0.0",
+        "randombytes": "^2.0.1",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "pump": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+      "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.1.0",
+        "once": "^1.3.1"
+      }
+    },
+    "pumpify": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+      "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+      "dev": true,
+      "requires": {
+        "duplexify": "^3.6.0",
+        "inherits": "^2.0.3",
+        "pump": "^2.0.0"
+      },
+      "dependencies": {
+        "pump": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+          "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+          "dev": true,
+          "requires": {
+            "end-of-stream": "^1.1.0",
+            "once": "^1.3.1"
+          }
+        }
+      }
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "dev": true
+    },
+    "qs": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+      "dev": true
+    },
+    "querystring": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+      "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+      "dev": true
+    },
+    "querystring-es3": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+      "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+      "dev": true
+    },
+    "querystringify": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
+      "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==",
+      "dev": true
+    },
+    "randombytes": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "randomfill": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+      "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+      "dev": true,
+      "requires": {
+        "randombytes": "^2.0.5",
+        "safe-buffer": "^5.1.0"
+      }
+    },
+    "range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "dev": true
+    },
+    "raw-body": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz",
+      "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==",
+      "dev": true,
+      "requires": {
+        "bytes": "3.1.0",
+        "http-errors": "1.7.3",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      }
+    },
+    "rc": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "dev": true,
+      "requires": {
+        "deep-extend": "^0.6.0",
+        "ini": "~1.3.0",
+        "minimist": "^1.2.0",
+        "strip-json-comments": "~2.0.1"
+      },
+      "dependencies": {
+        "strip-json-comments": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+          "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+          "dev": true
+        }
+      }
+    },
+    "read-pkg": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+      "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+      "dev": true,
+      "requires": {
+        "load-json-file": "^2.0.0",
+        "normalize-package-data": "^2.3.2",
+        "path-type": "^2.0.0"
+      }
+    },
+    "read-pkg-up": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+      "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+      "dev": true,
+      "requires": {
+        "find-up": "^2.0.0",
+        "read-pkg": "^2.0.0"
+      }
+    },
+    "readable-stream": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+      "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+      "requires": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      }
+    },
+    "readdirp": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
+      "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.11",
+        "micromatch": "^3.1.10",
+        "readable-stream": "^2.0.2"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "redent": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+      "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+      "dev": true,
+      "requires": {
+        "indent-string": "^2.1.0",
+        "strip-indent": "^1.0.1"
+      }
+    },
+    "regenerate": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
+      "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
+      "dev": true
+    },
+    "regenerate-unicode-properties": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz",
+      "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==",
+      "dev": true,
+      "requires": {
+        "regenerate": "^1.4.0"
+      }
+    },
+    "regenerator-runtime": {
+      "version": "0.13.5",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
+      "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
+    },
+    "regenerator-transform": {
+      "version": "0.14.4",
+      "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.4.tgz",
+      "integrity": "sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw==",
+      "dev": true,
+      "requires": {
+        "@babel/runtime": "^7.8.4",
+        "private": "^0.1.8"
+      }
+    },
+    "regex-not": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+      "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^3.0.2",
+        "safe-regex": "^1.1.0"
+      }
+    },
+    "regexp.prototype.flags": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz",
+      "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1"
+      }
+    },
+    "regexpp": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
+      "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
+      "dev": true
+    },
+    "regexpu-core": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz",
+      "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==",
+      "dev": true,
+      "requires": {
+        "regenerate": "^1.4.0",
+        "regenerate-unicode-properties": "^8.2.0",
+        "regjsgen": "^0.5.1",
+        "regjsparser": "^0.6.4",
+        "unicode-match-property-ecmascript": "^1.0.4",
+        "unicode-match-property-value-ecmascript": "^1.2.0"
+      }
+    },
+    "registry-auth-token": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz",
+      "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==",
+      "dev": true,
+      "requires": {
+        "rc": "^1.1.6",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "registry-url": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+      "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=",
+      "dev": true,
+      "requires": {
+        "rc": "^1.0.1"
+      }
+    },
+    "regjsgen": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz",
+      "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==",
+      "dev": true
+    },
+    "regjsparser": {
+      "version": "0.6.4",
+      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz",
+      "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==",
+      "dev": true,
+      "requires": {
+        "jsesc": "~0.5.0"
+      },
+      "dependencies": {
+        "jsesc": {
+          "version": "0.5.0",
+          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+          "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+          "dev": true
+        }
+      }
+    },
+    "relateurl": {
+      "version": "0.2.7",
+      "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
+      "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk="
+    },
+    "remove-trailing-separator": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+      "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+      "dev": true
+    },
+    "renderkid": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz",
+      "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==",
+      "requires": {
+        "css-select": "^1.1.0",
+        "dom-converter": "^0.2",
+        "htmlparser2": "^3.3.0",
+        "strip-ansi": "^3.0.0",
+        "utila": "^0.4.0"
+      }
+    },
+    "repeat-element": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+      "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
+      "dev": true
+    },
+    "repeat-string": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+      "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+      "dev": true
+    },
+    "repeating": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+      "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+      "dev": true,
+      "requires": {
+        "is-finite": "^1.0.0"
+      }
+    },
+    "request": {
+      "version": "2.88.2",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
+      "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
+      "dev": true,
+      "requires": {
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.8.0",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "form-data": "~2.3.2",
+        "har-validator": "~5.1.3",
+        "http-signature": "~1.2.0",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "~2.1.19",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.2",
+        "safe-buffer": "^5.1.2",
+        "tough-cookie": "~2.5.0",
+        "tunnel-agent": "^0.6.0",
+        "uuid": "^3.3.2"
+      }
+    },
+    "require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+      "dev": true
+    },
+    "require-main-filename": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+      "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
+      "dev": true
+    },
+    "requires-port": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+      "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
+      "dev": true
+    },
+    "resolve": {
+      "version": "1.17.0",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
+      "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
+      "dev": true,
+      "requires": {
+        "path-parse": "^1.0.6"
+      }
+    },
+    "resolve-cwd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
+      "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+      "dev": true,
+      "requires": {
+        "resolve-from": "^3.0.0"
+      },
+      "dependencies": {
+        "resolve-from": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+          "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+          "dev": true
+        }
+      }
+    },
+    "resolve-dir": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
+      "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=",
+      "dev": true,
+      "requires": {
+        "expand-tilde": "^2.0.0",
+        "global-modules": "^1.0.0"
+      },
+      "dependencies": {
+        "global-modules": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
+          "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
+          "dev": true,
+          "requires": {
+            "global-prefix": "^1.0.1",
+            "is-windows": "^1.0.1",
+            "resolve-dir": "^1.0.0"
+          }
+        }
+      }
+    },
+    "resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true
+    },
+    "resolve-url": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+      "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+      "dev": true
+    },
+    "restore-cursor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
+      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
+      "dev": true,
+      "requires": {
+        "onetime": "^5.1.0",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "ret": {
+      "version": "0.1.15",
+      "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+      "dev": true
+    },
+    "retry": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+      "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
+      "dev": true
+    },
+    "rimraf": {
+      "version": "2.6.3",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+      "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.3"
+      }
+    },
+    "ripemd160": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+      "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+      "dev": true,
+      "requires": {
+        "hash-base": "^3.0.0",
+        "inherits": "^2.0.1"
+      }
+    },
+    "run-async": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz",
+      "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==",
+      "dev": true,
+      "requires": {
+        "is-promise": "^2.1.0"
+      }
+    },
+    "run-queue": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+      "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.1.1"
+      }
+    },
+    "rxjs": {
+      "version": "6.5.5",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
+      "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+      "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
+    },
+    "safe-regex": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+      "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+      "dev": true,
+      "requires": {
+        "ret": "~0.1.10"
+      }
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "dev": true
+    },
+    "sass-graph": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
+      "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
+      "dev": true,
+      "requires": {
+        "glob": "^7.0.0",
+        "lodash": "^4.0.0",
+        "scss-tokenizer": "^0.2.3",
+        "yargs": "^7.0.0"
+      }
+    },
+    "sass-loader": {
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.3.1.tgz",
+      "integrity": "sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA==",
+      "dev": true,
+      "requires": {
+        "clone-deep": "^4.0.1",
+        "loader-utils": "^1.0.1",
+        "neo-async": "^2.5.0",
+        "pify": "^4.0.1",
+        "semver": "^6.3.0"
+      },
+      "dependencies": {
+        "big.js": {
+          "version": "5.2.2",
+          "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+          "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+          "dev": true
+        },
+        "emojis-list": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+          "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+          "dev": true
+        },
+        "json5": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+          "dev": true,
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        },
+        "loader-utils": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+          "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^1.0.1"
+          }
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
+      }
+    },
+    "sax": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+      "dev": true
+    },
+    "schema-utils": {
+      "version": "2.6.6",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz",
+      "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.12.0",
+        "ajv-keywords": "^3.4.1"
+      }
+    },
+    "scss-tokenizer": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
+      "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
+      "dev": true,
+      "requires": {
+        "js-base64": "^2.1.8",
+        "source-map": "^0.4.2"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.4.4",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+          "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+          "dev": true,
+          "requires": {
+            "amdefine": ">=0.0.4"
+          }
+        }
+      }
+    },
+    "secure-keys": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/secure-keys/-/secure-keys-1.0.0.tgz",
+      "integrity": "sha1-8MgtmKOxOah3aogIBQuCRDEIf8o=",
+      "dev": true
+    },
+    "select-hose": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
+      "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
+      "dev": true
+    },
+    "selfsigned": {
+      "version": "1.10.7",
+      "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
+      "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
+      "dev": true,
+      "requires": {
+        "node-forge": "0.9.0"
+      }
+    },
+    "semver": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+      "dev": true
+    },
+    "semver-diff": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz",
+      "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=",
+      "dev": true,
+      "requires": {
+        "semver": "^5.0.3"
+      }
+    },
+    "send": {
+      "version": "0.17.1",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+      "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+      "dev": true,
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "~1.1.2",
+        "destroy": "~1.0.4",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "~1.7.2",
+        "mime": "1.6.0",
+        "ms": "2.1.1",
+        "on-finished": "~2.3.0",
+        "range-parser": "~1.2.1",
+        "statuses": "~1.5.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          },
+          "dependencies": {
+            "ms": {
+              "version": "2.0.0",
+              "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+              "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+              "dev": true
+            }
+          }
+        },
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+          "dev": true
+        }
+      }
+    },
+    "serialize-javascript": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
+      "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
+      "dev": true
+    },
+    "serve-index": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
+      "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
+      "dev": true,
+      "requires": {
+        "accepts": "~1.3.4",
+        "batch": "0.6.1",
+        "debug": "2.6.9",
+        "escape-html": "~1.0.3",
+        "http-errors": "~1.6.2",
+        "mime-types": "~2.1.17",
+        "parseurl": "~1.3.2"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "http-errors": {
+          "version": "1.6.3",
+          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+          "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+          "dev": true,
+          "requires": {
+            "depd": "~1.1.2",
+            "inherits": "2.0.3",
+            "setprototypeof": "1.1.0",
+            "statuses": ">= 1.4.0 < 2"
+          }
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        },
+        "setprototypeof": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+          "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
+          "dev": true
+        }
+      }
+    },
+    "serve-static": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+      "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+      "dev": true,
+      "requires": {
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.17.1"
+      }
+    },
+    "set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+      "dev": true
+    },
+    "set-immediate-shim": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
+      "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
+      "dev": true
+    },
+    "set-value": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+      "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^2.0.1",
+        "is-extendable": "^0.1.1",
+        "is-plain-object": "^2.0.3",
+        "split-string": "^3.0.1"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "setimmediate": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+      "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+      "dev": true
+    },
+    "setprototypeof": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+      "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==",
+      "dev": true
+    },
+    "sha.js": {
+      "version": "2.4.11",
+      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+      "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "shallow-clone": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+      "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+      "dev": true,
+      "requires": {
+        "kind-of": "^6.0.2"
+      }
+    },
+    "shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "dev": true,
+      "requires": {
+        "shebang-regex": "^1.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "dev": true
+    },
+    "signal-exit": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
+      "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
+      "dev": true
+    },
+    "slice-ansi": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+      "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.0",
+        "astral-regex": "^1.0.0",
+        "is-fullwidth-code-point": "^2.0.0"
+      },
+      "dependencies": {
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        }
+      }
+    },
+    "smart-buffer": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz",
+      "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==",
+      "dev": true
+    },
+    "snapdragon": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+      "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+      "dev": true,
+      "requires": {
+        "base": "^0.11.1",
+        "debug": "^2.2.0",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "map-cache": "^0.2.2",
+        "source-map": "^0.5.6",
+        "source-map-resolve": "^0.5.0",
+        "use": "^3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        },
+        "source-map": {
+          "version": "0.5.7",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+          "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+          "dev": true
+        }
+      }
+    },
+    "snapdragon-node": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+      "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+      "dev": true,
+      "requires": {
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.0",
+        "snapdragon-util": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "snapdragon-util": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+      "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.2.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "snyk": {
+      "version": "1.311.0",
+      "resolved": "https://registry.npmjs.org/snyk/-/snyk-1.311.0.tgz",
+      "integrity": "sha512-eH+JFzgCU4qqmEeY9CXJq+XUtlOAVuXv0XtzRnIhWloBX9HaXDALsIjGyR87UPj0PpARrwkXd0uIwsSYM9x2mg==",
+      "dev": true,
+      "requires": {
+        "@snyk/cli-interface": "^2.4.0",
+        "@snyk/configstore": "^3.2.0-rc1",
+        "@snyk/dep-graph": "1.16.1",
+        "@snyk/gemfile": "1.2.0",
+        "@snyk/ruby-semver": "2.1.2",
+        "@snyk/snyk-cocoapods-plugin": "2.1.1",
+        "@snyk/update-notifier": "^2.5.1-rc2",
+        "@types/agent-base": "^4.2.0",
+        "@types/restify": "^4.3.6",
+        "abbrev": "^1.1.1",
+        "ansi-escapes": "3.2.0",
+        "chalk": "^2.4.2",
+        "cli-spinner": "0.2.10",
+        "debug": "^3.1.0",
+        "diff": "^4.0.1",
+        "git-url-parse": "11.1.2",
+        "glob": "^7.1.3",
+        "graphlib": "^2.1.8",
+        "inquirer": "^6.2.2",
+        "lodash": "^4.17.14",
+        "needle": "^2.2.4",
+        "opn": "^5.5.0",
+        "os-name": "^3.0.0",
+        "proxy-agent": "^3.1.1",
+        "proxy-from-env": "^1.0.0",
+        "semver": "^6.0.0",
+        "snyk-config": "^2.2.1",
+        "snyk-docker-plugin": "2.6.1",
+        "snyk-go-plugin": "1.13.0",
+        "snyk-gradle-plugin": "3.2.5",
+        "snyk-module": "1.9.1",
+        "snyk-mvn-plugin": "2.10.0",
+        "snyk-nodejs-lockfile-parser": "1.21.0",
+        "snyk-nuget-plugin": "1.16.0",
+        "snyk-php-plugin": "1.7.0",
+        "snyk-policy": "1.13.5",
+        "snyk-python-plugin": "1.17.0",
+        "snyk-resolve": "1.0.1",
+        "snyk-resolve-deps": "4.4.0",
+        "snyk-sbt-plugin": "2.11.0",
+        "snyk-tree": "^1.0.0",
+        "snyk-try-require": "1.3.1",
+        "source-map-support": "^0.5.11",
+        "strip-ansi": "^5.2.0",
+        "tempfile": "^2.0.0",
+        "then-fs": "^2.0.0",
+        "uuid": "^3.3.2",
+        "wrap-ansi": "^5.1.0"
+      },
+      "dependencies": {
+        "ansi-escapes": {
+          "version": "3.2.0",
+          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+          "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+          "dev": true
+        },
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "cli-cursor": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+          "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+          "dev": true,
+          "requires": {
+            "restore-cursor": "^2.0.0"
+          }
+        },
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "emoji-regex": {
+          "version": "7.0.3",
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+          "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+          "dev": true
+        },
+        "figures": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+          "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+          "dev": true,
+          "requires": {
+            "escape-string-regexp": "^1.0.5"
+          }
+        },
+        "inquirer": {
+          "version": "6.5.2",
+          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
+          "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
+          "dev": true,
+          "requires": {
+            "ansi-escapes": "^3.2.0",
+            "chalk": "^2.4.2",
+            "cli-cursor": "^2.1.0",
+            "cli-width": "^2.0.0",
+            "external-editor": "^3.0.3",
+            "figures": "^2.0.0",
+            "lodash": "^4.17.12",
+            "mute-stream": "0.0.7",
+            "run-async": "^2.2.0",
+            "rxjs": "^6.4.0",
+            "string-width": "^2.1.0",
+            "strip-ansi": "^5.1.0",
+            "through": "^2.3.6"
+          }
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "mimic-fn": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+          "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+          "dev": true
+        },
+        "mute-stream": {
+          "version": "0.0.7",
+          "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+          "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
+          "dev": true
+        },
+        "onetime": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+          "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+          "dev": true,
+          "requires": {
+            "mimic-fn": "^1.0.0"
+          }
+        },
+        "restore-cursor": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+          "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+          "dev": true,
+          "requires": {
+            "onetime": "^2.0.0",
+            "signal-exit": "^3.0.2"
+          }
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        },
+        "string-width": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+          "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+          "dev": true,
+          "requires": {
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^4.0.0"
+          },
+          "dependencies": {
+            "strip-ansi": {
+              "version": "4.0.0",
+              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+              "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+              "dev": true,
+              "requires": {
+                "ansi-regex": "^3.0.0"
+              }
+            }
+          }
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          },
+          "dependencies": {
+            "ansi-regex": {
+              "version": "4.1.0",
+              "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+              "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+              "dev": true
+            }
+          }
+        },
+        "wrap-ansi": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+          "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^3.2.0",
+            "string-width": "^3.0.0",
+            "strip-ansi": "^5.0.0"
+          },
+          "dependencies": {
+            "string-width": {
+              "version": "3.1.0",
+              "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+              "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+              "dev": true,
+              "requires": {
+                "emoji-regex": "^7.0.1",
+                "is-fullwidth-code-point": "^2.0.0",
+                "strip-ansi": "^5.1.0"
+              }
+            }
+          }
+        }
+      }
+    },
+    "snyk-config": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmjs.org/snyk-config/-/snyk-config-2.2.3.tgz",
+      "integrity": "sha512-9NjxHVMd1U1LFw66Lya4LXgrsFUiuRiL4opxfTFo0LmMNzUoU5Bk/p0zDdg3FE5Wg61r4fP2D8w+QTl6M8CGiw==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.1.0",
+        "lodash": "^4.17.15",
+        "nconf": "^0.10.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "snyk-docker-plugin": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/snyk-docker-plugin/-/snyk-docker-plugin-2.6.1.tgz",
+      "integrity": "sha512-v3LIPILRL5faZ+qiIhF9on0rAxuFaQku3UwaiGumoTrfXywLkv7x8PJgdMnrsWUxDwB8EZFc1k2qvI6V6rTF5g==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.1.1",
+        "dockerfile-ast": "0.0.19",
+        "event-loop-spinner": "^1.1.0",
+        "semver": "^6.1.0",
+        "tar-stream": "^2.1.0",
+        "tslib": "^1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
+      }
+    },
+    "snyk-go-parser": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/snyk-go-parser/-/snyk-go-parser-1.4.0.tgz",
+      "integrity": "sha512-zcLA8u/WreycCjFKBblYfxszg7Fmnemuu9Ug/CE/jqF0yBXsI5DCWMteUvFkoa8DRntfGTlgf98TRl2aTSc2MQ==",
+      "dev": true,
+      "requires": {
+        "toml": "^3.0.0",
+        "tslib": "^1.10.0"
+      }
+    },
+    "snyk-go-plugin": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/snyk-go-plugin/-/snyk-go-plugin-1.13.0.tgz",
+      "integrity": "sha512-6lN9S8uO6LE1Y6ZJMZm3EZ8kvvI/vZh8r+JJGAPfVO2C265xymEpFBJ4Nn2or0Q0LlqZ8W8lWi1HUMXXid6k+w==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.1.1",
+        "graphlib": "^2.1.1",
+        "snyk-go-parser": "1.4.0",
+        "tmp": "0.1.0",
+        "tslib": "^1.10.0"
+      },
+      "dependencies": {
+        "tmp": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
+          "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
+          "dev": true,
+          "requires": {
+            "rimraf": "^2.6.3"
+          }
+        }
+      }
+    },
+    "snyk-gradle-plugin": {
+      "version": "3.2.5",
+      "resolved": "https://registry.npmjs.org/snyk-gradle-plugin/-/snyk-gradle-plugin-3.2.5.tgz",
+      "integrity": "sha512-XxPi/B16dGkV1USoyFbpn6LlSJ9SUC6Y6z/4lWuF4spLnKtWwpEb1bwTdBFsxnkUfqzIRtPr0+wcxxXvv9Rvcw==",
+      "dev": true,
+      "requires": {
+        "@snyk/cli-interface": "2.3.0",
+        "@types/debug": "^4.1.4",
+        "chalk": "^2.4.2",
+        "debug": "^4.1.1",
+        "tmp": "0.0.33",
+        "tslib": "^1.9.3"
+      },
+      "dependencies": {
+        "@snyk/cli-interface": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-2.3.0.tgz",
+          "integrity": "sha512-ecbylK5Ol2ySb/WbfPj0s0GuLQR+KWKFzUgVaoNHaSoN6371qRWwf2uVr+hPUP4gXqCai21Ug/RDArfOhlPwrQ==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.3"
+          }
+        }
+      }
+    },
+    "snyk-module": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/snyk-module/-/snyk-module-1.9.1.tgz",
+      "integrity": "sha512-A+CCyBSa4IKok5uEhqT+hV/35RO6APFNLqk9DRRHg7xW2/j//nPX8wTSZUPF8QeRNEk/sX+6df7M1y6PBHGSHA==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.1.0",
+        "hosted-git-info": "^2.7.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "snyk-mvn-plugin": {
+      "version": "2.10.0",
+      "resolved": "https://registry.npmjs.org/snyk-mvn-plugin/-/snyk-mvn-plugin-2.10.0.tgz",
+      "integrity": "sha512-npslocHJXUbdFxehMPQ8w4oX6bB6J6vHTWNRDF7u2+pIhVumQe1QOvZGjwh3up+vOCoKiEprO7gdt7vC8im1Vg==",
+      "dev": true,
+      "requires": {
+        "@snyk/cli-interface": "2.4.0",
+        "@snyk/java-call-graph-builder": "^1.3.4",
+        "debug": "^4.1.1",
+        "lodash": "^4.17.15",
+        "needle": "^2.4.0",
+        "tmp": "^0.1.0",
+        "tslib": "1.11.0"
+      },
+      "dependencies": {
+        "tmp": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
+          "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
+          "dev": true,
+          "requires": {
+            "rimraf": "^2.6.3"
+          }
+        },
+        "tslib": {
+          "version": "1.11.0",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.0.tgz",
+          "integrity": "sha512-BmndXUtiTn/VDDrJzQE7Mm22Ix3PxgLltW9bSNLoeCY31gnG2OPx0QqJnuc9oMIKioYrz487i6K9o4Pdn0j+Kg==",
+          "dev": true
+        }
+      }
+    },
+    "snyk-nodejs-lockfile-parser": {
+      "version": "1.21.0",
+      "resolved": "https://registry.npmjs.org/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.21.0.tgz",
+      "integrity": "sha512-U9uCMqA8+72KHD4/ONaAIvmb27C3J927Jtirda8dOkoxpS7Wy6ubsz4kl+rNkeJXfkLV1H7E8n52pzPH2bjXZA==",
+      "dev": true,
+      "requires": {
+        "@yarnpkg/lockfile": "^1.0.2",
+        "event-loop-spinner": "^1.1.0",
+        "graphlib": "^2.1.5",
+        "lodash": "^4.17.14",
+        "p-map": "2.1.0",
+        "snyk-config": "^3.0.0",
+        "source-map-support": "^0.5.7",
+        "tslib": "^1.9.3",
+        "uuid": "^3.3.2"
+      },
+      "dependencies": {
+        "snyk-config": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/snyk-config/-/snyk-config-3.0.0.tgz",
+          "integrity": "sha512-sXjd7gUqPTmgkhtLowFkFU5J4xyS7tDIRUbHmpW/dvTjgmiH0ujobJzSdaim4W6pbiIf4snkGJsvHM3/wmdR5w==",
+          "dev": true,
+          "requires": {
+            "debug": "^4.1.1",
+            "lodash": "^4.17.15",
+            "nconf": "^0.10.0"
+          }
+        }
+      }
+    },
+    "snyk-nuget-plugin": {
+      "version": "1.16.0",
+      "resolved": "https://registry.npmjs.org/snyk-nuget-plugin/-/snyk-nuget-plugin-1.16.0.tgz",
+      "integrity": "sha512-OEusK3JKKpR4Yto5KwuqjQGgb9wAhmDqBWSQomWdtKQVFrzn5B6BMzOFikUzmeMTnUGGON7gurQBLXeZZLhRqg==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.1.0",
+        "dotnet-deps-parser": "4.9.0",
+        "jszip": "^3.1.5",
+        "lodash": "^4.17.14",
+        "snyk-paket-parser": "1.5.0",
+        "tslib": "^1.9.3",
+        "xml2js": "^0.4.17"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "snyk-paket-parser": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/snyk-paket-parser/-/snyk-paket-parser-1.5.0.tgz",
+      "integrity": "sha512-1CYMPChJ9D9LBy3NLqHyv8TY7pR/LMISSr08LhfFw/FpfRZ+gTH8W6bbxCmybAYrOFNCqZkRprqOYDqZQFHipA==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.9.3"
+      }
+    },
+    "snyk-php-plugin": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/snyk-php-plugin/-/snyk-php-plugin-1.7.0.tgz",
+      "integrity": "sha512-mDe90xkqSEVrpx1ZC7ItqCOc6fZCySbE+pHVI+dAPUmf1C1LSWZrZVmAVeo/Dw9sJzJfzmcdAFQl+jZP8/uV0A==",
+      "dev": true,
+      "requires": {
+        "@snyk/cli-interface": "2.2.0",
+        "@snyk/composer-lockfile-parser": "1.2.0",
+        "tslib": "1.9.3"
+      },
+      "dependencies": {
+        "@snyk/cli-interface": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/@snyk/cli-interface/-/cli-interface-2.2.0.tgz",
+          "integrity": "sha512-sA7V2JhgqJB9z5uYotgQc5iNDv//y+Mdm39rANxmFjtZMSYJZHkP80arzPjw1mB5ni/sWec7ieYUUFeySZBfVg==",
+          "dev": true,
+          "requires": {
+            "tslib": "^1.9.3"
+          }
+        },
+        "tslib": {
+          "version": "1.9.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
+          "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
+          "dev": true
+        }
+      }
+    },
+    "snyk-policy": {
+      "version": "1.13.5",
+      "resolved": "https://registry.npmjs.org/snyk-policy/-/snyk-policy-1.13.5.tgz",
+      "integrity": "sha512-KI6GHt+Oj4fYKiCp7duhseUj5YhyL/zJOrrJg0u6r59Ux9w8gmkUYT92FHW27ihwuT6IPzdGNEuy06Yv2C9WaQ==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.1.0",
+        "email-validator": "^2.0.4",
+        "js-yaml": "^3.13.1",
+        "lodash.clonedeep": "^4.5.0",
+        "semver": "^6.0.0",
+        "snyk-module": "^1.9.1",
+        "snyk-resolve": "^1.0.1",
+        "snyk-try-require": "^1.3.1",
+        "then-fs": "^2.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        }
+      }
+    },
+    "snyk-python-plugin": {
+      "version": "1.17.0",
+      "resolved": "https://registry.npmjs.org/snyk-python-plugin/-/snyk-python-plugin-1.17.0.tgz",
+      "integrity": "sha512-EKdVOUlvhiVpXA5TeW8vyxYVqbITAfT+2AbL2ZRiiUNLP5ae+WiNYaPy7aB5HAS9IKBKih+IH8Ag65Xu1IYSYA==",
+      "dev": true,
+      "requires": {
+        "@snyk/cli-interface": "^2.0.3",
+        "tmp": "0.0.33"
+      }
+    },
+    "snyk-resolve": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/snyk-resolve/-/snyk-resolve-1.0.1.tgz",
+      "integrity": "sha512-7+i+LLhtBo1Pkth01xv+RYJU8a67zmJ8WFFPvSxyCjdlKIcsps4hPQFebhz+0gC5rMemlaeIV6cqwqUf9PEDpw==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.1.0",
+        "then-fs": "^2.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "snyk-resolve-deps": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/snyk-resolve-deps/-/snyk-resolve-deps-4.4.0.tgz",
+      "integrity": "sha512-aFPtN8WLqIk4E1ulMyzvV5reY1Iksz+3oPnUVib1jKdyTHymmOIYF7z8QZ4UUr52UsgmrD9EA/dq7jpytwFoOQ==",
+      "dev": true,
+      "requires": {
+        "@types/node": "^6.14.4",
+        "@types/semver": "^5.5.0",
+        "ansicolors": "^0.3.2",
+        "debug": "^3.2.5",
+        "lodash.assign": "^4.2.0",
+        "lodash.assignin": "^4.2.0",
+        "lodash.clone": "^4.5.0",
+        "lodash.flatten": "^4.4.0",
+        "lodash.get": "^4.4.2",
+        "lodash.set": "^4.3.2",
+        "lru-cache": "^4.0.0",
+        "semver": "^5.5.1",
+        "snyk-module": "^1.6.0",
+        "snyk-resolve": "^1.0.0",
+        "snyk-tree": "^1.0.0",
+        "snyk-try-require": "^1.1.1",
+        "then-fs": "^2.0.0"
+      },
+      "dependencies": {
+        "@types/node": {
+          "version": "6.14.10",
+          "resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.10.tgz",
+          "integrity": "sha512-pF4HjZGSog75kGq7B1InK/wt/N08BuPATo+7HRfv7gZUzccebwv/fmWVGs/j6LvSiLWpCuGGhql51M/wcQsNzA==",
+          "dev": true
+        },
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "snyk-sbt-plugin": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/snyk-sbt-plugin/-/snyk-sbt-plugin-2.11.0.tgz",
+      "integrity": "sha512-wUqHLAa3MzV6sVO+05MnV+lwc+T6o87FZZaY+43tQPytBI2Wq23O3j4POREM4fa2iFfiQJoEYD6c7xmhiEUsSA==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.1.1",
+        "semver": "^6.1.2",
+        "tmp": "^0.1.0",
+        "tree-kill": "^1.2.2",
+        "tslib": "^1.10.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        },
+        "tmp": {
+          "version": "0.1.0",
+          "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
+          "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
+          "dev": true,
+          "requires": {
+            "rimraf": "^2.6.3"
+          }
+        }
+      }
+    },
+    "snyk-tree": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/snyk-tree/-/snyk-tree-1.0.0.tgz",
+      "integrity": "sha1-D7cxdtvzLngvGRAClBYESPkRHMg=",
+      "dev": true,
+      "requires": {
+        "archy": "^1.0.0"
+      }
+    },
+    "snyk-try-require": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/snyk-try-require/-/snyk-try-require-1.3.1.tgz",
+      "integrity": "sha1-bgJvkuZK9/zM6h7lPVJIQeQYohI=",
+      "dev": true,
+      "requires": {
+        "debug": "^3.1.0",
+        "lodash.clonedeep": "^4.3.0",
+        "lru-cache": "^4.0.0",
+        "then-fs": "^2.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "sockjs": {
+      "version": "0.3.19",
+      "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz",
+      "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==",
+      "dev": true,
+      "requires": {
+        "faye-websocket": "^0.10.0",
+        "uuid": "^3.0.1"
+      }
+    },
+    "sockjs-client": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz",
+      "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.2.5",
+        "eventsource": "^1.0.7",
+        "faye-websocket": "~0.11.1",
+        "inherits": "^2.0.3",
+        "json3": "^3.3.2",
+        "url-parse": "^1.4.3"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "faye-websocket": {
+          "version": "0.11.3",
+          "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz",
+          "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==",
+          "dev": true,
+          "requires": {
+            "websocket-driver": ">=0.5.1"
+          }
+        }
+      }
+    },
+    "socks": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz",
+      "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==",
+      "dev": true,
+      "requires": {
+        "ip": "1.1.5",
+        "smart-buffer": "^4.1.0"
+      }
+    },
+    "socks-proxy-agent": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz",
+      "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==",
+      "dev": true,
+      "requires": {
+        "agent-base": "~4.2.1",
+        "socks": "~2.3.2"
+      },
+      "dependencies": {
+        "agent-base": {
+          "version": "4.2.1",
+          "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
+          "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
+          "dev": true,
+          "requires": {
+            "es6-promisify": "^5.0.0"
+          }
+        }
+      }
+    },
+    "source-list-map": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
+      "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
+      "dev": true
+    },
+    "source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+    },
+    "source-map-resolve": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
+      "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
+      "dev": true,
+      "requires": {
+        "atob": "^2.1.2",
+        "decode-uri-component": "^0.2.0",
+        "resolve-url": "^0.2.1",
+        "source-map-url": "^0.4.0",
+        "urix": "^0.1.0"
+      }
+    },
+    "source-map-support": {
+      "version": "0.5.19",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
+      "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      }
+    },
+    "source-map-url": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+      "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+      "dev": true
+    },
+    "spdx-correct": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+      "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+      "dev": true,
+      "requires": {
+        "spdx-expression-parse": "^3.0.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-exceptions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+      "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+      "dev": true
+    },
+    "spdx-expression-parse": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+      "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+      "dev": true,
+      "requires": {
+        "spdx-exceptions": "^2.1.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-license-ids": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+      "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
+      "dev": true
+    },
+    "spdy": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
+      "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.1.0",
+        "handle-thing": "^2.0.0",
+        "http-deceiver": "^1.2.7",
+        "select-hose": "^2.0.0",
+        "spdy-transport": "^3.0.0"
+      }
+    },
+    "spdy-transport": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz",
+      "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.1.0",
+        "detect-node": "^2.0.4",
+        "hpack.js": "^2.1.6",
+        "obuf": "^1.1.2",
+        "readable-stream": "^3.0.6",
+        "wbuf": "^1.7.3"
+      }
+    },
+    "split-string": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+      "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^3.0.0"
+      }
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+      "dev": true
+    },
+    "sshpk": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+      "dev": true,
+      "requires": {
+        "asn1": "~0.2.3",
+        "assert-plus": "^1.0.0",
+        "bcrypt-pbkdf": "^1.0.0",
+        "dashdash": "^1.12.0",
+        "ecc-jsbn": "~0.1.1",
+        "getpass": "^0.1.1",
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.0.2",
+        "tweetnacl": "~0.14.0"
+      }
+    },
+    "ssri": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
+      "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
+      "dev": true,
+      "requires": {
+        "figgy-pudding": "^3.5.1"
+      }
+    },
+    "static-extend": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+      "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+      "dev": true,
+      "requires": {
+        "define-property": "^0.2.5",
+        "object-copy": "^0.1.0"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        }
+      }
+    },
+    "statuses": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+      "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+      "dev": true
+    },
+    "stdout-stream": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
+      "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==",
+      "dev": true,
+      "requires": {
+        "readable-stream": "^2.0.1"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "stream-browserify": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+      "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
+      "dev": true,
+      "requires": {
+        "inherits": "~2.0.1",
+        "readable-stream": "^2.0.2"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "stream-each": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
+      "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.1.0",
+        "stream-shift": "^1.0.0"
+      }
+    },
+    "stream-http": {
+      "version": "2.8.3",
+      "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
+      "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
+      "dev": true,
+      "requires": {
+        "builtin-status-codes": "^3.0.0",
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.3.6",
+        "to-arraybuffer": "^1.0.0",
+        "xtend": "^4.0.0"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "stream-shift": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+      "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
+      "dev": true
+    },
+    "string-width": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+      "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+      "dev": true,
+      "requires": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+          "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "6.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+          "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^5.0.0"
+          }
+        }
+      }
+    },
+    "string.prototype.trimend": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
+      "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5"
+      }
+    },
+    "string.prototype.trimleft": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz",
+      "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==",
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5",
+        "string.prototype.trimstart": "^1.0.0"
+      }
+    },
+    "string.prototype.trimright": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz",
+      "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==",
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5",
+        "string.prototype.trimend": "^1.0.0"
+      }
+    },
+    "string.prototype.trimstart": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
+      "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.5"
+      }
+    },
+    "string_decoder": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "requires": {
+        "safe-buffer": "~5.2.0"
+      }
+    },
+    "strip-ansi": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+      "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+      "requires": {
+        "ansi-regex": "^2.0.0"
+      }
+    },
+    "strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+      "dev": true
+    },
+    "strip-eof": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+      "dev": true
+    },
+    "strip-indent": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+      "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+      "dev": true,
+      "requires": {
+        "get-stdin": "^4.0.1"
+      },
+      "dependencies": {
+        "get-stdin": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+          "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+          "dev": true
+        }
+      }
+    },
+    "strip-json-comments": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz",
+      "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==",
+      "dev": true
+    },
+    "supports-color": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+      "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "table": {
+      "version": "5.4.6",
+      "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
+      "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.10.2",
+        "lodash": "^4.17.14",
+        "slice-ansi": "^2.1.0",
+        "string-width": "^3.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        },
+        "emoji-regex": {
+          "version": "7.0.3",
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+          "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        }
+      }
+    },
+    "tapable": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
+      "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="
+    },
+    "tar": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
+      "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
+      "dev": true,
+      "requires": {
+        "block-stream": "*",
+        "fstream": "^1.0.12",
+        "inherits": "2"
+      }
+    },
+    "tar-stream": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz",
+      "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==",
+      "dev": true,
+      "requires": {
+        "bl": "^4.0.1",
+        "end-of-stream": "^1.4.1",
+        "fs-constants": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.1.1"
+      }
+    },
+    "temp-dir": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
+      "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==",
+      "dev": true
+    },
+    "tempfile": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/tempfile/-/tempfile-2.0.0.tgz",
+      "integrity": "sha1-awRGhWqbERTRhW/8vlCczLCXcmU=",
+      "dev": true,
+      "requires": {
+        "temp-dir": "^1.0.0",
+        "uuid": "^3.0.1"
+      },
+      "dependencies": {
+        "temp-dir": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz",
+          "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=",
+          "dev": true
+        }
+      }
+    },
+    "term-size": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
+      "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=",
+      "dev": true,
+      "requires": {
+        "execa": "^0.7.0"
+      }
+    },
+    "terser": {
+      "version": "4.6.12",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.12.tgz",
+      "integrity": "sha512-fnIwuaKjFPANG6MAixC/k1TDtnl1YlPLUlLVIxxGZUn1gfUx2+l3/zGNB72wya+lgsb50QBi2tUV75RiODwnww==",
+      "dev": true,
+      "requires": {
+        "commander": "^2.20.0",
+        "source-map": "~0.6.1",
+        "source-map-support": "~0.5.12"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.20.3",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+          "dev": true
+        }
+      }
+    },
+    "terser-webpack-plugin": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
+      "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
+      "dev": true,
+      "requires": {
+        "cacache": "^12.0.2",
+        "find-cache-dir": "^2.1.0",
+        "is-wsl": "^1.1.0",
+        "schema-utils": "^1.0.0",
+        "serialize-javascript": "^2.1.2",
+        "source-map": "^0.6.1",
+        "terser": "^4.1.2",
+        "webpack-sources": "^1.4.0",
+        "worker-farm": "^1.7.0"
+      },
+      "dependencies": {
+        "schema-utils": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+          "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+          "dev": true,
+          "requires": {
+            "ajv": "^6.1.0",
+            "ajv-errors": "^1.0.0",
+            "ajv-keywords": "^3.1.0"
+          }
+        }
+      }
+    },
+    "text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+      "dev": true
+    },
+    "then-fs": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/then-fs/-/then-fs-2.0.0.tgz",
+      "integrity": "sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI=",
+      "dev": true,
+      "requires": {
+        "promise": ">=3.2 <8"
+      }
+    },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
+    "through2": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+      "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+      "dev": true,
+      "requires": {
+        "readable-stream": "~2.3.6",
+        "xtend": "~4.0.1"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "2.3.7",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+          "dev": true,
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.3",
+            "isarray": "~1.0.0",
+            "process-nextick-args": "~2.0.0",
+            "safe-buffer": "~5.1.1",
+            "string_decoder": "~1.1.1",
+            "util-deprecate": "~1.0.1"
+          }
+        },
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+          "dev": true,
+          "requires": {
+            "safe-buffer": "~5.1.0"
+          }
+        }
+      }
+    },
+    "thunkify": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz",
+      "integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=",
+      "dev": true
+    },
+    "thunky": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
+      "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
+      "dev": true
+    },
+    "timed-out": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
+      "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=",
+      "dev": true
+    },
+    "timers-browserify": {
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz",
+      "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==",
+      "dev": true,
+      "requires": {
+        "setimmediate": "^1.0.4"
+      }
+    },
+    "tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dev": true,
+      "requires": {
+        "os-tmpdir": "~1.0.2"
+      }
+    },
+    "to-arraybuffer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
+      "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
+      "dev": true
+    },
+    "to-fast-properties": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+      "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+      "dev": true
+    },
+    "to-object-path": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+      "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "to-regex": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+      "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+      "dev": true,
+      "requires": {
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "regex-not": "^1.0.2",
+        "safe-regex": "^1.1.0"
+      }
+    },
+    "to-regex-range": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+      "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+      "dev": true,
+      "requires": {
+        "is-number": "^3.0.0",
+        "repeat-string": "^1.6.1"
+      }
+    },
+    "toasters": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/toasters/-/toasters-2.0.1.tgz",
+      "integrity": "sha512-zqZz0itO/iQJhnLKkTGpj1FhWg0YKo2EnAGD0zjDPrR2rQgfSymCaHaQT0Gp9VIuPpu+3UY8m4w4HTI49HpdFw=="
+    },
+    "toidentifier": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+      "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
+      "dev": true
+    },
+    "toml": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
+      "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
+      "dev": true
+    },
+    "toposort": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz",
+      "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk="
+    },
+    "tough-cookie": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+      "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+      "dev": true,
+      "requires": {
+        "psl": "^1.1.28",
+        "punycode": "^2.1.1"
+      }
+    },
+    "tree-kill": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+      "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+      "dev": true
+    },
+    "trim-newlines": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+      "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+      "dev": true
+    },
+    "true-case-path": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
+      "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.2"
+      }
+    },
+    "tryer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
+      "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==",
+      "dev": true
+    },
+    "tslib": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
+      "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==",
+      "dev": true
+    },
+    "tty-browserify": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+      "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
+      "dev": true
+    },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "tweetnacl": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+      "dev": true
+    },
+    "type-check": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "~1.1.2"
+      }
+    },
+    "type-fest": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+      "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+      "dev": true
+    },
+    "type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "dev": true,
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      }
+    },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+      "dev": true
+    },
+    "uglify-js": {
+      "version": "3.4.10",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
+      "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==",
+      "requires": {
+        "commander": "~2.19.0",
+        "source-map": "~0.6.1"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.19.0",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
+          "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg=="
+        }
+      }
+    },
+    "unicode-canonical-property-names-ecmascript": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
+      "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==",
+      "dev": true
+    },
+    "unicode-match-property-ecmascript": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz",
+      "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==",
+      "dev": true,
+      "requires": {
+        "unicode-canonical-property-names-ecmascript": "^1.0.4",
+        "unicode-property-aliases-ecmascript": "^1.0.4"
+      }
+    },
+    "unicode-match-property-value-ecmascript": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz",
+      "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==",
+      "dev": true
+    },
+    "unicode-property-aliases-ecmascript": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz",
+      "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==",
+      "dev": true
+    },
+    "union-value": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+      "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
+      "dev": true,
+      "requires": {
+        "arr-union": "^3.1.0",
+        "get-value": "^2.0.6",
+        "is-extendable": "^0.1.1",
+        "set-value": "^2.0.1"
+      }
+    },
+    "uniq": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
+      "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8="
+    },
+    "unique-filename": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
+      "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
+      "dev": true,
+      "requires": {
+        "unique-slug": "^2.0.0"
+      }
+    },
+    "unique-slug": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
+      "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
+      "dev": true,
+      "requires": {
+        "imurmurhash": "^0.1.4"
+      }
+    },
+    "unique-string": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz",
+      "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=",
+      "dev": true,
+      "requires": {
+        "crypto-random-string": "^1.0.0"
+      }
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+      "dev": true
+    },
+    "unset-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+      "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+      "dev": true,
+      "requires": {
+        "has-value": "^0.3.1",
+        "isobject": "^3.0.0"
+      },
+      "dependencies": {
+        "has-value": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+          "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+          "dev": true,
+          "requires": {
+            "get-value": "^2.0.3",
+            "has-values": "^0.1.4",
+            "isobject": "^2.0.0"
+          },
+          "dependencies": {
+            "isobject": {
+              "version": "2.1.0",
+              "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+              "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+              "dev": true,
+              "requires": {
+                "isarray": "1.0.0"
+              }
+            }
+          }
+        },
+        "has-values": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+          "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+          "dev": true
+        }
+      }
+    },
+    "unzip-response": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz",
+      "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=",
+      "dev": true
+    },
+    "upath": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
+      "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
+      "dev": true
+    },
+    "upper-case": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
+      "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
+    },
+    "uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "dev": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "urix": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+      "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+      "dev": true
+    },
+    "url": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+      "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+      "dev": true,
+      "requires": {
+        "punycode": "1.3.2",
+        "querystring": "0.2.0"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.3.2",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+          "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+          "dev": true
+        }
+      }
+    },
+    "url-parse": {
+      "version": "1.4.7",
+      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
+      "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
+      "dev": true,
+      "requires": {
+        "querystringify": "^2.1.1",
+        "requires-port": "^1.0.0"
+      }
+    },
+    "url-parse-lax": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz",
+      "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=",
+      "dev": true,
+      "requires": {
+        "prepend-http": "^1.0.1"
+      }
+    },
+    "use": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+      "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
+      "dev": true
+    },
+    "util": {
+      "version": "0.11.1",
+      "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
+      "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
+      "dev": true,
+      "requires": {
+        "inherits": "2.0.3"
+      },
+      "dependencies": {
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        }
+      }
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+    },
+    "util.promisify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
+      "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==",
+      "requires": {
+        "define-properties": "^1.1.2",
+        "object.getownpropertydescriptors": "^2.0.3"
+      }
+    },
+    "utila": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
+      "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw="
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+      "dev": true
+    },
+    "uuid": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+      "dev": true
+    },
+    "v8-compile-cache": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
+      "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
+      "dev": true
+    },
+    "validate-npm-package-license": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
+      "requires": {
+        "spdx-correct": "^3.0.0",
+        "spdx-expression-parse": "^3.0.0"
+      }
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+      "dev": true
+    },
+    "verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    },
+    "vm-browserify": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
+      "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
+      "dev": true
+    },
+    "vscode-languageserver-types": {
+      "version": "3.15.1",
+      "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz",
+      "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==",
+      "dev": true
+    },
+    "vue": {
+      "version": "2.6.11",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
+      "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
+    },
+    "vue-eslint-parser": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz",
+      "integrity": "sha512-JlHVZwBBTNVvzmifwjpZYn0oPWH2SgWv5dojlZBsrhablDu95VFD+hriB1rQGwbD+bms6g+rAFhQHk6+NyiS6g==",
+      "dev": true,
+      "requires": {
+        "debug": "^4.1.0",
+        "eslint-scope": "^4.0.0",
+        "eslint-visitor-keys": "^1.0.0",
+        "espree": "^4.1.0",
+        "esquery": "^1.0.1",
+        "lodash": "^4.17.11"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "6.4.1",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+          "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+          "dev": true
+        },
+        "eslint-scope": {
+          "version": "4.0.3",
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+          "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+          "dev": true,
+          "requires": {
+            "esrecurse": "^4.1.0",
+            "estraverse": "^4.1.1"
+          }
+        },
+        "espree": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz",
+          "integrity": "sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==",
+          "dev": true,
+          "requires": {
+            "acorn": "^6.0.2",
+            "acorn-jsx": "^5.0.0",
+            "eslint-visitor-keys": "^1.0.0"
+          }
+        }
+      }
+    },
+    "vue-hot-reload-api": {
+      "version": "2.3.4",
+      "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
+      "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog=="
+    },
+    "vue-loader": {
+      "version": "15.9.1",
+      "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.1.tgz",
+      "integrity": "sha512-IaPU2KOPjs/QjMlxFs/TiTtQUSbftQ7lsAvoxe21rtcQohsMhx+1AltXCNhZIpIn46PtODiAgz+o8RbMpKtmJw==",
+      "requires": {
+        "@vue/component-compiler-utils": "^3.1.0",
+        "hash-sum": "^1.0.2",
+        "loader-utils": "^1.1.0",
+        "vue-hot-reload-api": "^2.3.0",
+        "vue-style-loader": "^4.1.0"
+      },
+      "dependencies": {
+        "big.js": {
+          "version": "5.2.2",
+          "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+          "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
+        },
+        "emojis-list": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+          "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
+        },
+        "json5": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        },
+        "loader-utils": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+          "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^1.0.1"
+          }
+        }
+      }
+    },
+    "vue-router": {
+      "version": "3.1.6",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.1.6.tgz",
+      "integrity": "sha512-GYhn2ynaZlysZMkFE5oCHRUTqE8BWs/a9YbKpNLi0i7xD6KG1EzDqpHQmv1F5gXjr8kL5iIVS8EOtRaVUEXTqA=="
+    },
+    "vue-style-loader": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",
+      "integrity": "sha512-0ip8ge6Gzz/Bk0iHovU9XAUQaFt/G2B61bnWa2tCcqqdgfHs1lF9xXorFbE55Gmy92okFT+8bfmySuUOu13vxQ==",
+      "requires": {
+        "hash-sum": "^1.0.2",
+        "loader-utils": "^1.0.2"
+      },
+      "dependencies": {
+        "big.js": {
+          "version": "5.2.2",
+          "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+          "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
+        },
+        "emojis-list": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+          "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
+        },
+        "json5": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        },
+        "loader-utils": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+          "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^1.0.1"
+          }
+        }
+      }
+    },
+    "vue-template-compiler": {
+      "version": "2.6.11",
+      "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz",
+      "integrity": "sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==",
+      "dev": true,
+      "requires": {
+        "de-indent": "^1.0.2",
+        "he": "^1.1.0"
+      }
+    },
+    "vue-template-es2015-compiler": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz",
+      "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw=="
+    },
+    "vuex": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.3.0.tgz",
+      "integrity": "sha512-1MfcBt+YFd20DPwKe0ThhYm1UEXZya4gVKUvCy7AtS11YAOUR+9a6u4fsv1Rr6ePZCDNxW/M1zuIaswp6nNv8Q=="
+    },
+    "watchpack": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz",
+      "integrity": "sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==",
+      "dev": true,
+      "requires": {
+        "chokidar": "^2.1.8",
+        "graceful-fs": "^4.1.2",
+        "neo-async": "^2.5.0"
+      }
+    },
+    "wbuf": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
+      "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==",
+      "dev": true,
+      "requires": {
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
+    "webpack": {
+      "version": "4.43.0",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz",
+      "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==",
+      "dev": true,
+      "requires": {
+        "@webassemblyjs/ast": "1.9.0",
+        "@webassemblyjs/helper-module-context": "1.9.0",
+        "@webassemblyjs/wasm-edit": "1.9.0",
+        "@webassemblyjs/wasm-parser": "1.9.0",
+        "acorn": "^6.4.1",
+        "ajv": "^6.10.2",
+        "ajv-keywords": "^3.4.1",
+        "chrome-trace-event": "^1.0.2",
+        "enhanced-resolve": "^4.1.0",
+        "eslint-scope": "^4.0.3",
+        "json-parse-better-errors": "^1.0.2",
+        "loader-runner": "^2.4.0",
+        "loader-utils": "^1.2.3",
+        "memory-fs": "^0.4.1",
+        "micromatch": "^3.1.10",
+        "mkdirp": "^0.5.3",
+        "neo-async": "^2.6.1",
+        "node-libs-browser": "^2.2.1",
+        "schema-utils": "^1.0.0",
+        "tapable": "^1.1.3",
+        "terser-webpack-plugin": "^1.4.3",
+        "watchpack": "^1.6.1",
+        "webpack-sources": "^1.4.1"
+      },
+      "dependencies": {
+        "acorn": {
+          "version": "6.4.1",
+          "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
+          "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
+          "dev": true
+        },
+        "big.js": {
+          "version": "5.2.2",
+          "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+          "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+          "dev": true
+        },
+        "emojis-list": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+          "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+          "dev": true
+        },
+        "eslint-scope": {
+          "version": "4.0.3",
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+          "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+          "dev": true,
+          "requires": {
+            "esrecurse": "^4.1.0",
+            "estraverse": "^4.1.1"
+          }
+        },
+        "json5": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+          "dev": true,
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        },
+        "loader-utils": {
+          "version": "1.4.0",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
+          "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^1.0.1"
+          }
+        },
+        "schema-utils": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+          "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+          "dev": true,
+          "requires": {
+            "ajv": "^6.1.0",
+            "ajv-errors": "^1.0.0",
+            "ajv-keywords": "^3.1.0"
+          }
+        }
+      }
+    },
+    "webpack-bundle-analyzer": {
+      "version": "3.7.0",
+      "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.7.0.tgz",
+      "integrity": "sha512-mETdjZ30a3Yf+NTB/wqTgACK7rAYQl5uxKK0WVTNmF0sM3Uv8s3R58YZMW7Rhu0Lk2Rmuhdj5dcH5Q76zCDVdA==",
+      "dev": true,
+      "requires": {
+        "acorn": "^7.1.1",
+        "acorn-walk": "^7.1.1",
+        "bfj": "^6.1.1",
+        "chalk": "^2.4.1",
+        "commander": "^2.18.0",
+        "ejs": "^2.6.1",
+        "express": "^4.16.3",
+        "filesize": "^3.6.1",
+        "gzip-size": "^5.0.0",
+        "lodash": "^4.17.15",
+        "mkdirp": "^0.5.1",
+        "opener": "^1.5.1",
+        "ws": "^6.0.0"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.20.3",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+          "dev": true
+        }
+      }
+    },
+    "webpack-cli": {
+      "version": "3.3.11",
+      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.11.tgz",
+      "integrity": "sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g==",
+      "dev": true,
+      "requires": {
+        "chalk": "2.4.2",
+        "cross-spawn": "6.0.5",
+        "enhanced-resolve": "4.1.0",
+        "findup-sync": "3.0.0",
+        "global-modules": "2.0.0",
+        "import-local": "2.0.0",
+        "interpret": "1.2.0",
+        "loader-utils": "1.2.3",
+        "supports-color": "6.1.0",
+        "v8-compile-cache": "2.0.3",
+        "yargs": "13.2.4"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        },
+        "big.js": {
+          "version": "5.2.2",
+          "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+          "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+          "dev": true
+        },
+        "cliui": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+          "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+          "dev": true,
+          "requires": {
+            "string-width": "^3.1.0",
+            "strip-ansi": "^5.2.0",
+            "wrap-ansi": "^5.1.0"
+          }
+        },
+        "emoji-regex": {
+          "version": "7.0.3",
+          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+          "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+          "dev": true
+        },
+        "enhanced-resolve": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz",
+          "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "memory-fs": "^0.4.0",
+            "tapable": "^1.0.0"
+          }
+        },
+        "execa": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+          "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+          "dev": true,
+          "requires": {
+            "cross-spawn": "^6.0.0",
+            "get-stream": "^4.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"
+          }
+        },
+        "find-up": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+          "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+          "dev": true,
+          "requires": {
+            "locate-path": "^3.0.0"
+          }
+        },
+        "get-caller-file": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+          "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+          "dev": true
+        },
+        "get-stream": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+          "dev": true,
+          "requires": {
+            "pump": "^3.0.0"
+          }
+        },
+        "invert-kv": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
+          "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "json5": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+          "dev": true,
+          "requires": {
+            "minimist": "^1.2.0"
+          }
+        },
+        "lcid": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
+          "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
+          "dev": true,
+          "requires": {
+            "invert-kv": "^2.0.0"
+          }
+        },
+        "loader-utils": {
+          "version": "1.2.3",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
+          "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^2.0.0",
+            "json5": "^1.0.1"
+          }
+        },
+        "locate-path": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+          "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+          "dev": true,
+          "requires": {
+            "p-locate": "^3.0.0",
+            "path-exists": "^3.0.0"
+          }
+        },
+        "os-locale": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
+          "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
+          "dev": true,
+          "requires": {
+            "execa": "^1.0.0",
+            "lcid": "^2.0.0",
+            "mem": "^4.0.0"
+          }
+        },
+        "p-limit": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+          "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+          "dev": true,
+          "requires": {
+            "p-try": "^2.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+          "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+          "dev": true,
+          "requires": {
+            "p-limit": "^2.0.0"
+          }
+        },
+        "p-try": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+          "dev": true
+        },
+        "require-main-filename": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+          "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+          "dev": true
+        },
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+          "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^4.1.0"
+          }
+        },
+        "v8-compile-cache": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz",
+          "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==",
+          "dev": true
+        },
+        "which-module": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+          "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+          "dev": true
+        },
+        "wrap-ansi": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+          "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^3.2.0",
+            "string-width": "^3.0.0",
+            "strip-ansi": "^5.0.0"
+          }
+        },
+        "y18n": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+          "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+          "dev": true
+        },
+        "yargs": {
+          "version": "13.2.4",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz",
+          "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==",
+          "dev": true,
+          "requires": {
+            "cliui": "^5.0.0",
+            "find-up": "^3.0.0",
+            "get-caller-file": "^2.0.1",
+            "os-locale": "^3.1.0",
+            "require-directory": "^2.1.1",
+            "require-main-filename": "^2.0.0",
+            "set-blocking": "^2.0.0",
+            "string-width": "^3.0.0",
+            "which-module": "^2.0.0",
+            "y18n": "^4.0.0",
+            "yargs-parser": "^13.1.0"
+          }
+        },
+        "yargs-parser": {
+          "version": "13.1.2",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
+          "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
+          "dev": true,
+          "requires": {
+            "camelcase": "^5.0.0",
+            "decamelize": "^1.2.0"
+          }
+        }
+      }
+    },
+    "webpack-dev-middleware": {
+      "version": "3.7.2",
+      "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz",
+      "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==",
+      "dev": true,
+      "requires": {
+        "memory-fs": "^0.4.1",
+        "mime": "^2.4.4",
+        "mkdirp": "^0.5.1",
+        "range-parser": "^1.2.1",
+        "webpack-log": "^2.0.0"
+      },
+      "dependencies": {
+        "mime": {
+          "version": "2.4.4",
+          "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
+          "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==",
+          "dev": true
+        }
+      }
+    },
+    "webpack-dev-server": {
+      "version": "3.10.3",
+      "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz",
+      "integrity": "sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ==",
+      "dev": true,
+      "requires": {
+        "ansi-html": "0.0.7",
+        "bonjour": "^3.5.0",
+        "chokidar": "^2.1.8",
+        "compression": "^1.7.4",
+        "connect-history-api-fallback": "^1.6.0",
+        "debug": "^4.1.1",
+        "del": "^4.1.1",
+        "express": "^4.17.1",
+        "html-entities": "^1.2.1",
+        "http-proxy-middleware": "0.19.1",
+        "import-local": "^2.0.0",
+        "internal-ip": "^4.3.0",
+        "ip": "^1.1.5",
+        "is-absolute-url": "^3.0.3",
+        "killable": "^1.0.1",
+        "loglevel": "^1.6.6",
+        "opn": "^5.5.0",
+        "p-retry": "^3.0.1",
+        "portfinder": "^1.0.25",
+        "schema-utils": "^1.0.0",
+        "selfsigned": "^1.10.7",
+        "semver": "^6.3.0",
+        "serve-index": "^1.9.1",
+        "sockjs": "0.3.19",
+        "sockjs-client": "1.4.0",
+        "spdy": "^4.0.1",
+        "strip-ansi": "^3.0.1",
+        "supports-color": "^6.1.0",
+        "url": "^0.11.0",
+        "webpack-dev-middleware": "^3.7.2",
+        "webpack-log": "^2.0.0",
+        "ws": "^6.2.1",
+        "yargs": "12.0.5"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "cliui": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
+          "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
+          "dev": true,
+          "requires": {
+            "string-width": "^2.1.1",
+            "strip-ansi": "^4.0.0",
+            "wrap-ansi": "^2.0.0"
+          },
+          "dependencies": {
+            "strip-ansi": {
+              "version": "4.0.0",
+              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+              "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+              "dev": true,
+              "requires": {
+                "ansi-regex": "^3.0.0"
+              }
+            }
+          }
+        },
+        "execa": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+          "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+          "dev": true,
+          "requires": {
+            "cross-spawn": "^6.0.0",
+            "get-stream": "^4.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"
+          }
+        },
+        "find-up": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+          "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+          "dev": true,
+          "requires": {
+            "locate-path": "^3.0.0"
+          }
+        },
+        "get-stream": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+          "dev": true,
+          "requires": {
+            "pump": "^3.0.0"
+          }
+        },
+        "invert-kv": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
+          "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "lcid": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
+          "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
+          "dev": true,
+          "requires": {
+            "invert-kv": "^2.0.0"
+          }
+        },
+        "locate-path": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+          "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+          "dev": true,
+          "requires": {
+            "p-locate": "^3.0.0",
+            "path-exists": "^3.0.0"
+          }
+        },
+        "os-locale": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
+          "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
+          "dev": true,
+          "requires": {
+            "execa": "^1.0.0",
+            "lcid": "^2.0.0",
+            "mem": "^4.0.0"
+          }
+        },
+        "p-limit": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+          "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+          "dev": true,
+          "requires": {
+            "p-try": "^2.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+          "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+          "dev": true,
+          "requires": {
+            "p-limit": "^2.0.0"
+          }
+        },
+        "p-try": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+          "dev": true
+        },
+        "schema-utils": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+          "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+          "dev": true,
+          "requires": {
+            "ajv": "^6.1.0",
+            "ajv-errors": "^1.0.0",
+            "ajv-keywords": "^3.1.0"
+          }
+        },
+        "semver": {
+          "version": "6.3.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+          "dev": true
+        },
+        "string-width": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+          "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+          "dev": true,
+          "requires": {
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^4.0.0"
+          },
+          "dependencies": {
+            "strip-ansi": {
+              "version": "4.0.0",
+              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+              "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+              "dev": true,
+              "requires": {
+                "ansi-regex": "^3.0.0"
+              }
+            }
+          }
+        },
+        "which-module": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+          "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+          "dev": true
+        },
+        "yargs": {
+          "version": "12.0.5",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
+          "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
+          "dev": true,
+          "requires": {
+            "cliui": "^4.0.0",
+            "decamelize": "^1.2.0",
+            "find-up": "^3.0.0",
+            "get-caller-file": "^1.0.1",
+            "os-locale": "^3.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 || ^4.0.0",
+            "yargs-parser": "^11.1.1"
+          }
+        },
+        "yargs-parser": {
+          "version": "11.1.1",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
+          "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
+          "dev": true,
+          "requires": {
+            "camelcase": "^5.0.0",
+            "decamelize": "^1.2.0"
+          }
+        }
+      }
+    },
+    "webpack-log": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
+      "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==",
+      "dev": true,
+      "requires": {
+        "ansi-colors": "^3.0.0",
+        "uuid": "^3.3.2"
+      }
+    },
+    "webpack-md5-hash": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/webpack-md5-hash/-/webpack-md5-hash-0.0.6.tgz",
+      "integrity": "sha512-HrQ0AJpeXHRa3IjsgyyEfTx8EqYs5y/4x/WklSYsNDcqBixHzCkrmJV5U+4ks+sx7ycKoIdqWLdyuk913FCS+Q==",
+      "requires": {
+        "md5": "^2.0.0"
+      }
+    },
+    "webpack-merge": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz",
+      "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==",
+      "requires": {
+        "lodash": "^4.17.15"
+      }
+    },
+    "webpack-sources": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz",
+      "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==",
+      "dev": true,
+      "requires": {
+        "source-list-map": "^2.0.0",
+        "source-map": "~0.6.1"
+      }
+    },
+    "websocket-driver": {
+      "version": "0.7.3",
+      "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz",
+      "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==",
+      "dev": true,
+      "requires": {
+        "http-parser-js": ">=0.4.0 <0.4.11",
+        "safe-buffer": ">=5.1.0",
+        "websocket-extensions": ">=0.1.1"
+      }
+    },
+    "websocket-extensions": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
+      "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
+      "dev": true
+    },
+    "which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "which-module": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
+      "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
+      "dev": true
+    },
+    "wide-align": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+      "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+      "dev": true,
+      "requires": {
+        "string-width": "^1.0.2 || 2"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+          "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+          "dev": true,
+          "requires": {
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^4.0.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^3.0.0"
+          }
+        }
+      }
+    },
+    "widest-line": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
+      "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
+      "dev": true,
+      "requires": {
+        "string-width": "^2.1.1"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+          "dev": true
+        },
+        "string-width": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+          "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+          "dev": true,
+          "requires": {
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^4.0.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^3.0.0"
+          }
+        }
+      }
+    },
+    "window-size": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz",
+      "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=",
+      "dev": true
+    },
+    "windows-release": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.0.tgz",
+      "integrity": "sha512-2HetyTg1Y+R+rUgrKeUEhAG/ZuOmTrI1NBb3ZyAGQMYmOJjBBPe4MTodghRkmLJZHwkuPi02anbeGP+Zf401LQ==",
+      "dev": true,
+      "requires": {
+        "execa": "^1.0.0"
+      },
+      "dependencies": {
+        "execa": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+          "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+          "dev": true,
+          "requires": {
+            "cross-spawn": "^6.0.0",
+            "get-stream": "^4.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"
+          }
+        },
+        "get-stream": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+          "dev": true,
+          "requires": {
+            "pump": "^3.0.0"
+          }
+        }
+      }
+    },
+    "word-wrap": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+      "dev": true
+    },
+    "worker-farm": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
+      "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==",
+      "dev": true,
+      "requires": {
+        "errno": "~0.1.7"
+      }
+    },
+    "wrap-ansi": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+      "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+      "dev": true,
+      "requires": {
+        "string-width": "^1.0.1",
+        "strip-ansi": "^3.0.1"
+      },
+      "dependencies": {
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "dev": true,
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "dev": true,
+          "requires": {
+            "code-point-at": "^1.0.0",
+            "is-fullwidth-code-point": "^1.0.0",
+            "strip-ansi": "^3.0.0"
+          }
+        }
+      }
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+      "dev": true
+    },
+    "write": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+      "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+      "dev": true,
+      "requires": {
+        "mkdirp": "^0.5.1"
+      }
+    },
+    "write-file-atomic": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
+      "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.11",
+        "imurmurhash": "^0.1.4",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "ws": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
+      "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
+      "dev": true,
+      "requires": {
+        "async-limiter": "~1.0.0"
+      }
+    },
+    "xdg-basedir": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz",
+      "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=",
+      "dev": true
+    },
+    "xml2js": {
+      "version": "0.4.23",
+      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
+      "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
+      "dev": true,
+      "requires": {
+        "sax": ">=0.6.0",
+        "xmlbuilder": "~11.0.0"
+      },
+      "dependencies": {
+        "xmlbuilder": {
+          "version": "11.0.1",
+          "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+          "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+          "dev": true
+        }
+      }
+    },
+    "xmlbuilder": {
+      "version": "9.0.7",
+      "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
+      "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
+      "dev": true
+    },
+    "xregexp": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz",
+      "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=",
+      "dev": true
+    },
+    "xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "dev": true
+    },
+    "y18n": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
+      "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
+      "dev": true
+    },
+    "yallist": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+      "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
+    },
+    "yargs": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
+      "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
+      "dev": true,
+      "requires": {
+        "camelcase": "^3.0.0",
+        "cliui": "^3.2.0",
+        "decamelize": "^1.1.1",
+        "get-caller-file": "^1.0.1",
+        "os-locale": "^1.4.0",
+        "read-pkg-up": "^1.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^1.0.1",
+        "set-blocking": "^2.0.0",
+        "string-width": "^1.0.2",
+        "which-module": "^1.0.0",
+        "y18n": "^3.2.1",
+        "yargs-parser": "^5.0.0"
+      },
+      "dependencies": {
+        "camelcase": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+          "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+          "dev": true
+        },
+        "find-up": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+          "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+          "dev": true,
+          "requires": {
+            "path-exists": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "dev": true,
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        },
+        "load-json-file": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+          "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "parse-json": "^2.2.0",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0",
+            "strip-bom": "^2.0.0"
+          }
+        },
+        "path-exists": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+          "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+          "dev": true,
+          "requires": {
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "path-type": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+          "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "pify": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
+        },
+        "read-pkg": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+          "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+          "dev": true,
+          "requires": {
+            "load-json-file": "^1.0.0",
+            "normalize-package-data": "^2.3.2",
+            "path-type": "^1.0.0"
+          }
+        },
+        "read-pkg-up": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+          "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+          "dev": true,
+          "requires": {
+            "find-up": "^1.0.0",
+            "read-pkg": "^1.0.0"
+          }
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "dev": true,
+          "requires": {
+            "code-point-at": "^1.0.0",
+            "is-fullwidth-code-point": "^1.0.0",
+            "strip-ansi": "^3.0.0"
+          }
+        },
+        "strip-bom": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+          "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+          "dev": true,
+          "requires": {
+            "is-utf8": "^0.2.0"
+          }
+        }
+      }
+    },
+    "yargs-parser": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
+      "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
+      "dev": true,
+      "requires": {
+        "camelcase": "^3.0.0"
+      },
+      "dependencies": {
+        "camelcase": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+          "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+          "dev": true
+        }
+      }
+    }
+  }
+}

+ 2 - 0
frontend/store/index.js

@@ -3,6 +3,7 @@ import Vuex from "vuex";
 
 import user from "./modules/user";
 import modals from "./modules/modals";
+import sidebars from "./modules/sidebars";
 import station from "./modules/station";
 import admin from "./modules/admin";
 
@@ -12,6 +13,7 @@ export default new Vuex.Store({
 	modules: {
 		user,
 		modals,
+		sidebars,
 		station,
 		admin
 	},

+ 35 - 2
frontend/store/modules/admin.js

@@ -1,6 +1,7 @@
 /* eslint no-param-reassign: 0 */
 
 import Vue from "vue";
+import admin from "../../api/admin/index";
 
 const state = {};
 const getters = {};
@@ -100,17 +101,36 @@ const modules = {
 	stations: {
 		namespaced: true,
 		state: {
+			stations: [],
 			station: {},
 			editing: {}
 		},
 		getters: {},
 		actions: {
-			editStation: ({ commit }, station) => commit("editStation", station)
+			editStation: ({ commit }, station) =>
+				commit("editStation", station),
+			loadStations: ({ commit }, stations) =>
+				commit("loadStations", stations),
+			stationRemoved: ({ commit }, stationId) =>
+				commit("stationRemoved", stationId),
+			stationAdded: ({ commit }, station) =>
+				commit("stationAdded", station)
 		},
 		mutations: {
 			editStation(state, station) {
 				state.station = station;
 				state.editing = JSON.parse(JSON.stringify(station));
+			},
+			loadStations(state, stations) {
+				state.stations = stations;
+			},
+			stationAdded(state, station) {
+				state.stations.push(station);
+			},
+			stationRemoved(state, stationId) {
+				state.stations = state.stations.filter(station => {
+					return station._id !== stationId;
+				});
 			}
 		}
 	},
@@ -121,7 +141,20 @@ const modules = {
 		},
 		getters: {},
 		actions: {
-			viewReport: ({ commit }, report) => commit("viewReport", report)
+			viewReport: ({ commit }, report) => commit("viewReport", report),
+			/* eslint-disable-next-line no-unused-vars */
+			resolveReport: ({ commit }, reportId) => {
+				return new Promise((resolve, reject) => {
+					return admin.reports
+						.resolve(reportId)
+						.then(res => {
+							return resolve(res);
+						})
+						.catch(err => {
+							return reject(new Error(err.message));
+						});
+				});
+			}
 		},
 		mutations: {
 			viewReport(state, report) {

+ 65 - 0
frontend/store/modules/sidebars.js

@@ -0,0 +1,65 @@
+/* eslint no-param-reassign: 0 */
+
+const state = {
+	sidebars: {
+		station: {
+			songslist: false,
+			users: false,
+			playlist: false
+		}
+	},
+	currentlyActive: {}
+};
+
+const getters = {};
+
+const actions = {
+	toggleSidebar: ({ commit }, data) => {
+		commit("toggleSidebar", data);
+	},
+	openSidebar: ({ commit }, data) => {
+		commit("openSidebar", data);
+	},
+	closeCurrentSidebar: ({ commit }) => {
+		commit("closeCurrentSidebar");
+	}
+};
+
+const mutations = {
+	toggleSidebar(state, data) {
+		const { sector, sidebar } = data;
+
+		if (
+			state.currentlyActive.sidebar &&
+			state.currentlyActive.sidebar !== sidebar
+		) {
+			state.sidebars[state.currentlyActive.sector][
+				state.currentlyActive.sidebar
+			] = false;
+			state.currentlyActive = {};
+		}
+
+		state.sidebars[sector][sidebar] = !state.sidebars[sector][sidebar];
+
+		if (state.sidebars[sector][sidebar])
+			state.currentlyActive = { sector, sidebar };
+	},
+	openSidebar(state, data) {
+		const { sector, sidebar } = data;
+		state.sidebars[sector][sidebar] = true;
+		state.currentlyActive = { sector, sidebar };
+	},
+	closeCurrentSidebar(state) {
+		const { sector, sidebar } = state.currentlyActive;
+		state.sidebars[sector][sidebar] = false;
+		state.currentlyActive = {};
+	}
+};
+
+export default {
+	namespaced: true,
+	state,
+	getters,
+	actions,
+	mutations
+};

+ 7 - 0
frontend/store/modules/station.js

@@ -2,6 +2,7 @@
 
 const state = {
 	station: {},
+	privatePlaylistQueueSelected: null,
 	editing: {},
 	userCount: 0,
 	users: [],
@@ -41,6 +42,9 @@ const actions = {
 	},
 	updateNoSong: ({ commit }, noSong) => {
 		commit("updateNoSong", noSong);
+	},
+	updatePrivatePlaylistQueueSelected: ({ commit }, status) => {
+		commit("updatePrivatePlaylistQueueSelected", status);
 	}
 };
 
@@ -78,6 +82,9 @@ const mutations = {
 	},
 	updateNoSong(state, noSong) {
 		state.noSong = noSong;
+	},
+	updatePrivatePlaylistQueueSelected(state, status) {
+		state.privatePlaylistQueueSelected = status;
 	}
 };
 

+ 18 - 5
frontend/store/modules/user.js

@@ -76,11 +76,8 @@ const modules = {
 
 					return auth
 						.register(user)
-						.then(() => {
-							return resolve({
-								status: "success",
-								message: "Account registered!"
-							});
+						.then(res => {
+							return resolve(res);
 						})
 						.catch(err => {
 							return reject(new Error(err.message));
@@ -216,6 +213,22 @@ const modules = {
 				state.editing = id;
 			}
 		}
+	},
+	preferences: {
+		namespaced: true,
+		state: {
+			nightmode: true
+		},
+		actions: {
+			changeNightmode: ({ commit }, nightmode) => {
+				commit("changeNightmode", nightmode);
+			}
+		},
+		mutations: {
+			changeNightmode(state, nightmode) {
+				state.nightmode = nightmode;
+			}
+		}
 	}
 };
 

+ 1 - 0
frontend/validation.js

@@ -4,6 +4,7 @@ module.exports = {
 		az09_: /^[a-z0-9_]+$/,
 		emailSimple: /^[\x00-\x7F]+@[a-z0-9]+\.[a-z0-9]+(\.[a-z0-9]+)?$/,
 		ascii: /^[\x00-\x7F]+$/,
+		password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&])[A-Za-z\d$@$!%*?&]/,
 		custom: regex => {
 			return new RegExp(`^[${regex}]+$`);
 		}

+ 2 - 2
lerna.json

@@ -1,9 +1,9 @@
 {
-  "npmClient": "yarn",
+  "npmClient": "npm",
   "useWorkspaces": true,
   "packages": [
     "frontend",
     "backend"
   ],
   "version": "independent"
-}
+}

+ 7844 - 0
package-lock.json

@@ -0,0 +1,7844 @@
+{
+  "name": "@musare/musare",
+  "version": "2.1.1",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@babel/code-frame": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
+      "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
+      "dev": true,
+      "requires": {
+        "@babel/highlight": "^7.8.3"
+      }
+    },
+    "@babel/highlight": {
+      "version": "7.8.3",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz",
+      "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.0.0",
+        "esutils": "^2.0.2",
+        "js-tokens": "^4.0.0"
+      }
+    },
+    "@evocateur/libnpmaccess": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz",
+      "integrity": "sha512-KSCAHwNWro0CF2ukxufCitT9K5LjL/KuMmNzSu8wuwN2rjyKHD8+cmOsiybK+W5hdnwc5M1SmRlVCaMHQo+3rg==",
+      "dev": true,
+      "requires": {
+        "@evocateur/npm-registry-fetch": "^4.0.0",
+        "aproba": "^2.0.0",
+        "figgy-pudding": "^3.5.1",
+        "get-stream": "^4.0.0",
+        "npm-package-arg": "^6.1.0"
+      },
+      "dependencies": {
+        "aproba": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
+          "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
+          "dev": true
+        }
+      }
+    },
+    "@evocateur/libnpmpublish": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/@evocateur/libnpmpublish/-/libnpmpublish-1.2.2.tgz",
+      "integrity": "sha512-MJrrk9ct1FeY9zRlyeoyMieBjGDG9ihyyD9/Ft6MMrTxql9NyoEx2hw9casTIP4CdqEVu+3nQ2nXxoJ8RCXyFg==",
+      "dev": true,
+      "requires": {
+        "@evocateur/npm-registry-fetch": "^4.0.0",
+        "aproba": "^2.0.0",
+        "figgy-pudding": "^3.5.1",
+        "get-stream": "^4.0.0",
+        "lodash.clonedeep": "^4.5.0",
+        "normalize-package-data": "^2.4.0",
+        "npm-package-arg": "^6.1.0",
+        "semver": "^5.5.1",
+        "ssri": "^6.0.1"
+      },
+      "dependencies": {
+        "aproba": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
+          "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
+          "dev": true
+        },
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "@evocateur/npm-registry-fetch": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/@evocateur/npm-registry-fetch/-/npm-registry-fetch-4.0.0.tgz",
+      "integrity": "sha512-k1WGfKRQyhJpIr+P17O5vLIo2ko1PFLKwoetatdduUSt/aQ4J2sJrJwwatdI5Z3SiYk/mRH9S3JpdmMFd/IK4g==",
+      "dev": true,
+      "requires": {
+        "JSONStream": "^1.3.4",
+        "bluebird": "^3.5.1",
+        "figgy-pudding": "^3.4.1",
+        "lru-cache": "^5.1.1",
+        "make-fetch-happen": "^5.0.0",
+        "npm-package-arg": "^6.1.0",
+        "safe-buffer": "^5.1.2"
+      }
+    },
+    "@evocateur/pacote": {
+      "version": "9.6.5",
+      "resolved": "https://registry.npmjs.org/@evocateur/pacote/-/pacote-9.6.5.tgz",
+      "integrity": "sha512-EI552lf0aG2nOV8NnZpTxNo2PcXKPmDbF9K8eCBFQdIZwHNGN/mi815fxtmUMa2wTa1yndotICIDt/V0vpEx2w==",
+      "dev": true,
+      "requires": {
+        "@evocateur/npm-registry-fetch": "^4.0.0",
+        "bluebird": "^3.5.3",
+        "cacache": "^12.0.3",
+        "chownr": "^1.1.2",
+        "figgy-pudding": "^3.5.1",
+        "get-stream": "^4.1.0",
+        "glob": "^7.1.4",
+        "infer-owner": "^1.0.4",
+        "lru-cache": "^5.1.1",
+        "make-fetch-happen": "^5.0.0",
+        "minimatch": "^3.0.4",
+        "minipass": "^2.3.5",
+        "mississippi": "^3.0.0",
+        "mkdirp": "^0.5.1",
+        "normalize-package-data": "^2.5.0",
+        "npm-package-arg": "^6.1.0",
+        "npm-packlist": "^1.4.4",
+        "npm-pick-manifest": "^3.0.0",
+        "osenv": "^0.1.5",
+        "promise-inflight": "^1.0.1",
+        "promise-retry": "^1.1.1",
+        "protoduck": "^5.0.1",
+        "rimraf": "^2.6.3",
+        "safe-buffer": "^5.2.0",
+        "semver": "^5.7.0",
+        "ssri": "^6.0.1",
+        "tar": "^4.4.10",
+        "unique-filename": "^1.1.1",
+        "which": "^1.3.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "@lerna/add": {
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.20.0.tgz",
+      "integrity": "sha512-AnH1oRIEEg/VDa3SjYq4x1/UglEAvrZuV0WssHUMN81RTZgQk3we+Mv3qZNddrZ/fBcZu2IAdN/EQ3+ie2JxKQ==",
+      "dev": true,
+      "requires": {
+        "@evocateur/pacote": "^9.6.3",
+        "@lerna/bootstrap": "3.20.0",
+        "@lerna/command": "3.18.5",
+        "@lerna/filter-options": "3.20.0",
+        "@lerna/npm-conf": "3.16.0",
+        "@lerna/validation-error": "3.13.0",
+        "dedent": "^0.7.0",
+        "npm-package-arg": "^6.1.0",
+        "p-map": "^2.1.0",
+        "semver": "^6.2.0"
+      }
+    },
+    "@lerna/bootstrap": {
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.20.0.tgz",
+      "integrity": "sha512-Wylullx3uthKE7r4izo09qeRGL20Y5yONlQEjPCfnbxCC2Elu+QcPu4RC6kqKQ7b+g7pdC3OOgcHZjngrwr5XQ==",
+      "dev": true,
+      "requires": {
+        "@lerna/command": "3.18.5",
+        "@lerna/filter-options": "3.20.0",
+        "@lerna/has-npm-version": "3.16.5",
+        "@lerna/npm-install": "3.16.5",
+        "@lerna/package-graph": "3.18.5",
+        "@lerna/pulse-till-done": "3.13.0",
+        "@lerna/rimraf-dir": "3.16.5",
+        "@lerna/run-lifecycle": "3.16.2",
+        "@lerna/run-topologically": "3.18.5",
+        "@lerna/symlink-binary": "3.17.0",
+        "@lerna/symlink-dependencies": "3.17.0",
+        "@lerna/validation-error": "3.13.0",
+        "dedent": "^0.7.0",
+        "get-port": "^4.2.0",
+        "multimatch": "^3.0.0",
+        "npm-package-arg": "^6.1.0",
+        "npmlog": "^4.1.2",
+        "p-finally": "^1.0.0",
+        "p-map": "^2.1.0",
+        "p-map-series": "^1.0.0",
+        "p-waterfall": "^1.0.0",
+        "read-package-tree": "^5.1.6",
+        "semver": "^6.2.0"
+      }
+    },
+    "@lerna/changed": {
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.20.0.tgz",
+      "integrity": "sha512-+hzMFSldbRPulZ0vbKk6RD9f36gaH3Osjx34wrrZ62VB4pKmjyuS/rxVYkCA3viPLHoiIw2F8zHM5BdYoDSbjw==",
+      "dev": true,
+      "requires": {
+        "@lerna/collect-updates": "3.20.0",
+        "@lerna/command": "3.18.5",
+        "@lerna/listable": "3.18.5",
+        "@lerna/output": "3.13.0"
+      }
+    },
+    "@lerna/check-working-tree": {
+      "version": "3.16.5",
+      "resolved": "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.16.5.tgz",
+      "integrity": "sha512-xWjVBcuhvB8+UmCSb5tKVLB5OuzSpw96WEhS2uz6hkWVa/Euh1A0/HJwn2cemyK47wUrCQXtczBUiqnq9yX5VQ==",
+      "dev": true,
+      "requires": {
+        "@lerna/collect-uncommitted": "3.16.5",
+        "@lerna/describe-ref": "3.16.5",
+        "@lerna/validation-error": "3.13.0"
+      }
+    },
+    "@lerna/child-process": {
+      "version": "3.16.5",
+      "resolved": "https://registry.npmjs.org/@lerna/child-process/-/child-process-3.16.5.tgz",
+      "integrity": "sha512-vdcI7mzei9ERRV4oO8Y1LHBZ3A5+ampRKg1wq5nutLsUA4mEBN6H7JqjWOMY9xZemv6+kATm2ofjJ3lW5TszQg==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.3.1",
+        "execa": "^1.0.0",
+        "strong-log-transformer": "^2.0.0"
+      }
+    },
+    "@lerna/clean": {
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.20.0.tgz",
+      "integrity": "sha512-9ZdYrrjQvR5wNXmHfDsfjWjp0foOkCwKe3hrckTzkAeQA1ibyz5llGwz5e1AeFrV12e2/OLajVqYfe+qdkZUgg==",
+      "dev": true,
+      "requires": {
+        "@lerna/command": "3.18.5",
+        "@lerna/filter-options": "3.20.0",
+        "@lerna/prompt": "3.18.5",
+        "@lerna/pulse-till-done": "3.13.0",
+        "@lerna/rimraf-dir": "3.16.5",
+        "p-map": "^2.1.0",
+        "p-map-series": "^1.0.0",
+        "p-waterfall": "^1.0.0"
+      }
+    },
+    "@lerna/cli": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/cli/-/cli-3.18.5.tgz",
+      "integrity": "sha512-erkbxkj9jfc89vVs/jBLY/fM0I80oLmJkFUV3Q3wk9J3miYhP14zgVEBsPZY68IZlEjT6T3Xlq2xO1AVaatHsA==",
+      "dev": true,
+      "requires": {
+        "@lerna/global-options": "3.13.0",
+        "dedent": "^0.7.0",
+        "npmlog": "^4.1.2",
+        "yargs": "^14.2.2"
+      }
+    },
+    "@lerna/collect-uncommitted": {
+      "version": "3.16.5",
+      "resolved": "https://registry.npmjs.org/@lerna/collect-uncommitted/-/collect-uncommitted-3.16.5.tgz",
+      "integrity": "sha512-ZgqnGwpDZiWyzIQVZtQaj9tRizsL4dUOhuOStWgTAw1EMe47cvAY2kL709DzxFhjr6JpJSjXV5rZEAeU3VE0Hg==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "chalk": "^2.3.1",
+        "figgy-pudding": "^3.5.1",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/collect-updates": {
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.20.0.tgz",
+      "integrity": "sha512-qBTVT5g4fupVhBFuY4nI/3FSJtQVcDh7/gEPOpRxoXB/yCSnT38MFHXWl+y4einLciCjt/+0x6/4AG80fjay2Q==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "@lerna/describe-ref": "3.16.5",
+        "minimatch": "^3.0.4",
+        "npmlog": "^4.1.2",
+        "slash": "^2.0.0"
+      },
+      "dependencies": {
+        "slash": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+          "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+          "dev": true
+        }
+      }
+    },
+    "@lerna/command": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/command/-/command-3.18.5.tgz",
+      "integrity": "sha512-36EnqR59yaTU4HrR1C9XDFti2jRx0BgpIUBeWn129LZZB8kAB3ov1/dJNa1KcNRKp91DncoKHLY99FZ6zTNpMQ==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "@lerna/package-graph": "3.18.5",
+        "@lerna/project": "3.18.0",
+        "@lerna/validation-error": "3.13.0",
+        "@lerna/write-log-file": "3.13.0",
+        "clone-deep": "^4.0.1",
+        "dedent": "^0.7.0",
+        "execa": "^1.0.0",
+        "is-ci": "^2.0.0",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/conventional-commits": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.18.5.tgz",
+      "integrity": "sha512-qcvXIEJ3qSgalxXnQ7Yxp5H9Ta5TVyai6vEor6AAEHc20WiO7UIdbLDCxBtiiHMdGdpH85dTYlsoYUwsCJu3HQ==",
+      "dev": true,
+      "requires": {
+        "@lerna/validation-error": "3.13.0",
+        "conventional-changelog-angular": "^5.0.3",
+        "conventional-changelog-core": "^3.1.6",
+        "conventional-recommended-bump": "^5.0.0",
+        "fs-extra": "^8.1.0",
+        "get-stream": "^4.0.0",
+        "lodash.template": "^4.5.0",
+        "npm-package-arg": "^6.1.0",
+        "npmlog": "^4.1.2",
+        "pify": "^4.0.1",
+        "semver": "^6.2.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        }
+      }
+    },
+    "@lerna/create": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.18.5.tgz",
+      "integrity": "sha512-cHpjocbpKmLopCuZFI7cKEM3E/QY8y+yC7VtZ4FQRSaLU8D8i2xXtXmYaP1GOlVNavji0iwoXjuNpnRMInIr2g==",
+      "dev": true,
+      "requires": {
+        "@evocateur/pacote": "^9.6.3",
+        "@lerna/child-process": "3.16.5",
+        "@lerna/command": "3.18.5",
+        "@lerna/npm-conf": "3.16.0",
+        "@lerna/validation-error": "3.13.0",
+        "camelcase": "^5.0.0",
+        "dedent": "^0.7.0",
+        "fs-extra": "^8.1.0",
+        "globby": "^9.2.0",
+        "init-package-json": "^1.10.3",
+        "npm-package-arg": "^6.1.0",
+        "p-reduce": "^1.0.0",
+        "pify": "^4.0.1",
+        "semver": "^6.2.0",
+        "slash": "^2.0.0",
+        "validate-npm-package-license": "^3.0.3",
+        "validate-npm-package-name": "^3.0.0",
+        "whatwg-url": "^7.0.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        },
+        "slash": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+          "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+          "dev": true
+        }
+      }
+    },
+    "@lerna/create-symlink": {
+      "version": "3.16.2",
+      "resolved": "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-3.16.2.tgz",
+      "integrity": "sha512-pzXIJp6av15P325sgiIRpsPXLFmkisLhMBCy4764d+7yjf2bzrJ4gkWVMhsv4AdF0NN3OyZ5jjzzTtLNqfR+Jw==",
+      "dev": true,
+      "requires": {
+        "@zkochan/cmd-shim": "^3.1.0",
+        "fs-extra": "^8.1.0",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/describe-ref": {
+      "version": "3.16.5",
+      "resolved": "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.16.5.tgz",
+      "integrity": "sha512-c01+4gUF0saOOtDBzbLMFOTJDHTKbDFNErEY6q6i9QaXuzy9LNN62z+Hw4acAAZuJQhrVWncVathcmkkjvSVGw==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/diff": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/diff/-/diff-3.18.5.tgz",
+      "integrity": "sha512-u90lGs+B8DRA9Z/2xX4YaS3h9X6GbypmGV6ITzx9+1Ga12UWGTVlKaCXBgONMBjzJDzAQOK8qPTwLA57SeBLgA==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "@lerna/command": "3.18.5",
+        "@lerna/validation-error": "3.13.0",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/exec": {
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/@lerna/exec/-/exec-3.20.0.tgz",
+      "integrity": "sha512-pS1mmC7kzV668rHLWuv31ClngqeXjeHC8kJuM+W2D6IpUVMGQHLcCTYLudFgQsuKGVpl0DGNYG+sjLhAPiiu6A==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "@lerna/command": "3.18.5",
+        "@lerna/filter-options": "3.20.0",
+        "@lerna/profiler": "3.20.0",
+        "@lerna/run-topologically": "3.18.5",
+        "@lerna/validation-error": "3.13.0",
+        "p-map": "^2.1.0"
+      }
+    },
+    "@lerna/filter-options": {
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.20.0.tgz",
+      "integrity": "sha512-bmcHtvxn7SIl/R9gpiNMVG7yjx7WyT0HSGw34YVZ9B+3xF/83N3r5Rgtjh4hheLZ+Q91Or0Jyu5O3Nr+AwZe2g==",
+      "dev": true,
+      "requires": {
+        "@lerna/collect-updates": "3.20.0",
+        "@lerna/filter-packages": "3.18.0",
+        "dedent": "^0.7.0",
+        "figgy-pudding": "^3.5.1",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/filter-packages": {
+      "version": "3.18.0",
+      "resolved": "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-3.18.0.tgz",
+      "integrity": "sha512-6/0pMM04bCHNATIOkouuYmPg6KH3VkPCIgTfQmdkPJTullERyEQfNUKikrefjxo1vHOoCACDpy65JYyKiAbdwQ==",
+      "dev": true,
+      "requires": {
+        "@lerna/validation-error": "3.13.0",
+        "multimatch": "^3.0.0",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/get-npm-exec-opts": {
+      "version": "3.13.0",
+      "resolved": "https://registry.npmjs.org/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-3.13.0.tgz",
+      "integrity": "sha512-Y0xWL0rg3boVyJk6An/vurKzubyJKtrxYv2sj4bB8Mc5zZ3tqtv0ccbOkmkXKqbzvNNF7VeUt1OJ3DRgtC/QZw==",
+      "dev": true,
+      "requires": {
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/get-packed": {
+      "version": "3.16.0",
+      "resolved": "https://registry.npmjs.org/@lerna/get-packed/-/get-packed-3.16.0.tgz",
+      "integrity": "sha512-AjsFiaJzo1GCPnJUJZiTW6J1EihrPkc2y3nMu6m3uWFxoleklsSCyImumzVZJssxMi3CPpztj8LmADLedl9kXw==",
+      "dev": true,
+      "requires": {
+        "fs-extra": "^8.1.0",
+        "ssri": "^6.0.1",
+        "tar": "^4.4.8"
+      }
+    },
+    "@lerna/github-client": {
+      "version": "3.16.5",
+      "resolved": "https://registry.npmjs.org/@lerna/github-client/-/github-client-3.16.5.tgz",
+      "integrity": "sha512-rHQdn8Dv/CJrO3VouOP66zAcJzrHsm+wFuZ4uGAai2At2NkgKH+tpNhQy2H1PSC0Ezj9LxvdaHYrUzULqVK5Hw==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "@octokit/plugin-enterprise-rest": "^3.6.1",
+        "@octokit/rest": "^16.28.4",
+        "git-url-parse": "^11.1.2",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/gitlab-client": {
+      "version": "3.15.0",
+      "resolved": "https://registry.npmjs.org/@lerna/gitlab-client/-/gitlab-client-3.15.0.tgz",
+      "integrity": "sha512-OsBvRSejHXUBMgwWQqNoioB8sgzL/Pf1pOUhHKtkiMl6aAWjklaaq5HPMvTIsZPfS6DJ9L5OK2GGZuooP/5c8Q==",
+      "dev": true,
+      "requires": {
+        "node-fetch": "^2.5.0",
+        "npmlog": "^4.1.2",
+        "whatwg-url": "^7.0.0"
+      }
+    },
+    "@lerna/global-options": {
+      "version": "3.13.0",
+      "resolved": "https://registry.npmjs.org/@lerna/global-options/-/global-options-3.13.0.tgz",
+      "integrity": "sha512-SlZvh1gVRRzYLVluz9fryY1nJpZ0FHDGB66U9tFfvnnxmueckRQxLopn3tXj3NU1kc3QANT2I5BsQkOqZ4TEFQ==",
+      "dev": true
+    },
+    "@lerna/has-npm-version": {
+      "version": "3.16.5",
+      "resolved": "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-3.16.5.tgz",
+      "integrity": "sha512-WL7LycR9bkftyqbYop5rEGJ9sRFIV55tSGmbN1HLrF9idwOCD7CLrT64t235t3t4O5gehDnwKI5h2U3oxTrF8Q==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "semver": "^6.2.0"
+      }
+    },
+    "@lerna/import": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.18.5.tgz",
+      "integrity": "sha512-PH0WVLEgp+ORyNKbGGwUcrueW89K3Iuk/DDCz8mFyG2IG09l/jOF0vzckEyGyz6PO5CMcz4TI1al/qnp3FrahQ==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "@lerna/command": "3.18.5",
+        "@lerna/prompt": "3.18.5",
+        "@lerna/pulse-till-done": "3.13.0",
+        "@lerna/validation-error": "3.13.0",
+        "dedent": "^0.7.0",
+        "fs-extra": "^8.1.0",
+        "p-map-series": "^1.0.0"
+      }
+    },
+    "@lerna/info": {
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/@lerna/info/-/info-3.20.0.tgz",
+      "integrity": "sha512-Rsz+KQF9mczbGUbPTrtOed1N0C+cA08Qz0eX/oI+NNjvsryZIju/o7uedG4I3P55MBiAioNrJI88fHH3eTgYug==",
+      "dev": true,
+      "requires": {
+        "@lerna/command": "3.18.5",
+        "@lerna/output": "3.13.0",
+        "envinfo": "^7.3.1"
+      }
+    },
+    "@lerna/init": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/init/-/init-3.18.5.tgz",
+      "integrity": "sha512-oCwipWrha98EcJAHm8AGd2YFFLNI7AW9AWi0/LbClj1+XY9ah+uifXIgYGfTk63LbgophDd8936ZEpHMxBsbAg==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "@lerna/command": "3.18.5",
+        "fs-extra": "^8.1.0",
+        "p-map": "^2.1.0",
+        "write-json-file": "^3.2.0"
+      }
+    },
+    "@lerna/link": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/link/-/link-3.18.5.tgz",
+      "integrity": "sha512-xTN3vktJpkT7Nqc3QkZRtHO4bT5NvuLMtKNIBDkks0HpGxC9PRyyqwOoCoh1yOGbrWIuDezhfMg3Qow+6I69IQ==",
+      "dev": true,
+      "requires": {
+        "@lerna/command": "3.18.5",
+        "@lerna/package-graph": "3.18.5",
+        "@lerna/symlink-dependencies": "3.17.0",
+        "p-map": "^2.1.0",
+        "slash": "^2.0.0"
+      },
+      "dependencies": {
+        "slash": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+          "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+          "dev": true
+        }
+      }
+    },
+    "@lerna/list": {
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/@lerna/list/-/list-3.20.0.tgz",
+      "integrity": "sha512-fXTicPrfioVnRzknyPawmYIVkzDRBaQqk9spejS1S3O1DOidkihK0xxNkr8HCVC0L22w6f92g83qWDp2BYRUbg==",
+      "dev": true,
+      "requires": {
+        "@lerna/command": "3.18.5",
+        "@lerna/filter-options": "3.20.0",
+        "@lerna/listable": "3.18.5",
+        "@lerna/output": "3.13.0"
+      }
+    },
+    "@lerna/listable": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/listable/-/listable-3.18.5.tgz",
+      "integrity": "sha512-Sdr3pVyaEv5A7ZkGGYR7zN+tTl2iDcinryBPvtuv20VJrXBE8wYcOks1edBTcOWsPjCE/rMP4bo1pseyk3UTsg==",
+      "dev": true,
+      "requires": {
+        "@lerna/query-graph": "3.18.5",
+        "chalk": "^2.3.1",
+        "columnify": "^1.5.4"
+      }
+    },
+    "@lerna/log-packed": {
+      "version": "3.16.0",
+      "resolved": "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-3.16.0.tgz",
+      "integrity": "sha512-Fp+McSNBV/P2mnLUYTaSlG8GSmpXM7krKWcllqElGxvAqv6chk2K3c2k80MeVB4WvJ9tRjUUf+i7HUTiQ9/ckQ==",
+      "dev": true,
+      "requires": {
+        "byte-size": "^5.0.1",
+        "columnify": "^1.5.4",
+        "has-unicode": "^2.0.1",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/npm-conf": {
+      "version": "3.16.0",
+      "resolved": "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-3.16.0.tgz",
+      "integrity": "sha512-HbO3DUrTkCAn2iQ9+FF/eisDpWY5POQAOF1m7q//CZjdC2HSW3UYbKEGsSisFxSfaF9Z4jtrV+F/wX6qWs3CuA==",
+      "dev": true,
+      "requires": {
+        "config-chain": "^1.1.11",
+        "pify": "^4.0.1"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        }
+      }
+    },
+    "@lerna/npm-dist-tag": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-3.18.5.tgz",
+      "integrity": "sha512-xw0HDoIG6HreVsJND9/dGls1c+lf6vhu7yJoo56Sz5bvncTloYGLUppIfDHQr4ZvmPCK8rsh0euCVh2giPxzKQ==",
+      "dev": true,
+      "requires": {
+        "@evocateur/npm-registry-fetch": "^4.0.0",
+        "@lerna/otplease": "3.18.5",
+        "figgy-pudding": "^3.5.1",
+        "npm-package-arg": "^6.1.0",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/npm-install": {
+      "version": "3.16.5",
+      "resolved": "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.16.5.tgz",
+      "integrity": "sha512-hfiKk8Eku6rB9uApqsalHHTHY+mOrrHeWEs+gtg7+meQZMTS3kzv4oVp5cBZigndQr3knTLjwthT/FX4KvseFg==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "@lerna/get-npm-exec-opts": "3.13.0",
+        "fs-extra": "^8.1.0",
+        "npm-package-arg": "^6.1.0",
+        "npmlog": "^4.1.2",
+        "signal-exit": "^3.0.2",
+        "write-pkg": "^3.1.0"
+      }
+    },
+    "@lerna/npm-publish": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.18.5.tgz",
+      "integrity": "sha512-3etLT9+2L8JAx5F8uf7qp6iAtOLSMj+ZYWY6oUgozPi/uLqU0/gsMsEXh3F0+YVW33q0M61RpduBoAlOOZnaTg==",
+      "dev": true,
+      "requires": {
+        "@evocateur/libnpmpublish": "^1.2.2",
+        "@lerna/otplease": "3.18.5",
+        "@lerna/run-lifecycle": "3.16.2",
+        "figgy-pudding": "^3.5.1",
+        "fs-extra": "^8.1.0",
+        "npm-package-arg": "^6.1.0",
+        "npmlog": "^4.1.2",
+        "pify": "^4.0.1",
+        "read-package-json": "^2.0.13"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        }
+      }
+    },
+    "@lerna/npm-run-script": {
+      "version": "3.16.5",
+      "resolved": "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-3.16.5.tgz",
+      "integrity": "sha512-1asRi+LjmVn3pMjEdpqKJZFT/3ZNpb+VVeJMwrJaV/3DivdNg7XlPK9LTrORuKU4PSvhdEZvJmSlxCKyDpiXsQ==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "@lerna/get-npm-exec-opts": "3.13.0",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/otplease": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/otplease/-/otplease-3.18.5.tgz",
+      "integrity": "sha512-S+SldXAbcXTEDhzdxYLU0ZBKuYyURP/ND2/dK6IpKgLxQYh/z4ScljPDMyKymmEvgiEJmBsPZAAPfmNPEzxjog==",
+      "dev": true,
+      "requires": {
+        "@lerna/prompt": "3.18.5",
+        "figgy-pudding": "^3.5.1"
+      }
+    },
+    "@lerna/output": {
+      "version": "3.13.0",
+      "resolved": "https://registry.npmjs.org/@lerna/output/-/output-3.13.0.tgz",
+      "integrity": "sha512-7ZnQ9nvUDu/WD+bNsypmPG5MwZBwu86iRoiW6C1WBuXXDxM5cnIAC1m2WxHeFnjyMrYlRXM9PzOQ9VDD+C15Rg==",
+      "dev": true,
+      "requires": {
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/pack-directory": {
+      "version": "3.16.4",
+      "resolved": "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-3.16.4.tgz",
+      "integrity": "sha512-uxSF0HZeGyKaaVHz5FroDY9A5NDDiCibrbYR6+khmrhZtY0Bgn6hWq8Gswl9iIlymA+VzCbshWIMX4o2O8C8ng==",
+      "dev": true,
+      "requires": {
+        "@lerna/get-packed": "3.16.0",
+        "@lerna/package": "3.16.0",
+        "@lerna/run-lifecycle": "3.16.2",
+        "figgy-pudding": "^3.5.1",
+        "npm-packlist": "^1.4.4",
+        "npmlog": "^4.1.2",
+        "tar": "^4.4.10",
+        "temp-write": "^3.4.0"
+      }
+    },
+    "@lerna/package": {
+      "version": "3.16.0",
+      "resolved": "https://registry.npmjs.org/@lerna/package/-/package-3.16.0.tgz",
+      "integrity": "sha512-2lHBWpaxcBoiNVbtyLtPUuTYEaB/Z+eEqRS9duxpZs6D+mTTZMNy6/5vpEVSCBmzvdYpyqhqaYjjSLvjjr5Riw==",
+      "dev": true,
+      "requires": {
+        "load-json-file": "^5.3.0",
+        "npm-package-arg": "^6.1.0",
+        "write-pkg": "^3.1.0"
+      },
+      "dependencies": {
+        "load-json-file": {
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz",
+          "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.15",
+            "parse-json": "^4.0.0",
+            "pify": "^4.0.1",
+            "strip-bom": "^3.0.0",
+            "type-fest": "^0.3.0"
+          }
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
+          }
+        },
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        },
+        "type-fest": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz",
+          "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==",
+          "dev": true
+        }
+      }
+    },
+    "@lerna/package-graph": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-3.18.5.tgz",
+      "integrity": "sha512-8QDrR9T+dBegjeLr+n9WZTVxUYUhIUjUgZ0gvNxUBN8S1WB9r6H5Yk56/MVaB64tA3oGAN9IIxX6w0WvTfFudA==",
+      "dev": true,
+      "requires": {
+        "@lerna/prerelease-id-from-version": "3.16.0",
+        "@lerna/validation-error": "3.13.0",
+        "npm-package-arg": "^6.1.0",
+        "npmlog": "^4.1.2",
+        "semver": "^6.2.0"
+      }
+    },
+    "@lerna/prerelease-id-from-version": {
+      "version": "3.16.0",
+      "resolved": "https://registry.npmjs.org/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-3.16.0.tgz",
+      "integrity": "sha512-qZyeUyrE59uOK8rKdGn7jQz+9uOpAaF/3hbslJVFL1NqF9ELDTqjCPXivuejMX/lN4OgD6BugTO4cR7UTq/sZA==",
+      "dev": true,
+      "requires": {
+        "semver": "^6.2.0"
+      }
+    },
+    "@lerna/profiler": {
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/@lerna/profiler/-/profiler-3.20.0.tgz",
+      "integrity": "sha512-bh8hKxAlm6yu8WEOvbLENm42i2v9SsR4WbrCWSbsmOElx3foRnMlYk7NkGECa+U5c3K4C6GeBbwgqs54PP7Ljg==",
+      "dev": true,
+      "requires": {
+        "figgy-pudding": "^3.5.1",
+        "fs-extra": "^8.1.0",
+        "npmlog": "^4.1.2",
+        "upath": "^1.2.0"
+      }
+    },
+    "@lerna/project": {
+      "version": "3.18.0",
+      "resolved": "https://registry.npmjs.org/@lerna/project/-/project-3.18.0.tgz",
+      "integrity": "sha512-+LDwvdAp0BurOAWmeHE3uuticsq9hNxBI0+FMHiIai8jrygpJGahaQrBYWpwbshbQyVLeQgx3+YJdW2TbEdFWA==",
+      "dev": true,
+      "requires": {
+        "@lerna/package": "3.16.0",
+        "@lerna/validation-error": "3.13.0",
+        "cosmiconfig": "^5.1.0",
+        "dedent": "^0.7.0",
+        "dot-prop": "^4.2.0",
+        "glob-parent": "^5.0.0",
+        "globby": "^9.2.0",
+        "load-json-file": "^5.3.0",
+        "npmlog": "^4.1.2",
+        "p-map": "^2.1.0",
+        "resolve-from": "^4.0.0",
+        "write-json-file": "^3.2.0"
+      },
+      "dependencies": {
+        "load-json-file": {
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz",
+          "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.15",
+            "parse-json": "^4.0.0",
+            "pify": "^4.0.1",
+            "strip-bom": "^3.0.0",
+            "type-fest": "^0.3.0"
+          }
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
+          }
+        },
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        },
+        "type-fest": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz",
+          "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==",
+          "dev": true
+        }
+      }
+    },
+    "@lerna/prompt": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/prompt/-/prompt-3.18.5.tgz",
+      "integrity": "sha512-rkKj4nm1twSbBEb69+Em/2jAERK8htUuV8/xSjN0NPC+6UjzAwY52/x9n5cfmpa9lyKf/uItp7chCI7eDmNTKQ==",
+      "dev": true,
+      "requires": {
+        "inquirer": "^6.2.0",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/publish": {
+      "version": "3.20.2",
+      "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.20.2.tgz",
+      "integrity": "sha512-N7Y6PdhJ+tYQPdI1tZum8W25cDlTp4D6brvRacKZusweWexxaopbV8RprBaKexkEX/KIbncuADq7qjDBdQHzaA==",
+      "dev": true,
+      "requires": {
+        "@evocateur/libnpmaccess": "^3.1.2",
+        "@evocateur/npm-registry-fetch": "^4.0.0",
+        "@evocateur/pacote": "^9.6.3",
+        "@lerna/check-working-tree": "3.16.5",
+        "@lerna/child-process": "3.16.5",
+        "@lerna/collect-updates": "3.20.0",
+        "@lerna/command": "3.18.5",
+        "@lerna/describe-ref": "3.16.5",
+        "@lerna/log-packed": "3.16.0",
+        "@lerna/npm-conf": "3.16.0",
+        "@lerna/npm-dist-tag": "3.18.5",
+        "@lerna/npm-publish": "3.18.5",
+        "@lerna/otplease": "3.18.5",
+        "@lerna/output": "3.13.0",
+        "@lerna/pack-directory": "3.16.4",
+        "@lerna/prerelease-id-from-version": "3.16.0",
+        "@lerna/prompt": "3.18.5",
+        "@lerna/pulse-till-done": "3.13.0",
+        "@lerna/run-lifecycle": "3.16.2",
+        "@lerna/run-topologically": "3.18.5",
+        "@lerna/validation-error": "3.13.0",
+        "@lerna/version": "3.20.2",
+        "figgy-pudding": "^3.5.1",
+        "fs-extra": "^8.1.0",
+        "npm-package-arg": "^6.1.0",
+        "npmlog": "^4.1.2",
+        "p-finally": "^1.0.0",
+        "p-map": "^2.1.0",
+        "p-pipe": "^1.2.0",
+        "semver": "^6.2.0"
+      }
+    },
+    "@lerna/pulse-till-done": {
+      "version": "3.13.0",
+      "resolved": "https://registry.npmjs.org/@lerna/pulse-till-done/-/pulse-till-done-3.13.0.tgz",
+      "integrity": "sha512-1SOHpy7ZNTPulzIbargrgaJX387csN7cF1cLOGZiJQA6VqnS5eWs2CIrG8i8wmaUavj2QlQ5oEbRMVVXSsGrzA==",
+      "dev": true,
+      "requires": {
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/query-graph": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/query-graph/-/query-graph-3.18.5.tgz",
+      "integrity": "sha512-50Lf4uuMpMWvJ306be3oQDHrWV42nai9gbIVByPBYJuVW8dT8O8pA3EzitNYBUdLL9/qEVbrR0ry1HD7EXwtRA==",
+      "dev": true,
+      "requires": {
+        "@lerna/package-graph": "3.18.5",
+        "figgy-pudding": "^3.5.1"
+      }
+    },
+    "@lerna/resolve-symlink": {
+      "version": "3.16.0",
+      "resolved": "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-3.16.0.tgz",
+      "integrity": "sha512-Ibj5e7njVHNJ/NOqT4HlEgPFPtPLWsO7iu59AM5bJDcAJcR96mLZ7KGVIsS2tvaO7akMEJvt2P+ErwCdloG3jQ==",
+      "dev": true,
+      "requires": {
+        "fs-extra": "^8.1.0",
+        "npmlog": "^4.1.2",
+        "read-cmd-shim": "^1.0.1"
+      }
+    },
+    "@lerna/rimraf-dir": {
+      "version": "3.16.5",
+      "resolved": "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-3.16.5.tgz",
+      "integrity": "sha512-bQlKmO0pXUsXoF8lOLknhyQjOZsCc0bosQDoX4lujBXSWxHVTg1VxURtWf2lUjz/ACsJVDfvHZbDm8kyBk5okA==",
+      "dev": true,
+      "requires": {
+        "@lerna/child-process": "3.16.5",
+        "npmlog": "^4.1.2",
+        "path-exists": "^3.0.0",
+        "rimraf": "^2.6.2"
+      }
+    },
+    "@lerna/run": {
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/@lerna/run/-/run-3.20.0.tgz",
+      "integrity": "sha512-9U3AqeaCeB7KsGS9oyKNp62s9vYoULg/B4cqXTKZkc+OKL6QOEjYHYVSBcMK9lUXrMjCjDIuDSX3PnTCPxQ2Dw==",
+      "dev": true,
+      "requires": {
+        "@lerna/command": "3.18.5",
+        "@lerna/filter-options": "3.20.0",
+        "@lerna/npm-run-script": "3.16.5",
+        "@lerna/output": "3.13.0",
+        "@lerna/profiler": "3.20.0",
+        "@lerna/run-topologically": "3.18.5",
+        "@lerna/timer": "3.13.0",
+        "@lerna/validation-error": "3.13.0",
+        "p-map": "^2.1.0"
+      }
+    },
+    "@lerna/run-lifecycle": {
+      "version": "3.16.2",
+      "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.16.2.tgz",
+      "integrity": "sha512-RqFoznE8rDpyyF0rOJy3+KjZCeTkO8y/OB9orPauR7G2xQ7PTdCpgo7EO6ZNdz3Al+k1BydClZz/j78gNCmL2A==",
+      "dev": true,
+      "requires": {
+        "@lerna/npm-conf": "3.16.0",
+        "figgy-pudding": "^3.5.1",
+        "npm-lifecycle": "^3.1.2",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/run-topologically": {
+      "version": "3.18.5",
+      "resolved": "https://registry.npmjs.org/@lerna/run-topologically/-/run-topologically-3.18.5.tgz",
+      "integrity": "sha512-6N1I+6wf4hLOnPW+XDZqwufyIQ6gqoPfHZFkfWlvTQ+Ue7CuF8qIVQ1Eddw5HKQMkxqN10thKOFfq/9NQZ4NUg==",
+      "dev": true,
+      "requires": {
+        "@lerna/query-graph": "3.18.5",
+        "figgy-pudding": "^3.5.1",
+        "p-queue": "^4.0.0"
+      }
+    },
+    "@lerna/symlink-binary": {
+      "version": "3.17.0",
+      "resolved": "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-3.17.0.tgz",
+      "integrity": "sha512-RLpy9UY6+3nT5J+5jkM5MZyMmjNHxZIZvXLV+Q3MXrf7Eaa1hNqyynyj4RO95fxbS+EZc4XVSk25DGFQbcRNSQ==",
+      "dev": true,
+      "requires": {
+        "@lerna/create-symlink": "3.16.2",
+        "@lerna/package": "3.16.0",
+        "fs-extra": "^8.1.0",
+        "p-map": "^2.1.0"
+      }
+    },
+    "@lerna/symlink-dependencies": {
+      "version": "3.17.0",
+      "resolved": "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.17.0.tgz",
+      "integrity": "sha512-KmjU5YT1bpt6coOmdFueTJ7DFJL4H1w5eF8yAQ2zsGNTtZ+i5SGFBWpb9AQaw168dydc3s4eu0W0Sirda+F59Q==",
+      "dev": true,
+      "requires": {
+        "@lerna/create-symlink": "3.16.2",
+        "@lerna/resolve-symlink": "3.16.0",
+        "@lerna/symlink-binary": "3.17.0",
+        "fs-extra": "^8.1.0",
+        "p-finally": "^1.0.0",
+        "p-map": "^2.1.0",
+        "p-map-series": "^1.0.0"
+      }
+    },
+    "@lerna/timer": {
+      "version": "3.13.0",
+      "resolved": "https://registry.npmjs.org/@lerna/timer/-/timer-3.13.0.tgz",
+      "integrity": "sha512-RHWrDl8U4XNPqY5MQHkToWS9jHPnkLZEt5VD+uunCKTfzlxGnRCr3/zVr8VGy/uENMYpVP3wJa4RKGY6M0vkRw==",
+      "dev": true
+    },
+    "@lerna/validation-error": {
+      "version": "3.13.0",
+      "resolved": "https://registry.npmjs.org/@lerna/validation-error/-/validation-error-3.13.0.tgz",
+      "integrity": "sha512-SiJP75nwB8GhgwLKQfdkSnDufAaCbkZWJqEDlKOUPUvVOplRGnfL+BPQZH5nvq2BYSRXsksXWZ4UHVnQZI/HYA==",
+      "dev": true,
+      "requires": {
+        "npmlog": "^4.1.2"
+      }
+    },
+    "@lerna/version": {
+      "version": "3.20.2",
+      "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.20.2.tgz",
+      "integrity": "sha512-ckBJMaBWc+xJen0cMyCE7W67QXLLrc0ELvigPIn8p609qkfNM0L0CF803MKxjVOldJAjw84b8ucNWZLvJagP/Q==",
+      "dev": true,
+      "requires": {
+        "@lerna/check-working-tree": "3.16.5",
+        "@lerna/child-process": "3.16.5",
+        "@lerna/collect-updates": "3.20.0",
+        "@lerna/command": "3.18.5",
+        "@lerna/conventional-commits": "3.18.5",
+        "@lerna/github-client": "3.16.5",
+        "@lerna/gitlab-client": "3.15.0",
+        "@lerna/output": "3.13.0",
+        "@lerna/prerelease-id-from-version": "3.16.0",
+        "@lerna/prompt": "3.18.5",
+        "@lerna/run-lifecycle": "3.16.2",
+        "@lerna/run-topologically": "3.18.5",
+        "@lerna/validation-error": "3.13.0",
+        "chalk": "^2.3.1",
+        "dedent": "^0.7.0",
+        "load-json-file": "^5.3.0",
+        "minimatch": "^3.0.4",
+        "npmlog": "^4.1.2",
+        "p-map": "^2.1.0",
+        "p-pipe": "^1.2.0",
+        "p-reduce": "^1.0.0",
+        "p-waterfall": "^1.0.0",
+        "semver": "^6.2.0",
+        "slash": "^2.0.0",
+        "temp-write": "^3.4.0",
+        "write-json-file": "^3.2.0"
+      },
+      "dependencies": {
+        "load-json-file": {
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz",
+          "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.15",
+            "parse-json": "^4.0.0",
+            "pify": "^4.0.1",
+            "strip-bom": "^3.0.0",
+            "type-fest": "^0.3.0"
+          }
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
+          }
+        },
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        },
+        "slash": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+          "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+          "dev": true
+        },
+        "type-fest": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz",
+          "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==",
+          "dev": true
+        }
+      }
+    },
+    "@lerna/write-log-file": {
+      "version": "3.13.0",
+      "resolved": "https://registry.npmjs.org/@lerna/write-log-file/-/write-log-file-3.13.0.tgz",
+      "integrity": "sha512-RibeMnDPvlL8bFYW5C8cs4mbI3AHfQef73tnJCQ/SgrXZHehmHnsyWUiE7qDQCAo+B1RfTapvSyFF69iPj326A==",
+      "dev": true,
+      "requires": {
+        "npmlog": "^4.1.2",
+        "write-file-atomic": "^2.3.0"
+      }
+    },
+    "@mrmlnc/readdir-enhanced": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
+      "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==",
+      "dev": true,
+      "requires": {
+        "call-me-maybe": "^1.0.1",
+        "glob-to-regexp": "^0.3.0"
+      }
+    },
+    "@nodelib/fs.stat": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
+      "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==",
+      "dev": true
+    },
+    "@octokit/auth-token": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.0.tgz",
+      "integrity": "sha512-eoOVMjILna7FVQf96iWc3+ZtE/ZT6y8ob8ZzcqKY1ibSQCnu4O/B7pJvzMx5cyZ/RjAff6DAdEb0O0Cjcxidkg==",
+      "dev": true,
+      "requires": {
+        "@octokit/types": "^2.0.0"
+      }
+    },
+    "@octokit/endpoint": {
+      "version": "5.5.2",
+      "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-5.5.2.tgz",
+      "integrity": "sha512-ICDcRA0C2vtTZZGud1nXRrBLXZqFayodXAKZfo3dkdcLNqcHsgaz3YSTupbURusYeucSVRjjG+RTcQhx6HPPcg==",
+      "dev": true,
+      "requires": {
+        "@octokit/types": "^2.0.0",
+        "is-plain-object": "^3.0.0",
+        "universal-user-agent": "^4.0.0"
+      },
+      "dependencies": {
+        "is-plain-object": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz",
+          "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==",
+          "dev": true,
+          "requires": {
+            "isobject": "^4.0.0"
+          }
+        },
+        "isobject": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz",
+          "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==",
+          "dev": true
+        }
+      }
+    },
+    "@octokit/plugin-enterprise-rest": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-3.6.2.tgz",
+      "integrity": "sha512-3wF5eueS5OHQYuAEudkpN+xVeUsg8vYEMMenEzLphUZ7PRZ8OJtDcsreL3ad9zxXmBbaFWzLmFcdob5CLyZftA==",
+      "dev": true
+    },
+    "@octokit/plugin-paginate-rest": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz",
+      "integrity": "sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==",
+      "dev": true,
+      "requires": {
+        "@octokit/types": "^2.0.1"
+      }
+    },
+    "@octokit/plugin-request-log": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz",
+      "integrity": "sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw==",
+      "dev": true
+    },
+    "@octokit/plugin-rest-endpoint-methods": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.2.0.tgz",
+      "integrity": "sha512-/Es7P4roLJls+4JaUUouZ25dqvXdrLJ80vvRoZzzR0EBCTlaU2ESRjXgH50mUpmR1MKPwJS3Ew5iFj3U3Q8UfQ==",
+      "dev": true,
+      "requires": {
+        "@octokit/types": "^2.0.1",
+        "deprecation": "^2.3.1"
+      }
+    },
+    "@octokit/request": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.3.1.tgz",
+      "integrity": "sha512-5/X0AL1ZgoU32fAepTfEoggFinO3rxsMLtzhlUX+RctLrusn/CApJuGFCd0v7GMFhF+8UiCsTTfsu7Fh1HnEJg==",
+      "dev": true,
+      "requires": {
+        "@octokit/endpoint": "^5.5.0",
+        "@octokit/request-error": "^1.0.1",
+        "@octokit/types": "^2.0.0",
+        "deprecation": "^2.0.0",
+        "is-plain-object": "^3.0.0",
+        "node-fetch": "^2.3.0",
+        "once": "^1.4.0",
+        "universal-user-agent": "^4.0.0"
+      },
+      "dependencies": {
+        "is-plain-object": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz",
+          "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==",
+          "dev": true,
+          "requires": {
+            "isobject": "^4.0.0"
+          }
+        },
+        "isobject": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz",
+          "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==",
+          "dev": true
+        }
+      }
+    },
+    "@octokit/request-error": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz",
+      "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==",
+      "dev": true,
+      "requires": {
+        "@octokit/types": "^2.0.0",
+        "deprecation": "^2.0.0",
+        "once": "^1.4.0"
+      }
+    },
+    "@octokit/rest": {
+      "version": "16.41.1",
+      "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.41.1.tgz",
+      "integrity": "sha512-C97cb2cwxakAxZ+5oIAhmd9a1lqJ48n9TN3ooRuuifRMVQZGJCIFsb7uQV76YjDsB1agjhhDokAOSKq5F2+HYw==",
+      "dev": true,
+      "requires": {
+        "@octokit/auth-token": "^2.4.0",
+        "@octokit/plugin-paginate-rest": "^1.1.1",
+        "@octokit/plugin-request-log": "^1.0.0",
+        "@octokit/plugin-rest-endpoint-methods": "2.2.0",
+        "@octokit/request": "^5.2.0",
+        "@octokit/request-error": "^1.0.2",
+        "atob-lite": "^2.0.0",
+        "before-after-hook": "^2.0.0",
+        "btoa-lite": "^1.0.0",
+        "deprecation": "^2.0.0",
+        "lodash.get": "^4.4.2",
+        "lodash.set": "^4.3.2",
+        "lodash.uniq": "^4.5.0",
+        "octokit-pagination-methods": "^1.1.0",
+        "once": "^1.4.0",
+        "universal-user-agent": "^4.0.0"
+      }
+    },
+    "@octokit/types": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.1.1.tgz",
+      "integrity": "sha512-89LOYH+d/vsbDX785NOfLxTW88GjNd0lWRz1DVPVsZgg9Yett5O+3MOvwo7iHgvUwbFz0mf/yPIjBkUbs4kxoQ==",
+      "dev": true,
+      "requires": {
+        "@types/node": ">= 8"
+      }
+    },
+    "@types/events": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
+      "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
+      "dev": true
+    },
+    "@types/glob": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
+      "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
+      "dev": true,
+      "requires": {
+        "@types/events": "*",
+        "@types/minimatch": "*",
+        "@types/node": "*"
+      }
+    },
+    "@types/minimatch": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
+      "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
+      "dev": true
+    },
+    "@types/node": {
+      "version": "13.7.0",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.0.tgz",
+      "integrity": "sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==",
+      "dev": true
+    },
+    "@types/normalize-package-data": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
+      "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==",
+      "dev": true
+    },
+    "@zkochan/cmd-shim": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@zkochan/cmd-shim/-/cmd-shim-3.1.0.tgz",
+      "integrity": "sha512-o8l0+x7C7sMZU3v9GuJIAU10qQLtwR1dtRQIOmlNMtyaqhmpXOzx1HWiYoWfmmf9HHZoAkXpc9TM9PQYF9d4Jg==",
+      "dev": true,
+      "requires": {
+        "is-windows": "^1.0.0",
+        "mkdirp-promise": "^5.0.1",
+        "mz": "^2.5.0"
+      }
+    },
+    "JSONStream": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz",
+      "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==",
+      "dev": true,
+      "requires": {
+        "jsonparse": "^1.2.0",
+        "through": ">=2.2.7 <3"
+      }
+    },
+    "abbrev": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+      "dev": true
+    },
+    "acorn": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz",
+      "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==",
+      "dev": true
+    },
+    "acorn-jsx": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz",
+      "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==",
+      "dev": true
+    },
+    "agent-base": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
+      "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
+      "dev": true,
+      "requires": {
+        "es6-promisify": "^5.0.0"
+      }
+    },
+    "agentkeepalive": {
+      "version": "3.5.2",
+      "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz",
+      "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==",
+      "dev": true,
+      "requires": {
+        "humanize-ms": "^1.2.1"
+      }
+    },
+    "ajv": {
+      "version": "6.11.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz",
+      "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==",
+      "dev": true,
+      "requires": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "ansi-escapes": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+      "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+      "dev": true
+    },
+    "ansi-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+      "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+      "dev": true
+    },
+    "ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^1.9.0"
+      }
+    },
+    "any-promise": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+      "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=",
+      "dev": true
+    },
+    "aproba": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+      "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+      "dev": true
+    },
+    "are-we-there-yet": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
+      "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+      "dev": true,
+      "requires": {
+        "delegates": "^1.0.0",
+        "readable-stream": "^2.0.6"
+      }
+    },
+    "argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "dev": true,
+      "requires": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "arr-diff": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+      "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+      "dev": true
+    },
+    "arr-flatten": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+      "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+      "dev": true
+    },
+    "arr-union": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+      "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+      "dev": true
+    },
+    "array-differ": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-2.1.0.tgz",
+      "integrity": "sha512-KbUpJgx909ZscOc/7CLATBFam7P1Z1QRQInvgT0UztM9Q72aGKCunKASAl7WNW0tnPmPyEMeMhdsfWhfmW037w==",
+      "dev": true
+    },
+    "array-find-index": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+      "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+      "dev": true
+    },
+    "array-ify": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
+      "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=",
+      "dev": true
+    },
+    "array-includes": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz",
+      "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0",
+        "is-string": "^1.0.5"
+      }
+    },
+    "array-union": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+      "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+      "dev": true,
+      "requires": {
+        "array-uniq": "^1.0.1"
+      }
+    },
+    "array-uniq": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+      "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+      "dev": true
+    },
+    "array-unique": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+      "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+      "dev": true
+    },
+    "array.prototype.flat": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz",
+      "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1"
+      }
+    },
+    "arrify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+      "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+      "dev": true
+    },
+    "asap": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+      "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
+      "dev": true
+    },
+    "asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "dev": true,
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
+    "assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+      "dev": true
+    },
+    "assign-symbols": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+      "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+      "dev": true
+    },
+    "astral-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+      "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+      "dev": true
+    },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+      "dev": true
+    },
+    "atob": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+      "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+      "dev": true
+    },
+    "atob-lite": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz",
+      "integrity": "sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY=",
+      "dev": true
+    },
+    "aws-sign2": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+      "dev": true
+    },
+    "aws4": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
+      "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
+      "dev": true
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "base": {
+      "version": "0.11.2",
+      "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+      "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+      "dev": true,
+      "requires": {
+        "cache-base": "^1.0.1",
+        "class-utils": "^0.3.5",
+        "component-emitter": "^1.2.1",
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.1",
+        "mixin-deep": "^1.2.0",
+        "pascalcase": "^0.1.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "bcrypt-pbkdf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "dev": true,
+      "requires": {
+        "tweetnacl": "^0.14.3"
+      }
+    },
+    "before-after-hook": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz",
+      "integrity": "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==",
+      "dev": true
+    },
+    "bluebird": {
+      "version": "3.7.2",
+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
+      "dev": true
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "braces": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+      "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+      "dev": true,
+      "requires": {
+        "arr-flatten": "^1.1.0",
+        "array-unique": "^0.3.2",
+        "extend-shallow": "^2.0.1",
+        "fill-range": "^4.0.0",
+        "isobject": "^3.0.1",
+        "repeat-element": "^1.1.2",
+        "snapdragon": "^0.8.1",
+        "snapdragon-node": "^2.0.1",
+        "split-string": "^3.0.2",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "btoa-lite": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz",
+      "integrity": "sha1-M3dm2hWAEhD92VbCLpxokaudAzc=",
+      "dev": true
+    },
+    "buffer-from": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+      "dev": true
+    },
+    "builtins": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
+      "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=",
+      "dev": true
+    },
+    "byline": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz",
+      "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=",
+      "dev": true
+    },
+    "byte-size": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-5.0.1.tgz",
+      "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==",
+      "dev": true
+    },
+    "cacache": {
+      "version": "12.0.4",
+      "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
+      "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
+      "dev": true,
+      "requires": {
+        "bluebird": "^3.5.5",
+        "chownr": "^1.1.1",
+        "figgy-pudding": "^3.5.1",
+        "glob": "^7.1.4",
+        "graceful-fs": "^4.1.15",
+        "infer-owner": "^1.0.3",
+        "lru-cache": "^5.1.1",
+        "mississippi": "^3.0.0",
+        "mkdirp": "^0.5.1",
+        "move-concurrently": "^1.0.1",
+        "promise-inflight": "^1.0.1",
+        "rimraf": "^2.6.3",
+        "ssri": "^6.0.1",
+        "unique-filename": "^1.1.1",
+        "y18n": "^4.0.0"
+      }
+    },
+    "cache-base": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+      "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+      "dev": true,
+      "requires": {
+        "collection-visit": "^1.0.0",
+        "component-emitter": "^1.2.1",
+        "get-value": "^2.0.6",
+        "has-value": "^1.0.0",
+        "isobject": "^3.0.1",
+        "set-value": "^2.0.0",
+        "to-object-path": "^0.3.0",
+        "union-value": "^1.0.0",
+        "unset-value": "^1.0.0"
+      }
+    },
+    "call-me-maybe": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz",
+      "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=",
+      "dev": true
+    },
+    "caller-callsite": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
+      "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=",
+      "dev": true,
+      "requires": {
+        "callsites": "^2.0.0"
+      },
+      "dependencies": {
+        "callsites": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
+          "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=",
+          "dev": true
+        }
+      }
+    },
+    "caller-path": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz",
+      "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=",
+      "dev": true,
+      "requires": {
+        "caller-callsite": "^2.0.0"
+      }
+    },
+    "callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true
+    },
+    "camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "dev": true
+    },
+    "camelcase-keys": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz",
+      "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=",
+      "dev": true,
+      "requires": {
+        "camelcase": "^4.1.0",
+        "map-obj": "^2.0.0",
+        "quick-lru": "^1.0.0"
+      },
+      "dependencies": {
+        "camelcase": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+          "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+          "dev": true
+        }
+      }
+    },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+      "dev": true
+    },
+    "chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      }
+    },
+    "chardet": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+      "dev": true
+    },
+    "chownr": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
+      "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==",
+      "dev": true
+    },
+    "ci-info": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
+      "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
+      "dev": true
+    },
+    "class-utils": {
+      "version": "0.3.6",
+      "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+      "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+      "dev": true,
+      "requires": {
+        "arr-union": "^3.1.0",
+        "define-property": "^0.2.5",
+        "isobject": "^3.0.0",
+        "static-extend": "^0.1.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        }
+      }
+    },
+    "cli-cursor": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+      "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+      "dev": true,
+      "requires": {
+        "restore-cursor": "^2.0.0"
+      }
+    },
+    "cli-width": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+      "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+      "dev": true
+    },
+    "cliui": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+      "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+      "dev": true,
+      "requires": {
+        "string-width": "^3.1.0",
+        "strip-ansi": "^5.2.0",
+        "wrap-ansi": "^5.1.0"
+      },
+      "dependencies": {
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        }
+      }
+    },
+    "clone": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+      "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=",
+      "dev": true
+    },
+    "clone-deep": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+      "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
+      "dev": true,
+      "requires": {
+        "is-plain-object": "^2.0.4",
+        "kind-of": "^6.0.2",
+        "shallow-clone": "^3.0.0"
+      }
+    },
+    "code-point-at": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+      "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+      "dev": true
+    },
+    "collection-visit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+      "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+      "dev": true,
+      "requires": {
+        "map-visit": "^1.0.0",
+        "object-visit": "^1.0.0"
+      }
+    },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "dev": true,
+      "requires": {
+        "color-name": "1.1.3"
+      }
+    },
+    "color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+      "dev": true
+    },
+    "columnify": {
+      "version": "1.5.4",
+      "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz",
+      "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=",
+      "dev": true,
+      "requires": {
+        "strip-ansi": "^3.0.0",
+        "wcwidth": "^1.0.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+          "dev": true
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^2.0.0"
+          }
+        }
+      }
+    },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "dev": true,
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
+    "commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "dev": true,
+      "optional": true
+    },
+    "compare-func": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz",
+      "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=",
+      "dev": true,
+      "requires": {
+        "array-ify": "^1.0.0",
+        "dot-prop": "^3.0.0"
+      },
+      "dependencies": {
+        "dot-prop": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz",
+          "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=",
+          "dev": true,
+          "requires": {
+            "is-obj": "^1.0.0"
+          }
+        }
+      }
+    },
+    "component-emitter": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
+      "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
+      "dev": true
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "concat-stream": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.2.2",
+        "typedarray": "^0.0.6"
+      }
+    },
+    "config-chain": {
+      "version": "1.1.12",
+      "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
+      "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==",
+      "dev": true,
+      "requires": {
+        "ini": "^1.3.4",
+        "proto-list": "~1.2.1"
+      }
+    },
+    "confusing-browser-globals": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz",
+      "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==",
+      "dev": true
+    },
+    "console-control-strings": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+      "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+      "dev": true
+    },
+    "contains-path": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
+      "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
+      "dev": true
+    },
+    "conventional-changelog-angular": {
+      "version": "5.0.6",
+      "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.6.tgz",
+      "integrity": "sha512-QDEmLa+7qdhVIv8sFZfVxU1VSyVvnXPsxq8Vam49mKUcO1Z8VTLEJk9uI21uiJUsnmm0I4Hrsdc9TgkOQo9WSA==",
+      "dev": true,
+      "requires": {
+        "compare-func": "^1.3.1",
+        "q": "^1.5.1"
+      }
+    },
+    "conventional-changelog-core": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-3.2.3.tgz",
+      "integrity": "sha512-LMMX1JlxPIq/Ez5aYAYS5CpuwbOk6QFp8O4HLAcZxe3vxoCtABkhfjetk8IYdRB9CDQGwJFLR3Dr55Za6XKgUQ==",
+      "dev": true,
+      "requires": {
+        "conventional-changelog-writer": "^4.0.6",
+        "conventional-commits-parser": "^3.0.3",
+        "dateformat": "^3.0.0",
+        "get-pkg-repo": "^1.0.0",
+        "git-raw-commits": "2.0.0",
+        "git-remote-origin-url": "^2.0.0",
+        "git-semver-tags": "^2.0.3",
+        "lodash": "^4.2.1",
+        "normalize-package-data": "^2.3.5",
+        "q": "^1.5.1",
+        "read-pkg": "^3.0.0",
+        "read-pkg-up": "^3.0.0",
+        "through2": "^3.0.0"
+      },
+      "dependencies": {
+        "load-json-file": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+          "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "parse-json": "^4.0.0",
+            "pify": "^3.0.0",
+            "strip-bom": "^3.0.0"
+          }
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
+          }
+        },
+        "path-type": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+          "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+          "dev": true,
+          "requires": {
+            "pify": "^3.0.0"
+          }
+        },
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        },
+        "read-pkg": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+          "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+          "dev": true,
+          "requires": {
+            "load-json-file": "^4.0.0",
+            "normalize-package-data": "^2.3.2",
+            "path-type": "^3.0.0"
+          }
+        },
+        "read-pkg-up": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz",
+          "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=",
+          "dev": true,
+          "requires": {
+            "find-up": "^2.0.0",
+            "read-pkg": "^3.0.0"
+          }
+        },
+        "through2": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz",
+          "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==",
+          "dev": true,
+          "requires": {
+            "readable-stream": "2 || 3"
+          }
+        }
+      }
+    },
+    "conventional-changelog-preset-loader": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.0.tgz",
+      "integrity": "sha512-/rHb32J2EJnEXeK4NpDgMaAVTFZS3o1ExmjKMtYVgIC4MQn0vkNSbYpdGRotkfGGRWiqk3Ri3FBkiZGbAfIfOQ==",
+      "dev": true
+    },
+    "conventional-changelog-writer": {
+      "version": "4.0.11",
+      "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.11.tgz",
+      "integrity": "sha512-g81GQOR392I+57Cw3IyP1f+f42ME6aEkbR+L7v1FBBWolB0xkjKTeCWVguzRrp6UiT1O6gBpJbEy2eq7AnV1rw==",
+      "dev": true,
+      "requires": {
+        "compare-func": "^1.3.1",
+        "conventional-commits-filter": "^2.0.2",
+        "dateformat": "^3.0.0",
+        "handlebars": "^4.4.0",
+        "json-stringify-safe": "^5.0.1",
+        "lodash": "^4.17.15",
+        "meow": "^5.0.0",
+        "semver": "^6.0.0",
+        "split": "^1.0.0",
+        "through2": "^3.0.0"
+      },
+      "dependencies": {
+        "through2": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz",
+          "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==",
+          "dev": true,
+          "requires": {
+            "readable-stream": "2 || 3"
+          }
+        }
+      }
+    },
+    "conventional-commits-filter": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.2.tgz",
+      "integrity": "sha512-WpGKsMeXfs21m1zIw4s9H5sys2+9JccTzpN6toXtxhpw2VNF2JUXwIakthKBy+LN4DvJm+TzWhxOMWOs1OFCFQ==",
+      "dev": true,
+      "requires": {
+        "lodash.ismatch": "^4.4.0",
+        "modify-values": "^1.0.0"
+      }
+    },
+    "conventional-commits-parser": {
+      "version": "3.0.8",
+      "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.0.8.tgz",
+      "integrity": "sha512-YcBSGkZbYp7d+Cr3NWUeXbPDFUN6g3SaSIzOybi8bjHL5IJ5225OSCxJJ4LgziyEJ7AaJtE9L2/EU6H7Nt/DDQ==",
+      "dev": true,
+      "requires": {
+        "JSONStream": "^1.0.4",
+        "is-text-path": "^1.0.1",
+        "lodash": "^4.17.15",
+        "meow": "^5.0.0",
+        "split2": "^2.0.0",
+        "through2": "^3.0.0",
+        "trim-off-newlines": "^1.0.0"
+      },
+      "dependencies": {
+        "through2": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz",
+          "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==",
+          "dev": true,
+          "requires": {
+            "readable-stream": "2 || 3"
+          }
+        }
+      }
+    },
+    "conventional-recommended-bump": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-5.0.1.tgz",
+      "integrity": "sha512-RVdt0elRcCxL90IrNP0fYCpq1uGt2MALko0eyeQ+zQuDVWtMGAy9ng6yYn3kax42lCj9+XBxQ8ZN6S9bdKxDhQ==",
+      "dev": true,
+      "requires": {
+        "concat-stream": "^2.0.0",
+        "conventional-changelog-preset-loader": "^2.1.1",
+        "conventional-commits-filter": "^2.0.2",
+        "conventional-commits-parser": "^3.0.3",
+        "git-raw-commits": "2.0.0",
+        "git-semver-tags": "^2.0.3",
+        "meow": "^4.0.0",
+        "q": "^1.5.1"
+      },
+      "dependencies": {
+        "concat-stream": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
+          "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
+          "dev": true,
+          "requires": {
+            "buffer-from": "^1.0.0",
+            "inherits": "^2.0.3",
+            "readable-stream": "^3.0.2",
+            "typedarray": "^0.0.6"
+          }
+        },
+        "load-json-file": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+          "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "parse-json": "^4.0.0",
+            "pify": "^3.0.0",
+            "strip-bom": "^3.0.0"
+          }
+        },
+        "meow": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz",
+          "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==",
+          "dev": true,
+          "requires": {
+            "camelcase-keys": "^4.0.0",
+            "decamelize-keys": "^1.0.0",
+            "loud-rejection": "^1.0.0",
+            "minimist": "^1.1.3",
+            "minimist-options": "^3.0.1",
+            "normalize-package-data": "^2.3.4",
+            "read-pkg-up": "^3.0.0",
+            "redent": "^2.0.0",
+            "trim-newlines": "^2.0.0"
+          }
+        },
+        "minimist": {
+          "version": "1.2.5",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+          "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+          "dev": true
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
+          }
+        },
+        "path-type": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+          "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+          "dev": true,
+          "requires": {
+            "pify": "^3.0.0"
+          }
+        },
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        },
+        "read-pkg": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+          "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+          "dev": true,
+          "requires": {
+            "load-json-file": "^4.0.0",
+            "normalize-package-data": "^2.3.2",
+            "path-type": "^3.0.0"
+          }
+        },
+        "read-pkg-up": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz",
+          "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=",
+          "dev": true,
+          "requires": {
+            "find-up": "^2.0.0",
+            "read-pkg": "^3.0.0"
+          }
+        },
+        "readable-stream": {
+          "version": "3.5.0",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.5.0.tgz",
+          "integrity": "sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
+      }
+    },
+    "copy-concurrently": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
+      "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.1.1",
+        "fs-write-stream-atomic": "^1.0.8",
+        "iferr": "^0.1.5",
+        "mkdirp": "^0.5.1",
+        "rimraf": "^2.5.4",
+        "run-queue": "^1.0.0"
+      }
+    },
+    "copy-descriptor": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+      "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+      "dev": true
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+      "dev": true
+    },
+    "cosmiconfig": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
+      "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==",
+      "dev": true,
+      "requires": {
+        "import-fresh": "^2.0.0",
+        "is-directory": "^0.3.1",
+        "js-yaml": "^3.13.1",
+        "parse-json": "^4.0.0"
+      },
+      "dependencies": {
+        "import-fresh": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz",
+          "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=",
+          "dev": true,
+          "requires": {
+            "caller-path": "^2.0.0",
+            "resolve-from": "^3.0.0"
+          }
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
+          }
+        },
+        "resolve-from": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+          "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+          "dev": true
+        }
+      }
+    },
+    "cross-spawn": {
+      "version": "6.0.5",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+      "dev": true,
+      "requires": {
+        "nice-try": "^1.0.4",
+        "path-key": "^2.0.1",
+        "semver": "^5.5.0",
+        "shebang-command": "^1.2.0",
+        "which": "^1.2.9"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "currently-unhandled": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+      "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+      "dev": true,
+      "requires": {
+        "array-find-index": "^1.0.1"
+      }
+    },
+    "cyclist": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
+      "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
+      "dev": true
+    },
+    "dargs": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz",
+      "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=",
+      "dev": true,
+      "requires": {
+        "number-is-nan": "^1.0.0"
+      }
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "dateformat": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
+      "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==",
+      "dev": true
+    },
+    "debug": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+      "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+      "dev": true,
+      "requires": {
+        "ms": "^2.1.1"
+      }
+    },
+    "debuglog": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz",
+      "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=",
+      "dev": true
+    },
+    "decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+      "dev": true
+    },
+    "decamelize-keys": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz",
+      "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=",
+      "dev": true,
+      "requires": {
+        "decamelize": "^1.1.0",
+        "map-obj": "^1.0.0"
+      },
+      "dependencies": {
+        "map-obj": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+          "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+          "dev": true
+        }
+      }
+    },
+    "decode-uri-component": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+      "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+      "dev": true
+    },
+    "dedent": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
+      "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=",
+      "dev": true
+    },
+    "deep-is": {
+      "version": "0.1.3",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+      "dev": true
+    },
+    "defaults": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
+      "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
+      "dev": true,
+      "requires": {
+        "clone": "^1.0.2"
+      }
+    },
+    "define-properties": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+      "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "dev": true,
+      "requires": {
+        "object-keys": "^1.0.12"
+      }
+    },
+    "define-property": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+      "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+      "dev": true,
+      "requires": {
+        "is-descriptor": "^1.0.2",
+        "isobject": "^3.0.1"
+      },
+      "dependencies": {
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+      "dev": true
+    },
+    "delegates": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+      "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+      "dev": true
+    },
+    "deprecation": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
+      "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
+      "dev": true
+    },
+    "detect-indent": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz",
+      "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=",
+      "dev": true
+    },
+    "dezalgo": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
+      "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=",
+      "dev": true,
+      "requires": {
+        "asap": "^2.0.0",
+        "wrappy": "1"
+      }
+    },
+    "dir-glob": {
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz",
+      "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==",
+      "dev": true,
+      "requires": {
+        "path-type": "^3.0.0"
+      },
+      "dependencies": {
+        "path-type": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+          "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+          "dev": true,
+          "requires": {
+            "pify": "^3.0.0"
+          }
+        },
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        }
+      }
+    },
+    "doctrine": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+      "dev": true,
+      "requires": {
+        "esutils": "^2.0.2"
+      }
+    },
+    "dot-prop": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
+      "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
+      "dev": true,
+      "requires": {
+        "is-obj": "^1.0.0"
+      }
+    },
+    "duplexer": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
+      "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
+      "dev": true
+    },
+    "duplexify": {
+      "version": "3.7.1",
+      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+      "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.0.0",
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0",
+        "stream-shift": "^1.0.0"
+      }
+    },
+    "ecc-jsbn": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "dev": true,
+      "requires": {
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.1.0"
+      }
+    },
+    "emoji-regex": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
+      "dev": true
+    },
+    "encoding": {
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+      "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+      "dev": true,
+      "requires": {
+        "iconv-lite": "~0.4.13"
+      }
+    },
+    "end-of-stream": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+      "dev": true,
+      "requires": {
+        "once": "^1.4.0"
+      }
+    },
+    "env-paths": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz",
+      "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==",
+      "dev": true
+    },
+    "envinfo": {
+      "version": "7.5.0",
+      "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.5.0.tgz",
+      "integrity": "sha512-jDgnJaF/Btomk+m3PZDTTCb5XIIIX3zYItnCRfF73zVgvinLoRomuhi75Y4su0PtQxWz4v66XnLLckyvyJTOIQ==",
+      "dev": true
+    },
+    "err-code": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz",
+      "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=",
+      "dev": true
+    },
+    "error-ex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "dev": true,
+      "requires": {
+        "is-arrayish": "^0.2.1"
+      }
+    },
+    "es-abstract": {
+      "version": "1.17.4",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz",
+      "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==",
+      "dev": true,
+      "requires": {
+        "es-to-primitive": "^1.2.1",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.1",
+        "is-callable": "^1.1.5",
+        "is-regex": "^1.0.5",
+        "object-inspect": "^1.7.0",
+        "object-keys": "^1.1.1",
+        "object.assign": "^4.1.0",
+        "string.prototype.trimleft": "^2.1.1",
+        "string.prototype.trimright": "^2.1.1"
+      }
+    },
+    "es-to-primitive": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+      "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+      "dev": true,
+      "requires": {
+        "is-callable": "^1.1.4",
+        "is-date-object": "^1.0.1",
+        "is-symbol": "^1.0.2"
+      }
+    },
+    "es6-promise": {
+      "version": "4.2.8",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
+      "dev": true
+    },
+    "es6-promisify": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+      "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
+      "dev": true,
+      "requires": {
+        "es6-promise": "^4.0.3"
+      }
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+      "dev": true
+    },
+    "eslint": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.1.0.tgz",
+      "integrity": "sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==",
+      "dev": true,
+      "requires": {
+        "@babel/code-frame": "^7.0.0",
+        "ajv": "^6.10.0",
+        "chalk": "^2.1.0",
+        "cross-spawn": "^6.0.5",
+        "debug": "^4.0.1",
+        "doctrine": "^3.0.0",
+        "eslint-scope": "^5.0.0",
+        "eslint-utils": "^1.3.1",
+        "eslint-visitor-keys": "^1.0.0",
+        "espree": "^6.0.0",
+        "esquery": "^1.0.1",
+        "esutils": "^2.0.2",
+        "file-entry-cache": "^5.0.1",
+        "functional-red-black-tree": "^1.0.1",
+        "glob-parent": "^5.0.0",
+        "globals": "^11.7.0",
+        "ignore": "^4.0.6",
+        "import-fresh": "^3.0.0",
+        "imurmurhash": "^0.1.4",
+        "inquirer": "^6.4.1",
+        "is-glob": "^4.0.0",
+        "js-yaml": "^3.13.1",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.3.0",
+        "lodash": "^4.17.14",
+        "minimatch": "^3.0.4",
+        "mkdirp": "^0.5.1",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.8.2",
+        "progress": "^2.0.0",
+        "regexpp": "^2.0.1",
+        "semver": "^6.1.2",
+        "strip-ansi": "^5.2.0",
+        "strip-json-comments": "^3.0.1",
+        "table": "^5.2.3",
+        "text-table": "^0.2.0",
+        "v8-compile-cache": "^2.0.3"
+      }
+    },
+    "eslint-config-airbnb-base": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.0.0.tgz",
+      "integrity": "sha512-2IDHobw97upExLmsebhtfoD3NAKhV4H0CJWP3Uprd/uk+cHuWYOczPVxQ8PxLFUAw7o3Th1RAU8u1DoUpr+cMA==",
+      "dev": true,
+      "requires": {
+        "confusing-browser-globals": "^1.0.7",
+        "object.assign": "^4.1.0",
+        "object.entries": "^1.1.0"
+      }
+    },
+    "eslint-import-resolver-node": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz",
+      "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==",
+      "dev": true,
+      "requires": {
+        "debug": "^2.6.9",
+        "resolve": "^1.13.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-module-utils": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz",
+      "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==",
+      "dev": true,
+      "requires": {
+        "debug": "^2.6.9",
+        "pkg-dir": "^2.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-plugin-import": {
+      "version": "2.20.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz",
+      "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==",
+      "dev": true,
+      "requires": {
+        "array-includes": "^3.0.3",
+        "array.prototype.flat": "^1.2.1",
+        "contains-path": "^0.1.0",
+        "debug": "^2.6.9",
+        "doctrine": "1.5.0",
+        "eslint-import-resolver-node": "^0.3.2",
+        "eslint-module-utils": "^2.4.1",
+        "has": "^1.0.3",
+        "minimatch": "^3.0.4",
+        "object.values": "^1.1.0",
+        "read-pkg-up": "^2.0.0",
+        "resolve": "^1.12.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "doctrine": {
+          "version": "1.5.0",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
+          "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2",
+            "isarray": "^1.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "eslint-scope": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
+      "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
+      "dev": true,
+      "requires": {
+        "esrecurse": "^4.1.0",
+        "estraverse": "^4.1.1"
+      }
+    },
+    "eslint-utils": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
+      "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
+      "dev": true,
+      "requires": {
+        "eslint-visitor-keys": "^1.1.0"
+      }
+    },
+    "eslint-visitor-keys": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
+      "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
+      "dev": true
+    },
+    "espree": {
+      "version": "6.1.2",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz",
+      "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==",
+      "dev": true,
+      "requires": {
+        "acorn": "^7.1.0",
+        "acorn-jsx": "^5.1.0",
+        "eslint-visitor-keys": "^1.1.0"
+      }
+    },
+    "esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+      "dev": true
+    },
+    "esquery": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
+      "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^4.0.0"
+      }
+    },
+    "esrecurse": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+      "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^4.1.0"
+      }
+    },
+    "estraverse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true
+    },
+    "eventemitter3": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
+      "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==",
+      "dev": true
+    },
+    "execa": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
+      "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
+      "dev": true,
+      "requires": {
+        "cross-spawn": "^6.0.0",
+        "get-stream": "^4.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"
+      }
+    },
+    "expand-brackets": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+      "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+      "dev": true,
+      "requires": {
+        "debug": "^2.3.3",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "posix-character-classes": "^0.1.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+      "dev": true
+    },
+    "extend-shallow": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+      "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+      "dev": true,
+      "requires": {
+        "assign-symbols": "^1.0.0",
+        "is-extendable": "^1.0.1"
+      },
+      "dependencies": {
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
+      }
+    },
+    "external-editor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+      "dev": true,
+      "requires": {
+        "chardet": "^0.7.0",
+        "iconv-lite": "^0.4.24",
+        "tmp": "^0.0.33"
+      }
+    },
+    "extglob": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+      "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+      "dev": true,
+      "requires": {
+        "array-unique": "^0.3.2",
+        "define-property": "^1.0.0",
+        "expand-brackets": "^2.1.4",
+        "extend-shallow": "^2.0.1",
+        "fragment-cache": "^0.2.1",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "extsprintf": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+      "dev": true
+    },
+    "fast-deep-equal": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
+      "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
+      "dev": true
+    },
+    "fast-glob": {
+      "version": "2.2.7",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz",
+      "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==",
+      "dev": true,
+      "requires": {
+        "@mrmlnc/readdir-enhanced": "^2.2.1",
+        "@nodelib/fs.stat": "^1.1.2",
+        "glob-parent": "^3.1.0",
+        "is-glob": "^4.0.0",
+        "merge2": "^1.2.3",
+        "micromatch": "^3.1.10"
+      },
+      "dependencies": {
+        "glob-parent": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+          "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+          "dev": true,
+          "requires": {
+            "is-glob": "^3.1.0",
+            "path-dirname": "^1.0.0"
+          },
+          "dependencies": {
+            "is-glob": {
+              "version": "3.1.0",
+              "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+              "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+              "dev": true,
+              "requires": {
+                "is-extglob": "^2.1.0"
+              }
+            }
+          }
+        }
+      }
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+      "dev": true
+    },
+    "figgy-pudding": {
+      "version": "3.5.1",
+      "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
+      "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
+      "dev": true
+    },
+    "figures": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+      "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+      "dev": true,
+      "requires": {
+        "escape-string-regexp": "^1.0.5"
+      }
+    },
+    "file-entry-cache": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+      "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+      "dev": true,
+      "requires": {
+        "flat-cache": "^2.0.1"
+      }
+    },
+    "fill-range": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+      "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^2.0.1",
+        "is-number": "^3.0.0",
+        "repeat-string": "^1.6.1",
+        "to-regex-range": "^2.1.0"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "find-up": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+      "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+      "dev": true,
+      "requires": {
+        "locate-path": "^2.0.0"
+      }
+    },
+    "flat-cache": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+      "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+      "dev": true,
+      "requires": {
+        "flatted": "^2.0.0",
+        "rimraf": "2.6.3",
+        "write": "1.0.3"
+      }
+    },
+    "flatted": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
+      "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
+      "dev": true
+    },
+    "flush-write-stream": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
+      "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.3.6"
+      }
+    },
+    "for-in": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+      "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+      "dev": true
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+      "dev": true
+    },
+    "form-data": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "dev": true,
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "fragment-cache": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+      "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+      "dev": true,
+      "requires": {
+        "map-cache": "^0.2.2"
+      }
+    },
+    "from2": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+      "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.1",
+        "readable-stream": "^2.0.0"
+      }
+    },
+    "fs-extra": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+      "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^4.0.0",
+        "universalify": "^0.1.0"
+      }
+    },
+    "fs-minipass": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
+      "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
+      "dev": true,
+      "requires": {
+        "minipass": "^2.6.0"
+      }
+    },
+    "fs-write-stream-atomic": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
+      "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "iferr": "^0.1.5",
+        "imurmurhash": "^0.1.4",
+        "readable-stream": "1 || 2"
+      }
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+      "dev": true
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "functional-red-black-tree": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+      "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+      "dev": true
+    },
+    "gauge": {
+      "version": "2.7.4",
+      "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+      "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+      "dev": true,
+      "requires": {
+        "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"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+          "dev": true
+        },
+        "is-fullwidth-code-point": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+          "dev": true,
+          "requires": {
+            "number-is-nan": "^1.0.0"
+          }
+        },
+        "string-width": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+          "dev": true,
+          "requires": {
+            "code-point-at": "^1.0.0",
+            "is-fullwidth-code-point": "^1.0.0",
+            "strip-ansi": "^3.0.0"
+          }
+        },
+        "strip-ansi": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^2.0.0"
+          }
+        }
+      }
+    },
+    "genfun": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz",
+      "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==",
+      "dev": true
+    },
+    "get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "dev": true
+    },
+    "get-pkg-repo": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz",
+      "integrity": "sha1-xztInAbYDMVTbCyFP54FIyBWly0=",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.1.4",
+        "meow": "^3.3.0",
+        "normalize-package-data": "^2.3.0",
+        "parse-github-repo-url": "^1.3.0",
+        "through2": "^2.0.0"
+      },
+      "dependencies": {
+        "camelcase": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+          "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+          "dev": true
+        },
+        "camelcase-keys": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+          "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+          "dev": true,
+          "requires": {
+            "camelcase": "^2.0.0",
+            "map-obj": "^1.0.0"
+          }
+        },
+        "find-up": {
+          "version": "1.1.2",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+          "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+          "dev": true,
+          "requires": {
+            "path-exists": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "get-stdin": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+          "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+          "dev": true
+        },
+        "indent-string": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+          "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+          "dev": true,
+          "requires": {
+            "repeating": "^2.0.0"
+          }
+        },
+        "load-json-file": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+          "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "parse-json": "^2.2.0",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0",
+            "strip-bom": "^2.0.0"
+          }
+        },
+        "map-obj": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+          "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+          "dev": true
+        },
+        "meow": {
+          "version": "3.7.0",
+          "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+          "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+          "dev": true,
+          "requires": {
+            "camelcase-keys": "^2.0.0",
+            "decamelize": "^1.1.2",
+            "loud-rejection": "^1.0.0",
+            "map-obj": "^1.0.1",
+            "minimist": "^1.1.3",
+            "normalize-package-data": "^2.3.4",
+            "object-assign": "^4.0.1",
+            "read-pkg-up": "^1.0.1",
+            "redent": "^1.0.0",
+            "trim-newlines": "^1.0.0"
+          }
+        },
+        "minimist": {
+          "version": "1.2.5",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+          "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+          "dev": true
+        },
+        "path-exists": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+          "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+          "dev": true,
+          "requires": {
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "path-type": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+          "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "pify": "^2.0.0",
+            "pinkie-promise": "^2.0.0"
+          }
+        },
+        "read-pkg": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+          "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+          "dev": true,
+          "requires": {
+            "load-json-file": "^1.0.0",
+            "normalize-package-data": "^2.3.2",
+            "path-type": "^1.0.0"
+          }
+        },
+        "read-pkg-up": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+          "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+          "dev": true,
+          "requires": {
+            "find-up": "^1.0.0",
+            "read-pkg": "^1.0.0"
+          }
+        },
+        "redent": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+          "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+          "dev": true,
+          "requires": {
+            "indent-string": "^2.1.0",
+            "strip-indent": "^1.0.1"
+          }
+        },
+        "strip-bom": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+          "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+          "dev": true,
+          "requires": {
+            "is-utf8": "^0.2.0"
+          }
+        },
+        "strip-indent": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+          "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+          "dev": true,
+          "requires": {
+            "get-stdin": "^4.0.1"
+          }
+        },
+        "trim-newlines": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+          "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+          "dev": true
+        }
+      }
+    },
+    "get-port": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/get-port/-/get-port-4.2.0.tgz",
+      "integrity": "sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==",
+      "dev": true
+    },
+    "get-stdin": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz",
+      "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==",
+      "dev": true
+    },
+    "get-stream": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+      "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
+      "dev": true,
+      "requires": {
+        "pump": "^3.0.0"
+      }
+    },
+    "get-value": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+      "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+      "dev": true
+    },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "git-raw-commits": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.0.tgz",
+      "integrity": "sha512-w4jFEJFgKXMQJ0H0ikBk2S+4KP2VEjhCvLCNqbNRQC8BgGWgLKNCO7a9K9LI+TVT7Gfoloje502sEnctibffgg==",
+      "dev": true,
+      "requires": {
+        "dargs": "^4.0.1",
+        "lodash.template": "^4.0.2",
+        "meow": "^4.0.0",
+        "split2": "^2.0.0",
+        "through2": "^2.0.0"
+      },
+      "dependencies": {
+        "load-json-file": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+          "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "parse-json": "^4.0.0",
+            "pify": "^3.0.0",
+            "strip-bom": "^3.0.0"
+          }
+        },
+        "meow": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz",
+          "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==",
+          "dev": true,
+          "requires": {
+            "camelcase-keys": "^4.0.0",
+            "decamelize-keys": "^1.0.0",
+            "loud-rejection": "^1.0.0",
+            "minimist": "^1.1.3",
+            "minimist-options": "^3.0.1",
+            "normalize-package-data": "^2.3.4",
+            "read-pkg-up": "^3.0.0",
+            "redent": "^2.0.0",
+            "trim-newlines": "^2.0.0"
+          }
+        },
+        "minimist": {
+          "version": "1.2.5",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+          "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+          "dev": true
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
+          }
+        },
+        "path-type": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+          "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+          "dev": true,
+          "requires": {
+            "pify": "^3.0.0"
+          }
+        },
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        },
+        "read-pkg": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+          "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+          "dev": true,
+          "requires": {
+            "load-json-file": "^4.0.0",
+            "normalize-package-data": "^2.3.2",
+            "path-type": "^3.0.0"
+          }
+        },
+        "read-pkg-up": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz",
+          "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=",
+          "dev": true,
+          "requires": {
+            "find-up": "^2.0.0",
+            "read-pkg": "^3.0.0"
+          }
+        }
+      }
+    },
+    "git-remote-origin-url": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz",
+      "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=",
+      "dev": true,
+      "requires": {
+        "gitconfiglocal": "^1.0.0",
+        "pify": "^2.3.0"
+      }
+    },
+    "git-semver-tags": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-2.0.3.tgz",
+      "integrity": "sha512-tj4FD4ww2RX2ae//jSrXZzrocla9db5h0V7ikPl1P/WwoZar9epdUhwR7XHXSgc+ZkNq72BEEerqQuicoEQfzA==",
+      "dev": true,
+      "requires": {
+        "meow": "^4.0.0",
+        "semver": "^6.0.0"
+      },
+      "dependencies": {
+        "load-json-file": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+          "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "parse-json": "^4.0.0",
+            "pify": "^3.0.0",
+            "strip-bom": "^3.0.0"
+          }
+        },
+        "meow": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz",
+          "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==",
+          "dev": true,
+          "requires": {
+            "camelcase-keys": "^4.0.0",
+            "decamelize-keys": "^1.0.0",
+            "loud-rejection": "^1.0.0",
+            "minimist": "^1.1.3",
+            "minimist-options": "^3.0.1",
+            "normalize-package-data": "^2.3.4",
+            "read-pkg-up": "^3.0.0",
+            "redent": "^2.0.0",
+            "trim-newlines": "^2.0.0"
+          }
+        },
+        "minimist": {
+          "version": "1.2.5",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+          "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+          "dev": true
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
+          }
+        },
+        "path-type": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+          "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+          "dev": true,
+          "requires": {
+            "pify": "^3.0.0"
+          }
+        },
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        },
+        "read-pkg": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+          "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+          "dev": true,
+          "requires": {
+            "load-json-file": "^4.0.0",
+            "normalize-package-data": "^2.3.2",
+            "path-type": "^3.0.0"
+          }
+        },
+        "read-pkg-up": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz",
+          "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=",
+          "dev": true,
+          "requires": {
+            "find-up": "^2.0.0",
+            "read-pkg": "^3.0.0"
+          }
+        }
+      }
+    },
+    "git-up": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/git-up/-/git-up-4.0.1.tgz",
+      "integrity": "sha512-LFTZZrBlrCrGCG07/dm1aCjjpL1z9L3+5aEeI9SBhAqSc+kiA9Or1bgZhQFNppJX6h/f5McrvJt1mQXTFm6Qrw==",
+      "dev": true,
+      "requires": {
+        "is-ssh": "^1.3.0",
+        "parse-url": "^5.0.0"
+      }
+    },
+    "git-url-parse": {
+      "version": "11.1.2",
+      "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.1.2.tgz",
+      "integrity": "sha512-gZeLVGY8QVKMIkckncX+iCq2/L8PlwncvDFKiWkBn9EtCfYDbliRTTp6qzyQ1VMdITUfq7293zDzfpjdiGASSQ==",
+      "dev": true,
+      "requires": {
+        "git-up": "^4.0.0"
+      }
+    },
+    "gitconfiglocal": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz",
+      "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=",
+      "dev": true,
+      "requires": {
+        "ini": "^1.3.2"
+      }
+    },
+    "glob": {
+      "version": "7.1.6",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+      "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+      "dev": true,
+      "requires": {
+        "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-parent": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
+      "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
+      "dev": true,
+      "requires": {
+        "is-glob": "^4.0.1"
+      }
+    },
+    "glob-to-regexp": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz",
+      "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=",
+      "dev": true
+    },
+    "globals": {
+      "version": "11.12.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+      "dev": true
+    },
+    "globby": {
+      "version": "9.2.0",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz",
+      "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==",
+      "dev": true,
+      "requires": {
+        "@types/glob": "^7.1.1",
+        "array-union": "^1.0.2",
+        "dir-glob": "^2.2.2",
+        "fast-glob": "^2.2.6",
+        "glob": "^7.1.3",
+        "ignore": "^4.0.3",
+        "pify": "^4.0.1",
+        "slash": "^2.0.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        },
+        "slash": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+          "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+          "dev": true
+        }
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
+      "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
+      "dev": true
+    },
+    "handlebars": {
+      "version": "4.7.6",
+      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz",
+      "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.5",
+        "neo-async": "^2.6.0",
+        "source-map": "^0.6.1",
+        "uglify-js": "^3.1.4",
+        "wordwrap": "^1.0.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
+    "har-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+      "dev": true
+    },
+    "har-validator": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+      "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.5.5",
+        "har-schema": "^2.0.0"
+      }
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+      "dev": true
+    },
+    "has-symbols": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
+      "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+      "dev": true
+    },
+    "has-unicode": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+      "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+      "dev": true
+    },
+    "has-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+      "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+      "dev": true,
+      "requires": {
+        "get-value": "^2.0.6",
+        "has-values": "^1.0.0",
+        "isobject": "^3.0.0"
+      }
+    },
+    "has-values": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+      "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+      "dev": true,
+      "requires": {
+        "is-number": "^3.0.0",
+        "kind-of": "^4.0.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+          "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "hosted-git-info": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz",
+      "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==",
+      "dev": true
+    },
+    "http-cache-semantics": {
+      "version": "3.8.1",
+      "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz",
+      "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==",
+      "dev": true
+    },
+    "http-proxy-agent": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
+      "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
+      "dev": true,
+      "requires": {
+        "agent-base": "4",
+        "debug": "3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "http-signature": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "jsprim": "^1.2.2",
+        "sshpk": "^1.7.0"
+      }
+    },
+    "https-proxy-agent": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz",
+      "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==",
+      "dev": true,
+      "requires": {
+        "agent-base": "^4.3.0",
+        "debug": "^3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "humanize-ms": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+      "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
+      "dev": true,
+      "requires": {
+        "ms": "^2.0.0"
+      }
+    },
+    "husky": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/husky/-/husky-3.1.0.tgz",
+      "integrity": "sha512-FJkPoHHB+6s4a+jwPqBudBDvYZsoQW5/HBuMSehC8qDiCe50kpcxeqFoDSlow+9I6wg47YxBoT3WxaURlrDIIQ==",
+      "dev": true,
+      "requires": {
+        "chalk": "^2.4.2",
+        "ci-info": "^2.0.0",
+        "cosmiconfig": "^5.2.1",
+        "execa": "^1.0.0",
+        "get-stdin": "^7.0.0",
+        "opencollective-postinstall": "^2.0.2",
+        "pkg-dir": "^4.2.0",
+        "please-upgrade-node": "^3.2.0",
+        "read-pkg": "^5.2.0",
+        "run-node": "^1.0.0",
+        "slash": "^3.0.0"
+      },
+      "dependencies": {
+        "find-up": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+          "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+          "dev": true,
+          "requires": {
+            "locate-path": "^5.0.0",
+            "path-exists": "^4.0.0"
+          }
+        },
+        "locate-path": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+          "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+          "dev": true,
+          "requires": {
+            "p-locate": "^4.1.0"
+          }
+        },
+        "p-limit": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
+          "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
+          "dev": true,
+          "requires": {
+            "p-try": "^2.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+          "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+          "dev": true,
+          "requires": {
+            "p-limit": "^2.2.0"
+          }
+        },
+        "p-try": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+          "dev": true
+        },
+        "parse-json": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
+          "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
+          "dev": true,
+          "requires": {
+            "@babel/code-frame": "^7.0.0",
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1",
+            "lines-and-columns": "^1.1.6"
+          }
+        },
+        "path-exists": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+          "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+          "dev": true
+        },
+        "pkg-dir": {
+          "version": "4.2.0",
+          "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+          "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+          "dev": true,
+          "requires": {
+            "find-up": "^4.0.0"
+          }
+        },
+        "read-pkg": {
+          "version": "5.2.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+          "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+          "dev": true,
+          "requires": {
+            "@types/normalize-package-data": "^2.4.0",
+            "normalize-package-data": "^2.5.0",
+            "parse-json": "^5.0.0",
+            "type-fest": "^0.6.0"
+          }
+        }
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "dev": true,
+      "requires": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      }
+    },
+    "iferr": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+      "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
+      "dev": true
+    },
+    "ignore": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+      "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+      "dev": true
+    },
+    "ignore-walk": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
+      "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
+      "dev": true,
+      "requires": {
+        "minimatch": "^3.0.4"
+      }
+    },
+    "import-fresh": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
+      "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
+      "dev": true,
+      "requires": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      }
+    },
+    "import-local": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz",
+      "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==",
+      "dev": true,
+      "requires": {
+        "pkg-dir": "^3.0.0",
+        "resolve-cwd": "^2.0.0"
+      },
+      "dependencies": {
+        "find-up": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+          "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+          "dev": true,
+          "requires": {
+            "locate-path": "^3.0.0"
+          }
+        },
+        "locate-path": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+          "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+          "dev": true,
+          "requires": {
+            "p-locate": "^3.0.0",
+            "path-exists": "^3.0.0"
+          }
+        },
+        "p-limit": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
+          "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
+          "dev": true,
+          "requires": {
+            "p-try": "^2.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+          "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+          "dev": true,
+          "requires": {
+            "p-limit": "^2.0.0"
+          }
+        },
+        "p-try": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+          "dev": true
+        },
+        "pkg-dir": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+          "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+          "dev": true,
+          "requires": {
+            "find-up": "^3.0.0"
+          }
+        }
+      }
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+      "dev": true
+    },
+    "indent-string": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
+      "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=",
+      "dev": true
+    },
+    "infer-owner": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+      "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dev": true,
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true
+    },
+    "ini": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+      "dev": true
+    },
+    "init-package-json": {
+      "version": "1.10.3",
+      "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz",
+      "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.1",
+        "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0",
+        "promzard": "^0.3.0",
+        "read": "~1.0.1",
+        "read-package-json": "1 || 2",
+        "semver": "2.x || 3.x || 4 || 5",
+        "validate-npm-package-license": "^3.0.1",
+        "validate-npm-package-name": "^3.0.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "inquirer": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
+      "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
+      "dev": true,
+      "requires": {
+        "ansi-escapes": "^3.2.0",
+        "chalk": "^2.4.2",
+        "cli-cursor": "^2.1.0",
+        "cli-width": "^2.0.0",
+        "external-editor": "^3.0.3",
+        "figures": "^2.0.0",
+        "lodash": "^4.17.12",
+        "mute-stream": "0.0.7",
+        "run-async": "^2.2.0",
+        "rxjs": "^6.4.0",
+        "string-width": "^2.1.0",
+        "strip-ansi": "^5.1.0",
+        "through": "^2.3.6"
+      }
+    },
+    "ip": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
+      "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
+      "dev": true
+    },
+    "is-accessor-descriptor": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+      "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+      "dev": true
+    },
+    "is-buffer": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+      "dev": true
+    },
+    "is-callable": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz",
+      "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
+      "dev": true
+    },
+    "is-ci": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
+      "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
+      "dev": true,
+      "requires": {
+        "ci-info": "^2.0.0"
+      }
+    },
+    "is-data-descriptor": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+      "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-date-object": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
+      "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==",
+      "dev": true
+    },
+    "is-descriptor": {
+      "version": "0.1.6",
+      "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+      "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+      "dev": true,
+      "requires": {
+        "is-accessor-descriptor": "^0.1.6",
+        "is-data-descriptor": "^0.1.4",
+        "kind-of": "^5.0.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "5.1.0",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+          "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+          "dev": true
+        }
+      }
+    },
+    "is-directory": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+      "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
+      "dev": true
+    },
+    "is-extendable": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+      "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+      "dev": true
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "dev": true
+    },
+    "is-finite": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+      "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+      "dev": true,
+      "requires": {
+        "number-is-nan": "^1.0.0"
+      }
+    },
+    "is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+      "dev": true
+    },
+    "is-glob": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+      "dev": true,
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-number": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+      "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "is-obj": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+      "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
+      "dev": true
+    },
+    "is-plain-obj": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+      "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
+      "dev": true
+    },
+    "is-plain-object": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
+    "is-promise": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+      "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+      "dev": true
+    },
+    "is-regex": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
+      "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==",
+      "dev": true,
+      "requires": {
+        "has": "^1.0.3"
+      }
+    },
+    "is-ssh": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.1.tgz",
+      "integrity": "sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg==",
+      "dev": true,
+      "requires": {
+        "protocols": "^1.1.0"
+      }
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "dev": true
+    },
+    "is-string": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
+      "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==",
+      "dev": true
+    },
+    "is-symbol": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
+      "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
+      "dev": true,
+      "requires": {
+        "has-symbols": "^1.0.1"
+      }
+    },
+    "is-text-path": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz",
+      "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=",
+      "dev": true,
+      "requires": {
+        "text-extensions": "^1.0.0"
+      }
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+      "dev": true
+    },
+    "is-utf8": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+      "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+      "dev": true
+    },
+    "is-windows": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+      "dev": true
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+      "dev": true
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
+    },
+    "isobject": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+      "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+      "dev": true
+    },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+      "dev": true
+    },
+    "js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+      "dev": true
+    },
+    "js-yaml": {
+      "version": "3.13.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+      "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+      "dev": true,
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+      "dev": true
+    },
+    "json-parse-better-errors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+      "dev": true
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+      "dev": true
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+      "dev": true
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+      "dev": true
+    },
+    "jsonfile": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+      "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "jsonparse": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
+      "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
+      "dev": true
+    },
+    "jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
+    "kind-of": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+      "dev": true
+    },
+    "lerna": {
+      "version": "3.20.2",
+      "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.20.2.tgz",
+      "integrity": "sha512-bjdL7hPLpU3Y8CBnw/1ys3ynQMUjiK6l9iDWnEGwFtDy48Xh5JboR9ZJwmKGCz9A/sarVVIGwf1tlRNKUG9etA==",
+      "dev": true,
+      "requires": {
+        "@lerna/add": "3.20.0",
+        "@lerna/bootstrap": "3.20.0",
+        "@lerna/changed": "3.20.0",
+        "@lerna/clean": "3.20.0",
+        "@lerna/cli": "3.18.5",
+        "@lerna/create": "3.18.5",
+        "@lerna/diff": "3.18.5",
+        "@lerna/exec": "3.20.0",
+        "@lerna/import": "3.18.5",
+        "@lerna/info": "3.20.0",
+        "@lerna/init": "3.18.5",
+        "@lerna/link": "3.18.5",
+        "@lerna/list": "3.20.0",
+        "@lerna/publish": "3.20.2",
+        "@lerna/run": "3.20.0",
+        "@lerna/version": "3.20.2",
+        "import-local": "^2.0.0",
+        "npmlog": "^4.1.2"
+      }
+    },
+    "levn": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2"
+      }
+    },
+    "lines-and-columns": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
+      "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
+      "dev": true
+    },
+    "load-json-file": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
+      "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "parse-json": "^2.2.0",
+        "pify": "^2.0.0",
+        "strip-bom": "^3.0.0"
+      }
+    },
+    "locate-path": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+      "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+      "dev": true,
+      "requires": {
+        "p-locate": "^2.0.0",
+        "path-exists": "^3.0.0"
+      }
+    },
+    "lodash": {
+      "version": "4.17.15",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+      "dev": true
+    },
+    "lodash._reinterpolate": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
+      "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
+      "dev": true
+    },
+    "lodash.clonedeep": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+      "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+      "dev": true
+    },
+    "lodash.get": {
+      "version": "4.4.2",
+      "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
+      "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
+      "dev": true
+    },
+    "lodash.ismatch": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz",
+      "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=",
+      "dev": true
+    },
+    "lodash.set": {
+      "version": "4.3.2",
+      "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
+      "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=",
+      "dev": true
+    },
+    "lodash.sortby": {
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
+      "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=",
+      "dev": true
+    },
+    "lodash.template": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
+      "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==",
+      "dev": true,
+      "requires": {
+        "lodash._reinterpolate": "^3.0.0",
+        "lodash.templatesettings": "^4.0.0"
+      }
+    },
+    "lodash.templatesettings": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
+      "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==",
+      "dev": true,
+      "requires": {
+        "lodash._reinterpolate": "^3.0.0"
+      }
+    },
+    "lodash.uniq": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+      "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=",
+      "dev": true
+    },
+    "loud-rejection": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+      "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+      "dev": true,
+      "requires": {
+        "currently-unhandled": "^0.4.1",
+        "signal-exit": "^3.0.0"
+      }
+    },
+    "lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "dev": true,
+      "requires": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "macos-release": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz",
+      "integrity": "sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==",
+      "dev": true
+    },
+    "make-dir": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+      "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+      "dev": true,
+      "requires": {
+        "pify": "^3.0.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        }
+      }
+    },
+    "make-fetch-happen": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz",
+      "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==",
+      "dev": true,
+      "requires": {
+        "agentkeepalive": "^3.4.1",
+        "cacache": "^12.0.0",
+        "http-cache-semantics": "^3.8.1",
+        "http-proxy-agent": "^2.1.0",
+        "https-proxy-agent": "^2.2.3",
+        "lru-cache": "^5.1.1",
+        "mississippi": "^3.0.0",
+        "node-fetch-npm": "^2.0.2",
+        "promise-retry": "^1.1.1",
+        "socks-proxy-agent": "^4.0.0",
+        "ssri": "^6.0.0"
+      }
+    },
+    "map-cache": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+      "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+      "dev": true
+    },
+    "map-obj": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz",
+      "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=",
+      "dev": true
+    },
+    "map-visit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+      "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+      "dev": true,
+      "requires": {
+        "object-visit": "^1.0.0"
+      }
+    },
+    "memorystream": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
+      "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=",
+      "dev": true
+    },
+    "meow": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz",
+      "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==",
+      "dev": true,
+      "requires": {
+        "camelcase-keys": "^4.0.0",
+        "decamelize-keys": "^1.0.0",
+        "loud-rejection": "^1.0.0",
+        "minimist-options": "^3.0.1",
+        "normalize-package-data": "^2.3.4",
+        "read-pkg-up": "^3.0.0",
+        "redent": "^2.0.0",
+        "trim-newlines": "^2.0.0",
+        "yargs-parser": "^10.0.0"
+      },
+      "dependencies": {
+        "camelcase": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+          "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+          "dev": true
+        },
+        "load-json-file": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+          "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "parse-json": "^4.0.0",
+            "pify": "^3.0.0",
+            "strip-bom": "^3.0.0"
+          }
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
+          }
+        },
+        "path-type": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+          "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+          "dev": true,
+          "requires": {
+            "pify": "^3.0.0"
+          }
+        },
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        },
+        "read-pkg": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+          "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+          "dev": true,
+          "requires": {
+            "load-json-file": "^4.0.0",
+            "normalize-package-data": "^2.3.2",
+            "path-type": "^3.0.0"
+          }
+        },
+        "read-pkg-up": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz",
+          "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=",
+          "dev": true,
+          "requires": {
+            "find-up": "^2.0.0",
+            "read-pkg": "^3.0.0"
+          }
+        },
+        "yargs-parser": {
+          "version": "10.1.0",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
+          "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==",
+          "dev": true,
+          "requires": {
+            "camelcase": "^4.1.0"
+          }
+        }
+      }
+    },
+    "merge2": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz",
+      "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==",
+      "dev": true
+    },
+    "micromatch": {
+      "version": "3.1.10",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+      "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+      "dev": true,
+      "requires": {
+        "arr-diff": "^4.0.0",
+        "array-unique": "^0.3.2",
+        "braces": "^2.3.1",
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "extglob": "^2.0.4",
+        "fragment-cache": "^0.2.1",
+        "kind-of": "^6.0.2",
+        "nanomatch": "^1.2.9",
+        "object.pick": "^1.3.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.2"
+      }
+    },
+    "mime-db": {
+      "version": "1.43.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz",
+      "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==",
+      "dev": true
+    },
+    "mime-types": {
+      "version": "2.1.26",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz",
+      "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==",
+      "dev": true,
+      "requires": {
+        "mime-db": "1.43.0"
+      }
+    },
+    "mimic-fn": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+      "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+      "dev": true
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+      "dev": true
+    },
+    "minimist-options": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz",
+      "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==",
+      "dev": true,
+      "requires": {
+        "arrify": "^1.0.1",
+        "is-plain-obj": "^1.1.0"
+      }
+    },
+    "minipass": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
+      "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.0"
+      }
+    },
+    "minizlib": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
+      "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
+      "dev": true,
+      "requires": {
+        "minipass": "^2.9.0"
+      }
+    },
+    "mississippi": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
+      "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
+      "dev": true,
+      "requires": {
+        "concat-stream": "^1.5.0",
+        "duplexify": "^3.4.2",
+        "end-of-stream": "^1.1.0",
+        "flush-write-stream": "^1.0.0",
+        "from2": "^2.1.0",
+        "parallel-transform": "^1.1.0",
+        "pump": "^3.0.0",
+        "pumpify": "^1.3.3",
+        "stream-each": "^1.1.0",
+        "through2": "^2.0.0"
+      }
+    },
+    "mixin-deep": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+      "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
+      "dev": true,
+      "requires": {
+        "for-in": "^1.0.2",
+        "is-extendable": "^1.0.1"
+      },
+      "dependencies": {
+        "is-extendable": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+          "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+          "dev": true,
+          "requires": {
+            "is-plain-object": "^2.0.4"
+          }
+        }
+      }
+    },
+    "mkdirp": {
+      "version": "0.5.5",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.5"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.5",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+          "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+          "dev": true
+        }
+      }
+    },
+    "mkdirp-promise": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz",
+      "integrity": "sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE=",
+      "dev": true,
+      "requires": {
+        "mkdirp": "*"
+      }
+    },
+    "modify-values": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz",
+      "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==",
+      "dev": true
+    },
+    "move-concurrently": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+      "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.1.1",
+        "copy-concurrently": "^1.0.0",
+        "fs-write-stream-atomic": "^1.0.8",
+        "mkdirp": "^0.5.1",
+        "rimraf": "^2.5.4",
+        "run-queue": "^1.0.3"
+      }
+    },
+    "ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "multimatch": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-3.0.0.tgz",
+      "integrity": "sha512-22foS/gqQfANZ3o+W7ST2x25ueHDVNWl/b9OlGcLpy/iKxjCpvcNCM51YCenUi7Mt/jAjjqv8JwZRs8YP5sRjA==",
+      "dev": true,
+      "requires": {
+        "array-differ": "^2.0.3",
+        "array-union": "^1.0.2",
+        "arrify": "^1.0.1",
+        "minimatch": "^3.0.4"
+      }
+    },
+    "mute-stream": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+      "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
+      "dev": true
+    },
+    "mz": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+      "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+      "dev": true,
+      "requires": {
+        "any-promise": "^1.0.0",
+        "object-assign": "^4.0.1",
+        "thenify-all": "^1.0.0"
+      }
+    },
+    "nanomatch": {
+      "version": "1.2.13",
+      "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+      "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+      "dev": true,
+      "requires": {
+        "arr-diff": "^4.0.0",
+        "array-unique": "^0.3.2",
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "fragment-cache": "^0.2.1",
+        "is-windows": "^1.0.2",
+        "kind-of": "^6.0.2",
+        "object.pick": "^1.3.0",
+        "regex-not": "^1.0.0",
+        "snapdragon": "^0.8.1",
+        "to-regex": "^3.0.1"
+      }
+    },
+    "natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+      "dev": true
+    },
+    "neo-async": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
+      "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
+      "dev": true
+    },
+    "nice-try": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+      "dev": true
+    },
+    "node-fetch": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
+      "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==",
+      "dev": true
+    },
+    "node-fetch-npm": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz",
+      "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==",
+      "dev": true,
+      "requires": {
+        "encoding": "^0.1.11",
+        "json-parse-better-errors": "^1.0.0",
+        "safe-buffer": "^5.1.1"
+      }
+    },
+    "node-gyp": {
+      "version": "5.0.7",
+      "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.0.7.tgz",
+      "integrity": "sha512-K8aByl8OJD51V0VbUURTKsmdswkQQusIvlvmTyhHlIT1hBvaSxzdxpSle857XuXa7uc02UEZx9OR5aDxSWS5Qw==",
+      "dev": true,
+      "requires": {
+        "env-paths": "^2.2.0",
+        "glob": "^7.1.4",
+        "graceful-fs": "^4.2.2",
+        "mkdirp": "^0.5.1",
+        "nopt": "^4.0.1",
+        "npmlog": "^4.1.2",
+        "request": "^2.88.0",
+        "rimraf": "^2.6.3",
+        "semver": "^5.7.1",
+        "tar": "^4.4.12",
+        "which": "^1.3.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "nopt": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+      "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+      "dev": true,
+      "requires": {
+        "abbrev": "1",
+        "osenv": "^0.1.4"
+      }
+    },
+    "normalize-package-data": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.1.4",
+        "resolve": "^1.10.0",
+        "semver": "2 || 3 || 4 || 5",
+        "validate-npm-package-license": "^3.0.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "normalize-url": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz",
+      "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==",
+      "dev": true
+    },
+    "npm-bundled": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
+      "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
+      "dev": true,
+      "requires": {
+        "npm-normalize-package-bin": "^1.0.1"
+      }
+    },
+    "npm-lifecycle": {
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz",
+      "integrity": "sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g==",
+      "dev": true,
+      "requires": {
+        "byline": "^5.0.0",
+        "graceful-fs": "^4.1.15",
+        "node-gyp": "^5.0.2",
+        "resolve-from": "^4.0.0",
+        "slide": "^1.1.6",
+        "uid-number": "0.0.6",
+        "umask": "^1.1.0",
+        "which": "^1.3.1"
+      }
+    },
+    "npm-normalize-package-bin": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
+      "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
+      "dev": true
+    },
+    "npm-package-arg": {
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz",
+      "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.7.1",
+        "osenv": "^0.1.5",
+        "semver": "^5.6.0",
+        "validate-npm-package-name": "^3.0.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "npm-packlist": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
+      "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
+      "dev": true,
+      "requires": {
+        "ignore-walk": "^3.0.1",
+        "npm-bundled": "^1.0.1",
+        "npm-normalize-package-bin": "^1.0.1"
+      }
+    },
+    "npm-pick-manifest": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz",
+      "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==",
+      "dev": true,
+      "requires": {
+        "figgy-pudding": "^3.5.1",
+        "npm-package-arg": "^6.0.0",
+        "semver": "^5.4.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "npm-run-all": {
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz",
+      "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "chalk": "^2.4.1",
+        "cross-spawn": "^6.0.5",
+        "memorystream": "^0.3.1",
+        "minimatch": "^3.0.4",
+        "pidtree": "^0.3.0",
+        "read-pkg": "^3.0.0",
+        "shell-quote": "^1.6.1",
+        "string.prototype.padend": "^3.0.0"
+      },
+      "dependencies": {
+        "load-json-file": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+          "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+          "dev": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "parse-json": "^4.0.0",
+            "pify": "^3.0.0",
+            "strip-bom": "^3.0.0"
+          }
+        },
+        "parse-json": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+          "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+          "dev": true,
+          "requires": {
+            "error-ex": "^1.3.1",
+            "json-parse-better-errors": "^1.0.1"
+          }
+        },
+        "path-type": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+          "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+          "dev": true,
+          "requires": {
+            "pify": "^3.0.0"
+          }
+        },
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        },
+        "read-pkg": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+          "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+          "dev": true,
+          "requires": {
+            "load-json-file": "^4.0.0",
+            "normalize-package-data": "^2.3.2",
+            "path-type": "^3.0.0"
+          }
+        }
+      }
+    },
+    "npm-run-path": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+      "dev": true,
+      "requires": {
+        "path-key": "^2.0.0"
+      }
+    },
+    "npmlog": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+      "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+      "dev": true,
+      "requires": {
+        "are-we-there-yet": "~1.1.2",
+        "console-control-strings": "~1.1.0",
+        "gauge": "~2.7.3",
+        "set-blocking": "~2.0.0"
+      }
+    },
+    "number-is-nan": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+      "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+      "dev": true
+    },
+    "oauth-sign": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+      "dev": true
+    },
+    "object-assign": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+      "dev": true
+    },
+    "object-copy": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+      "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+      "dev": true,
+      "requires": {
+        "copy-descriptor": "^0.1.0",
+        "define-property": "^0.2.5",
+        "kind-of": "^3.0.3"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "object-inspect": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
+      "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==",
+      "dev": true
+    },
+    "object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+      "dev": true
+    },
+    "object-visit": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+      "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.0"
+      }
+    },
+    "object.assign": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+      "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.2",
+        "function-bind": "^1.1.1",
+        "has-symbols": "^1.0.0",
+        "object-keys": "^1.0.11"
+      }
+    },
+    "object.entries": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz",
+      "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3"
+      }
+    },
+    "object.getownpropertydescriptors": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz",
+      "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1"
+      }
+    },
+    "object.pick": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+      "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
+    "object.values": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz",
+      "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1",
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3"
+      }
+    },
+    "octokit-pagination-methods": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz",
+      "integrity": "sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==",
+      "dev": true
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dev": true,
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "onetime": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+      "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+      "dev": true,
+      "requires": {
+        "mimic-fn": "^1.0.0"
+      }
+    },
+    "opencollective-postinstall": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz",
+      "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==",
+      "dev": true
+    },
+    "optionator": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+      "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+      "dev": true,
+      "requires": {
+        "deep-is": "~0.1.3",
+        "fast-levenshtein": "~2.0.6",
+        "levn": "~0.3.0",
+        "prelude-ls": "~1.1.2",
+        "type-check": "~0.3.2",
+        "word-wrap": "~1.2.3"
+      }
+    },
+    "os-homedir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+      "dev": true
+    },
+    "os-name": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz",
+      "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==",
+      "dev": true,
+      "requires": {
+        "macos-release": "^2.2.0",
+        "windows-release": "^3.1.0"
+      }
+    },
+    "os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+      "dev": true
+    },
+    "osenv": {
+      "version": "0.1.5",
+      "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+      "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+      "dev": true,
+      "requires": {
+        "os-homedir": "^1.0.0",
+        "os-tmpdir": "^1.0.0"
+      }
+    },
+    "p-finally": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+      "dev": true
+    },
+    "p-limit": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+      "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+      "dev": true,
+      "requires": {
+        "p-try": "^1.0.0"
+      }
+    },
+    "p-locate": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+      "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+      "dev": true,
+      "requires": {
+        "p-limit": "^1.1.0"
+      }
+    },
+    "p-map": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+      "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+      "dev": true
+    },
+    "p-map-series": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz",
+      "integrity": "sha1-v5j+V1cFZYqeE1G++4WuTB8Hvco=",
+      "dev": true,
+      "requires": {
+        "p-reduce": "^1.0.0"
+      }
+    },
+    "p-pipe": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/p-pipe/-/p-pipe-1.2.0.tgz",
+      "integrity": "sha1-SxoROZoRUgpneQ7loMHViB1r7+k=",
+      "dev": true
+    },
+    "p-queue": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-4.0.0.tgz",
+      "integrity": "sha512-3cRXXn3/O0o3+eVmUroJPSj/esxoEFIm0ZOno/T+NzG/VZgPOqQ8WKmlNqubSEpZmCIngEy34unkHGg83ZIBmg==",
+      "dev": true,
+      "requires": {
+        "eventemitter3": "^3.1.0"
+      }
+    },
+    "p-reduce": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz",
+      "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=",
+      "dev": true
+    },
+    "p-try": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+      "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+      "dev": true
+    },
+    "p-waterfall": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/p-waterfall/-/p-waterfall-1.0.0.tgz",
+      "integrity": "sha1-ftlLPOszMngjU69qrhGqn8I1uwA=",
+      "dev": true,
+      "requires": {
+        "p-reduce": "^1.0.0"
+      }
+    },
+    "parallel-transform": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
+      "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
+      "dev": true,
+      "requires": {
+        "cyclist": "^1.0.1",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.1.5"
+      }
+    },
+    "parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "requires": {
+        "callsites": "^3.0.0"
+      }
+    },
+    "parse-github-repo-url": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz",
+      "integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=",
+      "dev": true
+    },
+    "parse-json": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+      "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+      "dev": true,
+      "requires": {
+        "error-ex": "^1.2.0"
+      }
+    },
+    "parse-path": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.1.tgz",
+      "integrity": "sha512-d7yhga0Oc+PwNXDvQ0Jv1BuWkLVPXcAoQ/WREgd6vNNoKYaW52KI+RdOFjI63wjkmps9yUE8VS4veP+AgpQ/hA==",
+      "dev": true,
+      "requires": {
+        "is-ssh": "^1.3.0",
+        "protocols": "^1.4.0"
+      }
+    },
+    "parse-url": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-5.0.1.tgz",
+      "integrity": "sha512-flNUPP27r3vJpROi0/R3/2efgKkyXqnXwyP1KQ2U0SfFRgdizOdWfvrrvJg1LuOoxs7GQhmxJlq23IpQ/BkByg==",
+      "dev": true,
+      "requires": {
+        "is-ssh": "^1.3.0",
+        "normalize-url": "^3.3.0",
+        "parse-path": "^4.0.0",
+        "protocols": "^1.4.0"
+      }
+    },
+    "pascalcase": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+      "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+      "dev": true
+    },
+    "path-dirname": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+      "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+      "dev": true
+    },
+    "path-exists": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+      "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+      "dev": true
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "dev": true
+    },
+    "path-key": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "dev": true
+    },
+    "path-parse": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+      "dev": true
+    },
+    "path-type": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
+      "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+      "dev": true,
+      "requires": {
+        "pify": "^2.0.0"
+      }
+    },
+    "performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
+      "dev": true
+    },
+    "pidtree": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.0.tgz",
+      "integrity": "sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==",
+      "dev": true
+    },
+    "pify": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+      "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+      "dev": true
+    },
+    "pinkie": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+      "dev": true
+    },
+    "pinkie-promise": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+      "dev": true,
+      "requires": {
+        "pinkie": "^2.0.0"
+      }
+    },
+    "pkg-dir": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+      "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
+      "dev": true,
+      "requires": {
+        "find-up": "^2.1.0"
+      }
+    },
+    "please-upgrade-node": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
+      "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==",
+      "dev": true,
+      "requires": {
+        "semver-compare": "^1.0.0"
+      }
+    },
+    "posix-character-classes": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+      "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+      "dev": true
+    },
+    "prelude-ls": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+      "dev": true
+    },
+    "process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+      "dev": true
+    },
+    "progress": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+      "dev": true
+    },
+    "promise-inflight": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+      "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
+      "dev": true
+    },
+    "promise-retry": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz",
+      "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=",
+      "dev": true,
+      "requires": {
+        "err-code": "^1.0.0",
+        "retry": "^0.10.0"
+      }
+    },
+    "promzard": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz",
+      "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=",
+      "dev": true,
+      "requires": {
+        "read": "1"
+      }
+    },
+    "proto-list": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+      "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=",
+      "dev": true
+    },
+    "protocols": {
+      "version": "1.4.7",
+      "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.7.tgz",
+      "integrity": "sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg==",
+      "dev": true
+    },
+    "protoduck": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz",
+      "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==",
+      "dev": true,
+      "requires": {
+        "genfun": "^5.0.0"
+      }
+    },
+    "psl": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz",
+      "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==",
+      "dev": true
+    },
+    "pump": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+      "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.1.0",
+        "once": "^1.3.1"
+      }
+    },
+    "pumpify": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+      "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+      "dev": true,
+      "requires": {
+        "duplexify": "^3.6.0",
+        "inherits": "^2.0.3",
+        "pump": "^2.0.0"
+      },
+      "dependencies": {
+        "pump": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+          "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+          "dev": true,
+          "requires": {
+            "end-of-stream": "^1.1.0",
+            "once": "^1.3.1"
+          }
+        }
+      }
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "dev": true
+    },
+    "q": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
+      "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
+      "dev": true
+    },
+    "qs": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+      "dev": true
+    },
+    "quick-lru": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz",
+      "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=",
+      "dev": true
+    },
+    "read": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",
+      "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=",
+      "dev": true,
+      "requires": {
+        "mute-stream": "~0.0.4"
+      }
+    },
+    "read-cmd-shim": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz",
+      "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2"
+      }
+    },
+    "read-package-json": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz",
+      "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.1",
+        "graceful-fs": "^4.1.2",
+        "json-parse-better-errors": "^1.0.1",
+        "normalize-package-data": "^2.0.0",
+        "npm-normalize-package-bin": "^1.0.0"
+      }
+    },
+    "read-package-tree": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz",
+      "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==",
+      "dev": true,
+      "requires": {
+        "read-package-json": "^2.0.0",
+        "readdir-scoped-modules": "^1.0.0",
+        "util-promisify": "^2.1.0"
+      }
+    },
+    "read-pkg": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
+      "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+      "dev": true,
+      "requires": {
+        "load-json-file": "^2.0.0",
+        "normalize-package-data": "^2.3.2",
+        "path-type": "^2.0.0"
+      }
+    },
+    "read-pkg-up": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
+      "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+      "dev": true,
+      "requires": {
+        "find-up": "^2.0.0",
+        "read-pkg": "^2.0.0"
+      }
+    },
+    "readable-stream": {
+      "version": "2.3.7",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+      "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+      "dev": true,
+      "requires": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      },
+      "dependencies": {
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        }
+      }
+    },
+    "readdir-scoped-modules": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz",
+      "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==",
+      "dev": true,
+      "requires": {
+        "debuglog": "^1.0.1",
+        "dezalgo": "^1.0.0",
+        "graceful-fs": "^4.1.2",
+        "once": "^1.3.0"
+      }
+    },
+    "redent": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz",
+      "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=",
+      "dev": true,
+      "requires": {
+        "indent-string": "^3.0.0",
+        "strip-indent": "^2.0.0"
+      }
+    },
+    "regex-not": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+      "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^3.0.2",
+        "safe-regex": "^1.1.0"
+      }
+    },
+    "regexpp": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
+      "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
+      "dev": true
+    },
+    "repeat-element": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+      "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
+      "dev": true
+    },
+    "repeat-string": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+      "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+      "dev": true
+    },
+    "repeating": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+      "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+      "dev": true,
+      "requires": {
+        "is-finite": "^1.0.0"
+      }
+    },
+    "request": {
+      "version": "2.88.0",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+      "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
+      "dev": true,
+      "requires": {
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.8.0",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "form-data": "~2.3.2",
+        "har-validator": "~5.1.0",
+        "http-signature": "~1.2.0",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "~2.1.19",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.2",
+        "safe-buffer": "^5.1.2",
+        "tough-cookie": "~2.4.3",
+        "tunnel-agent": "^0.6.0",
+        "uuid": "^3.3.2"
+      }
+    },
+    "require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+      "dev": true
+    },
+    "require-main-filename": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+      "dev": true
+    },
+    "resolve": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz",
+      "integrity": "sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==",
+      "dev": true,
+      "requires": {
+        "path-parse": "^1.0.6"
+      }
+    },
+    "resolve-cwd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
+      "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+      "dev": true,
+      "requires": {
+        "resolve-from": "^3.0.0"
+      },
+      "dependencies": {
+        "resolve-from": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+          "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+          "dev": true
+        }
+      }
+    },
+    "resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true
+    },
+    "resolve-url": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+      "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+      "dev": true
+    },
+    "restore-cursor": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+      "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+      "dev": true,
+      "requires": {
+        "onetime": "^2.0.0",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "ret": {
+      "version": "0.1.15",
+      "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+      "dev": true
+    },
+    "retry": {
+      "version": "0.10.1",
+      "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
+      "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=",
+      "dev": true
+    },
+    "rimraf": {
+      "version": "2.6.3",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+      "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.3"
+      }
+    },
+    "run-async": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+      "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+      "dev": true,
+      "requires": {
+        "is-promise": "^2.1.0"
+      }
+    },
+    "run-node": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/run-node/-/run-node-1.0.0.tgz",
+      "integrity": "sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==",
+      "dev": true
+    },
+    "run-queue": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+      "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
+      "dev": true,
+      "requires": {
+        "aproba": "^1.1.1"
+      }
+    },
+    "rxjs": {
+      "version": "6.5.4",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
+      "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.9.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+      "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
+      "dev": true
+    },
+    "safe-regex": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+      "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+      "dev": true,
+      "requires": {
+        "ret": "~0.1.10"
+      }
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "dev": true
+    },
+    "semver": {
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+      "dev": true
+    },
+    "semver-compare": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+      "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
+      "dev": true
+    },
+    "set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+      "dev": true
+    },
+    "set-value": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+      "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^2.0.1",
+        "is-extendable": "^0.1.1",
+        "is-plain-object": "^2.0.3",
+        "split-string": "^3.0.1"
+      },
+      "dependencies": {
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        }
+      }
+    },
+    "shallow-clone": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+      "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+      "dev": true,
+      "requires": {
+        "kind-of": "^6.0.2"
+      }
+    },
+    "shebang-command": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "dev": true,
+      "requires": {
+        "shebang-regex": "^1.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "dev": true
+    },
+    "shell-quote": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
+      "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
+      "dev": true
+    },
+    "signal-exit": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+      "dev": true
+    },
+    "slash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+      "dev": true
+    },
+    "slice-ansi": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+      "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.0",
+        "astral-regex": "^1.0.0",
+        "is-fullwidth-code-point": "^2.0.0"
+      }
+    },
+    "slide": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
+      "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=",
+      "dev": true
+    },
+    "smart-buffer": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz",
+      "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==",
+      "dev": true
+    },
+    "snapdragon": {
+      "version": "0.8.2",
+      "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+      "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+      "dev": true,
+      "requires": {
+        "base": "^0.11.1",
+        "debug": "^2.2.0",
+        "define-property": "^0.2.5",
+        "extend-shallow": "^2.0.1",
+        "map-cache": "^0.2.2",
+        "source-map": "^0.5.6",
+        "source-map-resolve": "^0.5.0",
+        "use": "^3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        },
+        "extend-shallow": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+          "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+          "dev": true,
+          "requires": {
+            "is-extendable": "^0.1.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
+        }
+      }
+    },
+    "snapdragon-node": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+      "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+      "dev": true,
+      "requires": {
+        "define-property": "^1.0.0",
+        "isobject": "^3.0.0",
+        "snapdragon-util": "^3.0.1"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+          "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^1.0.0"
+          }
+        },
+        "is-accessor-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+          "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-data-descriptor": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+          "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+          "dev": true,
+          "requires": {
+            "kind-of": "^6.0.0"
+          }
+        },
+        "is-descriptor": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+          "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+          "dev": true,
+          "requires": {
+            "is-accessor-descriptor": "^1.0.0",
+            "is-data-descriptor": "^1.0.0",
+            "kind-of": "^6.0.2"
+          }
+        }
+      }
+    },
+    "snapdragon-util": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+      "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.2.0"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "socks": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz",
+      "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==",
+      "dev": true,
+      "requires": {
+        "ip": "1.1.5",
+        "smart-buffer": "^4.1.0"
+      }
+    },
+    "socks-proxy-agent": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz",
+      "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==",
+      "dev": true,
+      "requires": {
+        "agent-base": "~4.2.1",
+        "socks": "~2.3.2"
+      },
+      "dependencies": {
+        "agent-base": {
+          "version": "4.2.1",
+          "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
+          "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
+          "dev": true,
+          "requires": {
+            "es6-promisify": "^5.0.0"
+          }
+        }
+      }
+    },
+    "sort-keys": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz",
+      "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=",
+      "dev": true,
+      "requires": {
+        "is-plain-obj": "^1.0.0"
+      }
+    },
+    "source-map": {
+      "version": "0.5.7",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+      "dev": true
+    },
+    "source-map-resolve": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
+      "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
+      "dev": true,
+      "requires": {
+        "atob": "^2.1.2",
+        "decode-uri-component": "^0.2.0",
+        "resolve-url": "^0.2.1",
+        "source-map-url": "^0.4.0",
+        "urix": "^0.1.0"
+      }
+    },
+    "source-map-url": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+      "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+      "dev": true
+    },
+    "spdx-correct": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+      "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+      "dev": true,
+      "requires": {
+        "spdx-expression-parse": "^3.0.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-exceptions": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+      "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+      "dev": true
+    },
+    "spdx-expression-parse": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+      "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+      "dev": true,
+      "requires": {
+        "spdx-exceptions": "^2.1.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-license-ids": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+      "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
+      "dev": true
+    },
+    "split": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
+      "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
+      "dev": true,
+      "requires": {
+        "through": "2"
+      }
+    },
+    "split-string": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+      "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+      "dev": true,
+      "requires": {
+        "extend-shallow": "^3.0.0"
+      }
+    },
+    "split2": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz",
+      "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==",
+      "dev": true,
+      "requires": {
+        "through2": "^2.0.2"
+      }
+    },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+      "dev": true
+    },
+    "sshpk": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+      "dev": true,
+      "requires": {
+        "asn1": "~0.2.3",
+        "assert-plus": "^1.0.0",
+        "bcrypt-pbkdf": "^1.0.0",
+        "dashdash": "^1.12.0",
+        "ecc-jsbn": "~0.1.1",
+        "getpass": "^0.1.1",
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.0.2",
+        "tweetnacl": "~0.14.0"
+      }
+    },
+    "ssri": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
+      "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
+      "dev": true,
+      "requires": {
+        "figgy-pudding": "^3.5.1"
+      }
+    },
+    "static-extend": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+      "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+      "dev": true,
+      "requires": {
+        "define-property": "^0.2.5",
+        "object-copy": "^0.1.0"
+      },
+      "dependencies": {
+        "define-property": {
+          "version": "0.2.5",
+          "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+          "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+          "dev": true,
+          "requires": {
+            "is-descriptor": "^0.1.0"
+          }
+        }
+      }
+    },
+    "stream-each": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
+      "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
+      "dev": true,
+      "requires": {
+        "end-of-stream": "^1.1.0",
+        "stream-shift": "^1.0.0"
+      }
+    },
+    "stream-shift": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+      "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
+      "dev": true
+    },
+    "string-width": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+      "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+      "dev": true,
+      "requires": {
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^4.0.0"
+      },
+      "dependencies": {
+        "strip-ansi": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+          "dev": true,
+          "requires": {
+            "ansi-regex": "^3.0.0"
+          }
+        }
+      }
+    },
+    "string.prototype.padend": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz",
+      "integrity": "sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.17.0-next.1"
+      }
+    },
+    "string.prototype.trimleft": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz",
+      "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "function-bind": "^1.1.1"
+      }
+    },
+    "string.prototype.trimright": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz",
+      "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==",
+      "dev": true,
+      "requires": {
+        "define-properties": "^1.1.3",
+        "function-bind": "^1.1.1"
+      }
+    },
+    "string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      },
+      "dependencies": {
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true
+        }
+      }
+    },
+    "strip-ansi": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^4.1.0"
+      },
+      "dependencies": {
+        "ansi-regex": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+          "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+          "dev": true
+        }
+      }
+    },
+    "strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+      "dev": true
+    },
+    "strip-eof": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+      "dev": true
+    },
+    "strip-indent": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz",
+      "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=",
+      "dev": true
+    },
+    "strip-json-comments": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
+      "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
+      "dev": true
+    },
+    "strong-log-transformer": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz",
+      "integrity": "sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==",
+      "dev": true,
+      "requires": {
+        "duplexer": "^0.1.1",
+        "minimist": "^1.2.0",
+        "through": "^2.3.4"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.5",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+          "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+          "dev": true
+        }
+      }
+    },
+    "supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
+    "table": {
+      "version": "5.4.6",
+      "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
+      "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.10.2",
+        "lodash": "^4.17.14",
+        "slice-ansi": "^2.1.0",
+        "string-width": "^3.0.0"
+      },
+      "dependencies": {
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        }
+      }
+    },
+    "tar": {
+      "version": "4.4.13",
+      "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
+      "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
+      "dev": true,
+      "requires": {
+        "chownr": "^1.1.1",
+        "fs-minipass": "^1.2.5",
+        "minipass": "^2.8.6",
+        "minizlib": "^1.2.1",
+        "mkdirp": "^0.5.0",
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.3"
+      }
+    },
+    "temp-dir": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz",
+      "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=",
+      "dev": true
+    },
+    "temp-write": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/temp-write/-/temp-write-3.4.0.tgz",
+      "integrity": "sha1-jP9jD7fp2gXwR8dM5M5NaFRX1JI=",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "is-stream": "^1.1.0",
+        "make-dir": "^1.0.0",
+        "pify": "^3.0.0",
+        "temp-dir": "^1.0.0",
+        "uuid": "^3.0.1"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        }
+      }
+    },
+    "text-extensions": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz",
+      "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==",
+      "dev": true
+    },
+    "text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+      "dev": true
+    },
+    "thenify": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
+      "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=",
+      "dev": true,
+      "requires": {
+        "any-promise": "^1.0.0"
+      }
+    },
+    "thenify-all": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+      "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=",
+      "dev": true,
+      "requires": {
+        "thenify": ">= 3.1.0 < 4"
+      }
+    },
+    "through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+      "dev": true
+    },
+    "through2": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+      "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+      "dev": true,
+      "requires": {
+        "readable-stream": "~2.3.6",
+        "xtend": "~4.0.1"
+      }
+    },
+    "tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dev": true,
+      "requires": {
+        "os-tmpdir": "~1.0.2"
+      }
+    },
+    "to-object-path": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+      "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+      "dev": true,
+      "requires": {
+        "kind-of": "^3.0.2"
+      },
+      "dependencies": {
+        "kind-of": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+          "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+          "dev": true,
+          "requires": {
+            "is-buffer": "^1.1.5"
+          }
+        }
+      }
+    },
+    "to-regex": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+      "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+      "dev": true,
+      "requires": {
+        "define-property": "^2.0.2",
+        "extend-shallow": "^3.0.2",
+        "regex-not": "^1.0.2",
+        "safe-regex": "^1.1.0"
+      }
+    },
+    "to-regex-range": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+      "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+      "dev": true,
+      "requires": {
+        "is-number": "^3.0.0",
+        "repeat-string": "^1.6.1"
+      }
+    },
+    "tough-cookie": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+      "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
+      "dev": true,
+      "requires": {
+        "psl": "^1.1.24",
+        "punycode": "^1.4.1"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+          "dev": true
+        }
+      }
+    },
+    "tr46": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+      "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
+      "dev": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "trim-newlines": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz",
+      "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=",
+      "dev": true
+    },
+    "trim-off-newlines": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz",
+      "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=",
+      "dev": true
+    },
+    "tslib": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
+      "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
+      "dev": true
+    },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "tweetnacl": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+      "dev": true
+    },
+    "type-check": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "~1.1.2"
+      }
+    },
+    "type-fest": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+      "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+      "dev": true
+    },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+      "dev": true
+    },
+    "uglify-js": {
+      "version": "3.9.1",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.1.tgz",
+      "integrity": "sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA==",
+      "dev": true,
+      "optional": true,
+      "requires": {
+        "commander": "~2.20.3"
+      }
+    },
+    "uid-number": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
+      "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=",
+      "dev": true
+    },
+    "umask": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz",
+      "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=",
+      "dev": true
+    },
+    "union-value": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+      "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
+      "dev": true,
+      "requires": {
+        "arr-union": "^3.1.0",
+        "get-value": "^2.0.6",
+        "is-extendable": "^0.1.1",
+        "set-value": "^2.0.1"
+      }
+    },
+    "unique-filename": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
+      "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
+      "dev": true,
+      "requires": {
+        "unique-slug": "^2.0.0"
+      }
+    },
+    "unique-slug": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
+      "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
+      "dev": true,
+      "requires": {
+        "imurmurhash": "^0.1.4"
+      }
+    },
+    "universal-user-agent": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.0.tgz",
+      "integrity": "sha512-eM8knLpev67iBDizr/YtqkJsF3GK8gzDc6st/WKzrTuPtcsOKW/0IdL4cnMBsU69pOx0otavLWBDGTwg+dB0aA==",
+      "dev": true,
+      "requires": {
+        "os-name": "^3.1.0"
+      }
+    },
+    "universalify": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+      "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+      "dev": true
+    },
+    "unset-value": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+      "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+      "dev": true,
+      "requires": {
+        "has-value": "^0.3.1",
+        "isobject": "^3.0.0"
+      },
+      "dependencies": {
+        "has-value": {
+          "version": "0.3.1",
+          "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+          "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+          "dev": true,
+          "requires": {
+            "get-value": "^2.0.3",
+            "has-values": "^0.1.4",
+            "isobject": "^2.0.0"
+          },
+          "dependencies": {
+            "isobject": {
+              "version": "2.1.0",
+              "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+              "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+              "dev": true,
+              "requires": {
+                "isarray": "1.0.0"
+              }
+            }
+          }
+        },
+        "has-values": {
+          "version": "0.1.4",
+          "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+          "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+          "dev": true
+        }
+      }
+    },
+    "upath": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
+      "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
+      "dev": true
+    },
+    "uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "dev": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "urix": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+      "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+      "dev": true
+    },
+    "use": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+      "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
+      "dev": true
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+      "dev": true
+    },
+    "util-promisify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz",
+      "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=",
+      "dev": true,
+      "requires": {
+        "object.getownpropertydescriptors": "^2.0.3"
+      }
+    },
+    "uuid": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+      "dev": true
+    },
+    "v8-compile-cache": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
+      "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
+      "dev": true
+    },
+    "validate-npm-package-license": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
+      "requires": {
+        "spdx-correct": "^3.0.0",
+        "spdx-expression-parse": "^3.0.0"
+      }
+    },
+    "validate-npm-package-name": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
+      "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
+      "dev": true,
+      "requires": {
+        "builtins": "^1.0.3"
+      }
+    },
+    "verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "dev": true,
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    },
+    "wcwidth": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+      "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
+      "dev": true,
+      "requires": {
+        "defaults": "^1.0.3"
+      }
+    },
+    "webidl-conversions": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+      "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+      "dev": true
+    },
+    "whatwg-url": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
+      "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
+      "dev": true,
+      "requires": {
+        "lodash.sortby": "^4.7.0",
+        "tr46": "^1.0.1",
+        "webidl-conversions": "^4.0.2"
+      }
+    },
+    "which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "which-module": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+      "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+      "dev": true
+    },
+    "wide-align": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+      "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+      "dev": true,
+      "requires": {
+        "string-width": "^1.0.2 || 2"
+      }
+    },
+    "windows-release": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz",
+      "integrity": "sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==",
+      "dev": true,
+      "requires": {
+        "execa": "^1.0.0"
+      }
+    },
+    "word-wrap": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+      "dev": true
+    },
+    "wordwrap": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+      "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+      "dev": true
+    },
+    "wrap-ansi": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+      "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.0",
+        "string-width": "^3.0.0",
+        "strip-ansi": "^5.0.0"
+      },
+      "dependencies": {
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        }
+      }
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+      "dev": true
+    },
+    "write": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+      "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+      "dev": true,
+      "requires": {
+        "mkdirp": "^0.5.1"
+      }
+    },
+    "write-file-atomic": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
+      "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.11",
+        "imurmurhash": "^0.1.4",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "write-json-file": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-3.2.0.tgz",
+      "integrity": "sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==",
+      "dev": true,
+      "requires": {
+        "detect-indent": "^5.0.0",
+        "graceful-fs": "^4.1.15",
+        "make-dir": "^2.1.0",
+        "pify": "^4.0.1",
+        "sort-keys": "^2.0.0",
+        "write-file-atomic": "^2.4.2"
+      },
+      "dependencies": {
+        "make-dir": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+          "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+          "dev": true,
+          "requires": {
+            "pify": "^4.0.1",
+            "semver": "^5.6.0"
+          }
+        },
+        "pify": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+          "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+          "dev": true
+        },
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "write-pkg": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-3.2.0.tgz",
+      "integrity": "sha512-tX2ifZ0YqEFOF1wjRW2Pk93NLsj02+n1UP5RvO6rCs0K6R2g1padvf006cY74PQJKMGS2r42NK7FD0dG6Y6paw==",
+      "dev": true,
+      "requires": {
+        "sort-keys": "^2.0.0",
+        "write-json-file": "^2.2.0"
+      },
+      "dependencies": {
+        "pify": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+          "dev": true
+        },
+        "write-json-file": {
+          "version": "2.3.0",
+          "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-2.3.0.tgz",
+          "integrity": "sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=",
+          "dev": true,
+          "requires": {
+            "detect-indent": "^5.0.0",
+            "graceful-fs": "^4.1.2",
+            "make-dir": "^1.0.0",
+            "pify": "^3.0.0",
+            "sort-keys": "^2.0.0",
+            "write-file-atomic": "^2.0.0"
+          }
+        }
+      }
+    },
+    "xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "dev": true
+    },
+    "y18n": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+      "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+      "dev": true
+    },
+    "yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+      "dev": true
+    },
+    "yargs": {
+      "version": "14.2.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.2.tgz",
+      "integrity": "sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA==",
+      "dev": true,
+      "requires": {
+        "cliui": "^5.0.0",
+        "decamelize": "^1.2.0",
+        "find-up": "^3.0.0",
+        "get-caller-file": "^2.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^2.0.0",
+        "set-blocking": "^2.0.0",
+        "string-width": "^3.0.0",
+        "which-module": "^2.0.0",
+        "y18n": "^4.0.0",
+        "yargs-parser": "^15.0.0"
+      },
+      "dependencies": {
+        "find-up": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+          "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+          "dev": true,
+          "requires": {
+            "locate-path": "^3.0.0"
+          }
+        },
+        "locate-path": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+          "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+          "dev": true,
+          "requires": {
+            "p-locate": "^3.0.0",
+            "path-exists": "^3.0.0"
+          }
+        },
+        "p-limit": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz",
+          "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==",
+          "dev": true,
+          "requires": {
+            "p-try": "^2.0.0"
+          }
+        },
+        "p-locate": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+          "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+          "dev": true,
+          "requires": {
+            "p-limit": "^2.0.0"
+          }
+        },
+        "p-try": {
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+          "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+          "dev": true
+        },
+        "string-width": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+          "dev": true,
+          "requires": {
+            "emoji-regex": "^7.0.1",
+            "is-fullwidth-code-point": "^2.0.0",
+            "strip-ansi": "^5.1.0"
+          }
+        }
+      }
+    },
+    "yargs-parser": {
+      "version": "15.0.0",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.0.tgz",
+      "integrity": "sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ==",
+      "dev": true,
+      "requires": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      }
+    }
+  }
+}

+ 3 - 3
package.json

@@ -7,11 +7,11 @@
   "license": "GPL-3.0",
   "repository": "https://github.com/Musare/MusareNode",
   "scripts": {
-    "postinstall": "yarn run bootstrap",
+    "postinstall": "npm run bootstrap",
     "bootstrap": "npx lerna bootstrap --ci",
     "dev": "npx npm-run-all -p dev:*",
-    "dev:frontend": "cd ./frontend && yarn dev",
-    "dev:backend": "cd ./backend && yarn dev"
+    "dev:frontend": "cd ./frontend && npm dev",
+    "dev:backend": "cd ./backend && npm dev"
   },
   "devDependencies": {
     "eslint": "6.1.0",

+ 1 - 1
windows-start.cmd

@@ -1,7 +1,7 @@
 start "Redis" "startRedis.cmd"
 start "Mongo" "startMongo.cmd"
 cd frontend
-start "Frontend" yarn run dev
+start "Frontend" npm run dev
 cd ..
 SLEEP 20
 cd backend

+ 0 - 11035
yarn.lock

@@ -1,11035 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d"
-  integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==
-  dependencies:
-    "@babel/highlight" "^7.0.0"
-
-"@babel/core@^7.5.4":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.5.tgz#17b2686ef0d6bc58f963dddd68ab669755582c30"
-  integrity sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==
-  dependencies:
-    "@babel/code-frame" "^7.5.5"
-    "@babel/generator" "^7.5.5"
-    "@babel/helpers" "^7.5.5"
-    "@babel/parser" "^7.5.5"
-    "@babel/template" "^7.4.4"
-    "@babel/traverse" "^7.5.5"
-    "@babel/types" "^7.5.5"
-    convert-source-map "^1.1.0"
-    debug "^4.1.0"
-    json5 "^2.1.0"
-    lodash "^4.17.13"
-    resolve "^1.3.2"
-    semver "^5.4.1"
-    source-map "^0.5.0"
-
-"@babel/generator@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.5.5.tgz#873a7f936a3c89491b43536d12245b626664e3cf"
-  integrity sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==
-  dependencies:
-    "@babel/types" "^7.5.5"
-    jsesc "^2.5.1"
-    lodash "^4.17.13"
-    source-map "^0.5.0"
-    trim-right "^1.0.1"
-
-"@babel/helper-annotate-as-pure@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32"
-  integrity sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==
-  dependencies:
-    "@babel/types" "^7.0.0"
-
-"@babel/helper-builder-binary-assignment-operator-visitor@^7.1.0":
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz#6b69628dfe4087798e0c4ed98e3d4a6b2fbd2f5f"
-  integrity sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==
-  dependencies:
-    "@babel/helper-explode-assignable-expression" "^7.1.0"
-    "@babel/types" "^7.0.0"
-
-"@babel/helper-call-delegate@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz#87c1f8ca19ad552a736a7a27b1c1fcf8b1ff1f43"
-  integrity sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==
-  dependencies:
-    "@babel/helper-hoist-variables" "^7.4.4"
-    "@babel/traverse" "^7.4.4"
-    "@babel/types" "^7.4.4"
-
-"@babel/helper-define-map@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz#3dec32c2046f37e09b28c93eb0b103fd2a25d369"
-  integrity sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==
-  dependencies:
-    "@babel/helper-function-name" "^7.1.0"
-    "@babel/types" "^7.5.5"
-    lodash "^4.17.13"
-
-"@babel/helper-explode-assignable-expression@^7.1.0":
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz#537fa13f6f1674df745b0c00ec8fe4e99681c8f6"
-  integrity sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==
-  dependencies:
-    "@babel/traverse" "^7.1.0"
-    "@babel/types" "^7.0.0"
-
-"@babel/helper-function-name@^7.1.0":
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53"
-  integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==
-  dependencies:
-    "@babel/helper-get-function-arity" "^7.0.0"
-    "@babel/template" "^7.1.0"
-    "@babel/types" "^7.0.0"
-
-"@babel/helper-get-function-arity@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3"
-  integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==
-  dependencies:
-    "@babel/types" "^7.0.0"
-
-"@babel/helper-hoist-variables@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz#0298b5f25c8c09c53102d52ac4a98f773eb2850a"
-  integrity sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==
-  dependencies:
-    "@babel/types" "^7.4.4"
-
-"@babel/helper-member-expression-to-functions@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz#1fb5b8ec4453a93c439ee9fe3aeea4a84b76b590"
-  integrity sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==
-  dependencies:
-    "@babel/types" "^7.5.5"
-
-"@babel/helper-module-imports@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d"
-  integrity sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==
-  dependencies:
-    "@babel/types" "^7.0.0"
-
-"@babel/helper-module-transforms@^7.1.0", "@babel/helper-module-transforms@^7.4.4":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz#f84ff8a09038dcbca1fd4355661a500937165b4a"
-  integrity sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==
-  dependencies:
-    "@babel/helper-module-imports" "^7.0.0"
-    "@babel/helper-simple-access" "^7.1.0"
-    "@babel/helper-split-export-declaration" "^7.4.4"
-    "@babel/template" "^7.4.4"
-    "@babel/types" "^7.5.5"
-    lodash "^4.17.13"
-
-"@babel/helper-optimise-call-expression@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5"
-  integrity sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==
-  dependencies:
-    "@babel/types" "^7.0.0"
-
-"@babel/helper-plugin-utils@^7.0.0":
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250"
-  integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==
-
-"@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.5.5.tgz#0aa6824f7100a2e0e89c1527c23936c152cab351"
-  integrity sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==
-  dependencies:
-    lodash "^4.17.13"
-
-"@babel/helper-remap-async-to-generator@^7.1.0":
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz#361d80821b6f38da75bd3f0785ece20a88c5fe7f"
-  integrity sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==
-  dependencies:
-    "@babel/helper-annotate-as-pure" "^7.0.0"
-    "@babel/helper-wrap-function" "^7.1.0"
-    "@babel/template" "^7.1.0"
-    "@babel/traverse" "^7.1.0"
-    "@babel/types" "^7.0.0"
-
-"@babel/helper-replace-supers@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz#f84ce43df031222d2bad068d2626cb5799c34bc2"
-  integrity sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==
-  dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.5.5"
-    "@babel/helper-optimise-call-expression" "^7.0.0"
-    "@babel/traverse" "^7.5.5"
-    "@babel/types" "^7.5.5"
-
-"@babel/helper-simple-access@^7.1.0":
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c"
-  integrity sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==
-  dependencies:
-    "@babel/template" "^7.1.0"
-    "@babel/types" "^7.0.0"
-
-"@babel/helper-split-export-declaration@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677"
-  integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==
-  dependencies:
-    "@babel/types" "^7.4.4"
-
-"@babel/helper-wrap-function@^7.1.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz#c4e0012445769e2815b55296ead43a958549f6fa"
-  integrity sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==
-  dependencies:
-    "@babel/helper-function-name" "^7.1.0"
-    "@babel/template" "^7.1.0"
-    "@babel/traverse" "^7.1.0"
-    "@babel/types" "^7.2.0"
-
-"@babel/helpers@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.5.5.tgz#63908d2a73942229d1e6685bc2a0e730dde3b75e"
-  integrity sha512-nRq2BUhxZFnfEn/ciJuhklHvFOqjJUD5wpx+1bxUF2axL9C+v4DE/dmp5sT2dKnpOs4orZWzpAZqlCy8QqE/7g==
-  dependencies:
-    "@babel/template" "^7.4.4"
-    "@babel/traverse" "^7.5.5"
-    "@babel/types" "^7.5.5"
-
-"@babel/highlight@^7.0.0":
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540"
-  integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==
-  dependencies:
-    chalk "^2.0.0"
-    esutils "^2.0.2"
-    js-tokens "^4.0.0"
-
-"@babel/parser@^7.0.0", "@babel/parser@^7.4.4", "@babel/parser@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b"
-  integrity sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==
-
-"@babel/plugin-proposal-async-generator-functions@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e"
-  integrity sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-remap-async-to-generator" "^7.1.0"
-    "@babel/plugin-syntax-async-generators" "^7.2.0"
-
-"@babel/plugin-proposal-dynamic-import@^7.5.0":
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz#e532202db4838723691b10a67b8ce509e397c506"
-  integrity sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/plugin-syntax-dynamic-import" "^7.2.0"
-
-"@babel/plugin-proposal-json-strings@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317"
-  integrity sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/plugin-syntax-json-strings" "^7.2.0"
-
-"@babel/plugin-proposal-object-rest-spread@^7.5.4", "@babel/plugin-proposal-object-rest-spread@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz#61939744f71ba76a3ae46b5eea18a54c16d22e58"
-  integrity sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/plugin-syntax-object-rest-spread" "^7.2.0"
-
-"@babel/plugin-proposal-optional-catch-binding@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5"
-  integrity sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
-
-"@babel/plugin-proposal-unicode-property-regex@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz#501ffd9826c0b91da22690720722ac7cb1ca9c78"
-  integrity sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-regex" "^7.4.4"
-    regexpu-core "^4.5.4"
-
-"@babel/plugin-syntax-async-generators@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f"
-  integrity sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-syntax-dynamic-import@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612"
-  integrity sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-syntax-json-strings@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470"
-  integrity sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-syntax-object-rest-spread@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e"
-  integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-syntax-optional-catch-binding@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz#a94013d6eda8908dfe6a477e7f9eda85656ecf5c"
-  integrity sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-arrow-functions@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550"
-  integrity sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-async-to-generator@^7.5.0":
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz#89a3848a0166623b5bc481164b5936ab947e887e"
-  integrity sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==
-  dependencies:
-    "@babel/helper-module-imports" "^7.0.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-remap-async-to-generator" "^7.1.0"
-
-"@babel/plugin-transform-block-scoped-functions@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz#5d3cc11e8d5ddd752aa64c9148d0db6cb79fd190"
-  integrity sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-block-scoping@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz#a35f395e5402822f10d2119f6f8e045e3639a2ce"
-  integrity sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    lodash "^4.17.13"
-
-"@babel/plugin-transform-classes@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz#d094299d9bd680a14a2a0edae38305ad60fb4de9"
-  integrity sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==
-  dependencies:
-    "@babel/helper-annotate-as-pure" "^7.0.0"
-    "@babel/helper-define-map" "^7.5.5"
-    "@babel/helper-function-name" "^7.1.0"
-    "@babel/helper-optimise-call-expression" "^7.0.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-replace-supers" "^7.5.5"
-    "@babel/helper-split-export-declaration" "^7.4.4"
-    globals "^11.1.0"
-
-"@babel/plugin-transform-computed-properties@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz#83a7df6a658865b1c8f641d510c6f3af220216da"
-  integrity sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-destructuring@^7.5.0":
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.5.0.tgz#f6c09fdfe3f94516ff074fe877db7bc9ef05855a"
-  integrity sha512-YbYgbd3TryYYLGyC7ZR+Tq8H/+bCmwoaxHfJHupom5ECstzbRLTch6gOQbhEY9Z4hiCNHEURgq06ykFv9JZ/QQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-dotall-regex@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz#361a148bc951444312c69446d76ed1ea8e4450c3"
-  integrity sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-regex" "^7.4.4"
-    regexpu-core "^4.5.4"
-
-"@babel/plugin-transform-duplicate-keys@^7.5.0":
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz#c5dbf5106bf84cdf691222c0974c12b1df931853"
-  integrity sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-exponentiation-operator@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz#a63868289e5b4007f7054d46491af51435766008"
-  integrity sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==
-  dependencies:
-    "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-for-of@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz#0267fc735e24c808ba173866c6c4d1440fc3c556"
-  integrity sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-function-name@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz#e1436116abb0610c2259094848754ac5230922ad"
-  integrity sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==
-  dependencies:
-    "@babel/helper-function-name" "^7.1.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-literals@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz#690353e81f9267dad4fd8cfd77eafa86aba53ea1"
-  integrity sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-member-expression-literals@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz#fa10aa5c58a2cb6afcf2c9ffa8cb4d8b3d489a2d"
-  integrity sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-modules-amd@^7.5.0":
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz#ef00435d46da0a5961aa728a1d2ecff063e4fb91"
-  integrity sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==
-  dependencies:
-    "@babel/helper-module-transforms" "^7.1.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    babel-plugin-dynamic-import-node "^2.3.0"
-
-"@babel/plugin-transform-modules-commonjs@^7.5.0":
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.5.0.tgz#425127e6045231360858eeaa47a71d75eded7a74"
-  integrity sha512-xmHq0B+ytyrWJvQTc5OWAC4ii6Dhr0s22STOoydokG51JjWhyYo5mRPXoi+ZmtHQhZZwuXNN+GG5jy5UZZJxIQ==
-  dependencies:
-    "@babel/helper-module-transforms" "^7.4.4"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-simple-access" "^7.1.0"
-    babel-plugin-dynamic-import-node "^2.3.0"
-
-"@babel/plugin-transform-modules-systemjs@^7.5.0":
-  version "7.5.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz#e75266a13ef94202db2a0620977756f51d52d249"
-  integrity sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==
-  dependencies:
-    "@babel/helper-hoist-variables" "^7.4.4"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    babel-plugin-dynamic-import-node "^2.3.0"
-
-"@babel/plugin-transform-modules-umd@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz#7678ce75169f0877b8eb2235538c074268dd01ae"
-  integrity sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==
-  dependencies:
-    "@babel/helper-module-transforms" "^7.1.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-named-capturing-groups-regex@^7.4.5":
-  version "7.4.5"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz#9d269fd28a370258199b4294736813a60bbdd106"
-  integrity sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==
-  dependencies:
-    regexp-tree "^0.1.6"
-
-"@babel/plugin-transform-new-target@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz#18d120438b0cc9ee95a47f2c72bc9768fbed60a5"
-  integrity sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-object-super@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz#c70021df834073c65eb613b8679cc4a381d1a9f9"
-  integrity sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-replace-supers" "^7.5.5"
-
-"@babel/plugin-transform-parameters@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz#7556cf03f318bd2719fe4c922d2d808be5571e16"
-  integrity sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==
-  dependencies:
-    "@babel/helper-call-delegate" "^7.4.4"
-    "@babel/helper-get-function-arity" "^7.0.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-property-literals@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz#03e33f653f5b25c4eb572c98b9485055b389e905"
-  integrity sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-regenerator@^7.4.5":
-  version "7.4.5"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz#629dc82512c55cee01341fb27bdfcb210354680f"
-  integrity sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==
-  dependencies:
-    regenerator-transform "^0.14.0"
-
-"@babel/plugin-transform-reserved-words@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz#4792af87c998a49367597d07fedf02636d2e1634"
-  integrity sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-runtime@^7.5.0":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.5.5.tgz#a6331afbfc59189d2135b2e09474457a8e3d28bc"
-  integrity sha512-6Xmeidsun5rkwnGfMOp6/z9nSzWpHFNVr2Jx7kwoq4mVatQfQx5S56drBgEHF+XQbKOdIaOiMIINvp/kAwMN+w==
-  dependencies:
-    "@babel/helper-module-imports" "^7.0.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    resolve "^1.8.1"
-    semver "^5.5.1"
-
-"@babel/plugin-transform-shorthand-properties@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz#6333aee2f8d6ee7e28615457298934a3b46198f0"
-  integrity sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-spread@^7.2.0":
-  version "7.2.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz#3103a9abe22f742b6d406ecd3cd49b774919b406"
-  integrity sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-sticky-regex@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz#a1e454b5995560a9c1e0d537dfc15061fd2687e1"
-  integrity sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-regex" "^7.0.0"
-
-"@babel/plugin-transform-template-literals@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz#9d28fea7bbce637fb7612a0750989d8321d4bcb0"
-  integrity sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==
-  dependencies:
-    "@babel/helper-annotate-as-pure" "^7.0.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-typeof-symbol@^7.2.0":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz#117d2bcec2fbf64b4b59d1f9819894682d29f2b2"
-  integrity sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-
-"@babel/plugin-transform-unicode-regex@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz#ab4634bb4f14d36728bf5978322b35587787970f"
-  integrity sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/helper-regex" "^7.4.4"
-    regexpu-core "^4.5.4"
-
-"@babel/preset-env@^7.5.4":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.5.5.tgz#bc470b53acaa48df4b8db24a570d6da1fef53c9a"
-  integrity sha512-GMZQka/+INwsMz1A5UEql8tG015h5j/qjptpKY2gJ7giy8ohzU710YciJB5rcKsWGWHiW3RUnHib0E5/m3Tp3A==
-  dependencies:
-    "@babel/helper-module-imports" "^7.0.0"
-    "@babel/helper-plugin-utils" "^7.0.0"
-    "@babel/plugin-proposal-async-generator-functions" "^7.2.0"
-    "@babel/plugin-proposal-dynamic-import" "^7.5.0"
-    "@babel/plugin-proposal-json-strings" "^7.2.0"
-    "@babel/plugin-proposal-object-rest-spread" "^7.5.5"
-    "@babel/plugin-proposal-optional-catch-binding" "^7.2.0"
-    "@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
-    "@babel/plugin-syntax-async-generators" "^7.2.0"
-    "@babel/plugin-syntax-dynamic-import" "^7.2.0"
-    "@babel/plugin-syntax-json-strings" "^7.2.0"
-    "@babel/plugin-syntax-object-rest-spread" "^7.2.0"
-    "@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
-    "@babel/plugin-transform-arrow-functions" "^7.2.0"
-    "@babel/plugin-transform-async-to-generator" "^7.5.0"
-    "@babel/plugin-transform-block-scoped-functions" "^7.2.0"
-    "@babel/plugin-transform-block-scoping" "^7.5.5"
-    "@babel/plugin-transform-classes" "^7.5.5"
-    "@babel/plugin-transform-computed-properties" "^7.2.0"
-    "@babel/plugin-transform-destructuring" "^7.5.0"
-    "@babel/plugin-transform-dotall-regex" "^7.4.4"
-    "@babel/plugin-transform-duplicate-keys" "^7.5.0"
-    "@babel/plugin-transform-exponentiation-operator" "^7.2.0"
-    "@babel/plugin-transform-for-of" "^7.4.4"
-    "@babel/plugin-transform-function-name" "^7.4.4"
-    "@babel/plugin-transform-literals" "^7.2.0"
-    "@babel/plugin-transform-member-expression-literals" "^7.2.0"
-    "@babel/plugin-transform-modules-amd" "^7.5.0"
-    "@babel/plugin-transform-modules-commonjs" "^7.5.0"
-    "@babel/plugin-transform-modules-systemjs" "^7.5.0"
-    "@babel/plugin-transform-modules-umd" "^7.2.0"
-    "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.5"
-    "@babel/plugin-transform-new-target" "^7.4.4"
-    "@babel/plugin-transform-object-super" "^7.5.5"
-    "@babel/plugin-transform-parameters" "^7.4.4"
-    "@babel/plugin-transform-property-literals" "^7.2.0"
-    "@babel/plugin-transform-regenerator" "^7.4.5"
-    "@babel/plugin-transform-reserved-words" "^7.2.0"
-    "@babel/plugin-transform-shorthand-properties" "^7.2.0"
-    "@babel/plugin-transform-spread" "^7.2.0"
-    "@babel/plugin-transform-sticky-regex" "^7.2.0"
-    "@babel/plugin-transform-template-literals" "^7.4.4"
-    "@babel/plugin-transform-typeof-symbol" "^7.2.0"
-    "@babel/plugin-transform-unicode-regex" "^7.4.4"
-    "@babel/types" "^7.5.5"
-    browserslist "^4.6.0"
-    core-js-compat "^3.1.1"
-    invariant "^2.2.2"
-    js-levenshtein "^1.1.3"
-    semver "^5.5.0"
-
-"@babel/runtime@^7.5.4":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
-  integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
-  dependencies:
-    regenerator-runtime "^0.13.2"
-
-"@babel/template@^7.1.0", "@babel/template@^7.4.4":
-  version "7.4.4"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237"
-  integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==
-  dependencies:
-    "@babel/code-frame" "^7.0.0"
-    "@babel/parser" "^7.4.4"
-    "@babel/types" "^7.4.4"
-
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.5.5.tgz#f664f8f368ed32988cd648da9f72d5ca70f165bb"
-  integrity sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==
-  dependencies:
-    "@babel/code-frame" "^7.5.5"
-    "@babel/generator" "^7.5.5"
-    "@babel/helper-function-name" "^7.1.0"
-    "@babel/helper-split-export-declaration" "^7.4.4"
-    "@babel/parser" "^7.5.5"
-    "@babel/types" "^7.5.5"
-    debug "^4.1.0"
-    globals "^11.1.0"
-    lodash "^4.17.13"
-
-"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5":
-  version "7.5.5"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a"
-  integrity sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==
-  dependencies:
-    esutils "^2.0.2"
-    lodash "^4.17.13"
-    to-fast-properties "^2.0.0"
-
-"@evocateur/libnpmaccess@^3.1.2":
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845"
-  integrity sha512-KSCAHwNWro0CF2ukxufCitT9K5LjL/KuMmNzSu8wuwN2rjyKHD8+cmOsiybK+W5hdnwc5M1SmRlVCaMHQo+3rg==
-  dependencies:
-    "@evocateur/npm-registry-fetch" "^4.0.0"
-    aproba "^2.0.0"
-    figgy-pudding "^3.5.1"
-    get-stream "^4.0.0"
-    npm-package-arg "^6.1.0"
-
-"@evocateur/libnpmpublish@^1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@evocateur/libnpmpublish/-/libnpmpublish-1.2.2.tgz#55df09d2dca136afba9c88c759ca272198db9f1a"
-  integrity sha512-MJrrk9ct1FeY9zRlyeoyMieBjGDG9ihyyD9/Ft6MMrTxql9NyoEx2hw9casTIP4CdqEVu+3nQ2nXxoJ8RCXyFg==
-  dependencies:
-    "@evocateur/npm-registry-fetch" "^4.0.0"
-    aproba "^2.0.0"
-    figgy-pudding "^3.5.1"
-    get-stream "^4.0.0"
-    lodash.clonedeep "^4.5.0"
-    normalize-package-data "^2.4.0"
-    npm-package-arg "^6.1.0"
-    semver "^5.5.1"
-    ssri "^6.0.1"
-
-"@evocateur/npm-registry-fetch@^4.0.0":
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/@evocateur/npm-registry-fetch/-/npm-registry-fetch-4.0.0.tgz#8c4c38766d8d32d3200fcb0a83f064b57365ed66"
-  integrity sha512-k1WGfKRQyhJpIr+P17O5vLIo2ko1PFLKwoetatdduUSt/aQ4J2sJrJwwatdI5Z3SiYk/mRH9S3JpdmMFd/IK4g==
-  dependencies:
-    JSONStream "^1.3.4"
-    bluebird "^3.5.1"
-    figgy-pudding "^3.4.1"
-    lru-cache "^5.1.1"
-    make-fetch-happen "^5.0.0"
-    npm-package-arg "^6.1.0"
-    safe-buffer "^5.1.2"
-
-"@evocateur/pacote@^9.6.3":
-  version "9.6.3"
-  resolved "https://registry.yarnpkg.com/@evocateur/pacote/-/pacote-9.6.3.tgz#bcd7adbd3c2ef303aa89bd24166f06dd9c080d89"
-  integrity sha512-ExqNqcbdHQprEgKnY/uQz7WRtyHRbQxRl4JnVkSkmtF8qffRrF9K+piZKNLNSkRMOT/3H0e3IP44QVCHaXMWOQ==
-  dependencies:
-    "@evocateur/npm-registry-fetch" "^4.0.0"
-    bluebird "^3.5.3"
-    cacache "^12.0.0"
-    figgy-pudding "^3.5.1"
-    get-stream "^4.1.0"
-    glob "^7.1.4"
-    lru-cache "^5.1.1"
-    make-fetch-happen "^5.0.0"
-    minimatch "^3.0.4"
-    minipass "^2.3.5"
-    mississippi "^3.0.0"
-    mkdirp "^0.5.1"
-    normalize-package-data "^2.5.0"
-    npm-package-arg "^6.1.0"
-    npm-packlist "^1.4.4"
-    npm-pick-manifest "^2.2.3"
-    osenv "^0.1.5"
-    promise-inflight "^1.0.1"
-    promise-retry "^1.1.1"
-    protoduck "^5.0.1"
-    rimraf "^2.6.3"
-    safe-buffer "^5.2.0"
-    semver "^5.7.0"
-    ssri "^6.0.1"
-    tar "^4.4.10"
-    unique-filename "^1.1.1"
-    which "^1.3.1"
-
-"@lerna/add@3.16.2":
-  version "3.16.2"
-  resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.16.2.tgz#90ecc1be7051cfcec75496ce122f656295bd6e94"
-  integrity sha512-RAAaF8aODPogj2Ge9Wj3uxPFIBGpog9M+HwSuq03ZnkkO831AmasCTJDqV+GEpl1U2DvnhZQEwHpWmTT0uUeEw==
-  dependencies:
-    "@evocateur/pacote" "^9.6.3"
-    "@lerna/bootstrap" "3.16.2"
-    "@lerna/command" "3.16.0"
-    "@lerna/filter-options" "3.16.0"
-    "@lerna/npm-conf" "3.16.0"
-    "@lerna/validation-error" "3.13.0"
-    dedent "^0.7.0"
-    npm-package-arg "^6.1.0"
-    p-map "^2.1.0"
-    semver "^6.2.0"
-
-"@lerna/batch-packages@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/batch-packages/-/batch-packages-3.16.0.tgz#1c16cb697e7d718177db744cbcbdac4e30253c8c"
-  integrity sha512-7AdMkANpubY/FKFI01im01tlx6ygOBJ/0JcixMUWoWP/7Ds3SWQF22ID6fbBr38jUWptYLDs2fagtTDL7YUPuA==
-  dependencies:
-    "@lerna/package-graph" "3.16.0"
-    npmlog "^4.1.2"
-
-"@lerna/bootstrap@3.16.2":
-  version "3.16.2"
-  resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-3.16.2.tgz#be268d940221d3c3270656b9b791b492559ad9d8"
-  integrity sha512-I+gs7eh6rv9Vyd+CwqL7sftRfOOsSzCle8cv/CGlMN7/p7EAVhxEdAw8SYoHIKHzipXszuqqy1Y3opyleD0qdA==
-  dependencies:
-    "@lerna/batch-packages" "3.16.0"
-    "@lerna/command" "3.16.0"
-    "@lerna/filter-options" "3.16.0"
-    "@lerna/has-npm-version" "3.16.0"
-    "@lerna/npm-install" "3.16.0"
-    "@lerna/package-graph" "3.16.0"
-    "@lerna/pulse-till-done" "3.13.0"
-    "@lerna/rimraf-dir" "3.14.2"
-    "@lerna/run-lifecycle" "3.16.2"
-    "@lerna/run-parallel-batches" "3.16.0"
-    "@lerna/symlink-binary" "3.16.2"
-    "@lerna/symlink-dependencies" "3.16.2"
-    "@lerna/validation-error" "3.13.0"
-    dedent "^0.7.0"
-    get-port "^4.2.0"
-    multimatch "^3.0.0"
-    npm-package-arg "^6.1.0"
-    npmlog "^4.1.2"
-    p-finally "^1.0.0"
-    p-map "^2.1.0"
-    p-map-series "^1.0.0"
-    p-waterfall "^1.0.0"
-    read-package-tree "^5.1.6"
-    semver "^6.2.0"
-
-"@lerna/changed@3.16.4":
-  version "3.16.4"
-  resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-3.16.4.tgz#c3e727d01453513140eee32c94b695de577dc955"
-  integrity sha512-NCD7XkK744T23iW0wqKEgF4R9MYmReUbyHCZKopFnsNpQdqumc3SOIvQUAkKCP6hQJmYvxvOieoVgy/CVDpZ5g==
-  dependencies:
-    "@lerna/collect-updates" "3.16.0"
-    "@lerna/command" "3.16.0"
-    "@lerna/listable" "3.16.0"
-    "@lerna/output" "3.13.0"
-    "@lerna/version" "3.16.4"
-
-"@lerna/check-working-tree@3.14.2":
-  version "3.14.2"
-  resolved "https://registry.yarnpkg.com/@lerna/check-working-tree/-/check-working-tree-3.14.2.tgz#5ce007722180a69643a8456766ed8a91fc7e9ae1"
-  integrity sha512-7safqxM/MYoAoxZxulUDtIJIbnBIgo0PB/FHytueG+9VaX7GMnDte2Bt1EKa0dz2sAyQdmQ3Q8ZXpf/6JDjaeg==
-  dependencies:
-    "@lerna/collect-uncommitted" "3.14.2"
-    "@lerna/describe-ref" "3.14.2"
-    "@lerna/validation-error" "3.13.0"
-
-"@lerna/child-process@3.14.2":
-  version "3.14.2"
-  resolved "https://registry.yarnpkg.com/@lerna/child-process/-/child-process-3.14.2.tgz#950240cba83f7dfe25247cfa6c9cebf30b7d94f6"
-  integrity sha512-xnq+W5yQb6RkwI0p16ZQnrn6HkloH/MWTw4lGE1nKsBLAUbmSU5oTE93W1nrG0X3IMF/xWc9UYvNdUGMWvZZ4w==
-  dependencies:
-    chalk "^2.3.1"
-    execa "^1.0.0"
-    strong-log-transformer "^2.0.0"
-
-"@lerna/clean@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-3.16.0.tgz#1c134334cacea1b1dbeacdc580e8b9240db8efa1"
-  integrity sha512-5P9U5Y19WmYZr7UAMGXBpY7xCRdlR7zhHy8MAPDKVx70rFIBS6nWXn5n7Kntv74g7Lm1gJ2rsiH5tj1OPcRJgg==
-  dependencies:
-    "@lerna/command" "3.16.0"
-    "@lerna/filter-options" "3.16.0"
-    "@lerna/prompt" "3.13.0"
-    "@lerna/pulse-till-done" "3.13.0"
-    "@lerna/rimraf-dir" "3.14.2"
-    p-map "^2.1.0"
-    p-map-series "^1.0.0"
-    p-waterfall "^1.0.0"
-
-"@lerna/cli@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/cli/-/cli-3.13.0.tgz#3d7b357fdd7818423e9681a7b7f2abd106c8a266"
-  integrity sha512-HgFGlyCZbYaYrjOr3w/EsY18PdvtsTmDfpUQe8HwDjXlPeCCUgliZjXLOVBxSjiOvPeOSwvopwIHKWQmYbwywg==
-  dependencies:
-    "@lerna/global-options" "3.13.0"
-    dedent "^0.7.0"
-    npmlog "^4.1.2"
-    yargs "^12.0.1"
-
-"@lerna/collect-uncommitted@3.14.2":
-  version "3.14.2"
-  resolved "https://registry.yarnpkg.com/@lerna/collect-uncommitted/-/collect-uncommitted-3.14.2.tgz#b5ed00d800bea26bb0d18404432b051eee8d030e"
-  integrity sha512-4EkQu4jIOdNL2BMzy/N0ydHB8+Z6syu6xiiKXOoFl0WoWU9H1jEJCX4TH7CmVxXL1+jcs8FIS2pfQz4oew99Eg==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    chalk "^2.3.1"
-    figgy-pudding "^3.5.1"
-    npmlog "^4.1.2"
-
-"@lerna/collect-updates@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/collect-updates/-/collect-updates-3.16.0.tgz#6db3ce8a740a4e2b972c033a63bdfb77f2553d8c"
-  integrity sha512-HwAIl815X2TNlmcp28zCrSdXfoZWNP7GJPEqNWYk7xDJTYLqQ+SrmKUePjb3AMGBwYAraZSEJLbHdBpJ5+cHmQ==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    "@lerna/describe-ref" "3.14.2"
-    minimatch "^3.0.4"
-    npmlog "^4.1.2"
-    slash "^2.0.0"
-
-"@lerna/command@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/command/-/command-3.16.0.tgz#ba3dba49cb5ce4d11b48269cf95becd86e30773f"
-  integrity sha512-u7tE4GC4/gfbPA9eQg+0ulnoJ+PMoMqomx033r/IxqZrHtmJR9+pF/37S0fsxJ2hX/RMFPC7c9Q/i8NEufSpdQ==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    "@lerna/package-graph" "3.16.0"
-    "@lerna/project" "3.16.0"
-    "@lerna/validation-error" "3.13.0"
-    "@lerna/write-log-file" "3.13.0"
-    dedent "^0.7.0"
-    execa "^1.0.0"
-    is-ci "^2.0.0"
-    lodash "^4.17.14"
-    npmlog "^4.1.2"
-
-"@lerna/conventional-commits@3.16.4":
-  version "3.16.4"
-  resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-3.16.4.tgz#bf464f11b2f6534dad204db00430e1651b346a04"
-  integrity sha512-QSZJ0bC9n6FVaf+7KDIq5zMv8WnHXnwhyL5jG1Nyh3SgOg9q2uflqh7YsYB+G6FwaRfnPaKosh6obijpYg0llA==
-  dependencies:
-    "@lerna/validation-error" "3.13.0"
-    conventional-changelog-angular "^5.0.3"
-    conventional-changelog-core "^3.1.6"
-    conventional-recommended-bump "^5.0.0"
-    fs-extra "^8.1.0"
-    get-stream "^4.0.0"
-    lodash.template "^4.5.0"
-    npm-package-arg "^6.1.0"
-    npmlog "^4.1.2"
-    pify "^4.0.1"
-    semver "^6.2.0"
-
-"@lerna/create-symlink@3.16.2":
-  version "3.16.2"
-  resolved "https://registry.yarnpkg.com/@lerna/create-symlink/-/create-symlink-3.16.2.tgz#412cb8e59a72f5a7d9463e4e4721ad2070149967"
-  integrity sha512-pzXIJp6av15P325sgiIRpsPXLFmkisLhMBCy4764d+7yjf2bzrJ4gkWVMhsv4AdF0NN3OyZ5jjzzTtLNqfR+Jw==
-  dependencies:
-    "@zkochan/cmd-shim" "^3.1.0"
-    fs-extra "^8.1.0"
-    npmlog "^4.1.2"
-
-"@lerna/create@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/create/-/create-3.16.0.tgz#4de841ec7d98b29bb19fb7d6ad982e65f7a150e8"
-  integrity sha512-OZApR1Iz7awutbmj4sAArwhqCyKgcrnw9rH0aWAUrkYWrD1w4TwkvAcYAsfx5GpQGbLQwoXhoyyPwPfZRRWz3Q==
-  dependencies:
-    "@evocateur/pacote" "^9.6.3"
-    "@lerna/child-process" "3.14.2"
-    "@lerna/command" "3.16.0"
-    "@lerna/npm-conf" "3.16.0"
-    "@lerna/validation-error" "3.13.0"
-    camelcase "^5.0.0"
-    dedent "^0.7.0"
-    fs-extra "^8.1.0"
-    globby "^9.2.0"
-    init-package-json "^1.10.3"
-    npm-package-arg "^6.1.0"
-    p-reduce "^1.0.0"
-    pify "^4.0.1"
-    semver "^6.2.0"
-    slash "^2.0.0"
-    validate-npm-package-license "^3.0.3"
-    validate-npm-package-name "^3.0.0"
-    whatwg-url "^7.0.0"
-
-"@lerna/describe-ref@3.14.2":
-  version "3.14.2"
-  resolved "https://registry.yarnpkg.com/@lerna/describe-ref/-/describe-ref-3.14.2.tgz#edc3c973f5ca9728d23358c4f4d3b55a21f65be5"
-  integrity sha512-qa5pzDRK2oBQXNjyRmRnN7E8a78NMYfQjjlRFB0KNHMsT6mCiL9+8kIS39sSE2NqT8p7xVNo2r2KAS8R/m3CoQ==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    npmlog "^4.1.2"
-
-"@lerna/diff@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-3.16.0.tgz#6d09a786f9f5b343a2fdc460eb0be08a05b420aa"
-  integrity sha512-QUpVs5TPl8vBIne10/vyjUxanQBQQp7Lk3iaB8MnCysKr0O+oy7trWeFVDPEkBTCD177By7yPGyW5Yey1nCBbA==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    "@lerna/command" "3.16.0"
-    "@lerna/validation-error" "3.13.0"
-    npmlog "^4.1.2"
-
-"@lerna/exec@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-3.16.0.tgz#2b6c033cee46181b6eede0eb12aad5c2c0181e89"
-  integrity sha512-mH3O5NXf/O88jBaBBTUf+d56CUkxpg782s3Jxy7HWbVuSUULt3iMRPTh+zEXO5/555etsIVVDDyUR76meklrJA==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    "@lerna/command" "3.16.0"
-    "@lerna/filter-options" "3.16.0"
-    "@lerna/run-topologically" "3.16.0"
-    "@lerna/validation-error" "3.13.0"
-    p-map "^2.1.0"
-
-"@lerna/filter-options@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/filter-options/-/filter-options-3.16.0.tgz#b1660b4480c02a5c6efa4d0cd98b9afde4ed0bba"
-  integrity sha512-InIi1fF8+PxpCwir9bIy+pGxrdE6hvN0enIs1eNGCVS1TTE8osNgiZXa838bMQ1yaEccdcnVX6Z03BNKd56kNg==
-  dependencies:
-    "@lerna/collect-updates" "3.16.0"
-    "@lerna/filter-packages" "3.16.0"
-    dedent "^0.7.0"
-
-"@lerna/filter-packages@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/filter-packages/-/filter-packages-3.16.0.tgz#7d34dc8530c71016263d6f67dc65308ecf11c9fc"
-  integrity sha512-eGFzQTx0ogkGDCnbTuXqssryR6ilp8+dcXt6B+aq1MaqL/vOJRZyqMm4TY3CUOUnzZCi9S2WWyMw3PnAJOF+kg==
-  dependencies:
-    "@lerna/validation-error" "3.13.0"
-    multimatch "^3.0.0"
-    npmlog "^4.1.2"
-
-"@lerna/get-npm-exec-opts@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-3.13.0.tgz#d1b552cb0088199fc3e7e126f914e39a08df9ea5"
-  integrity sha512-Y0xWL0rg3boVyJk6An/vurKzubyJKtrxYv2sj4bB8Mc5zZ3tqtv0ccbOkmkXKqbzvNNF7VeUt1OJ3DRgtC/QZw==
-  dependencies:
-    npmlog "^4.1.2"
-
-"@lerna/get-packed@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/get-packed/-/get-packed-3.16.0.tgz#1b316b706dcee86c7baa55e50b087959447852ff"
-  integrity sha512-AjsFiaJzo1GCPnJUJZiTW6J1EihrPkc2y3nMu6m3uWFxoleklsSCyImumzVZJssxMi3CPpztj8LmADLedl9kXw==
-  dependencies:
-    fs-extra "^8.1.0"
-    ssri "^6.0.1"
-    tar "^4.4.8"
-
-"@lerna/github-client@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-3.16.0.tgz#619874e461641d4f59ab1b3f1a7ba22dba88125d"
-  integrity sha512-IVJjcKjkYaUEPJsDyAblHGEFFNKCRyMagbIDm14L7Ab94ccN6i4TKOqAFEJn2SJHYvKKBdp3Zj2zNlASOMe3DA==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    "@octokit/plugin-enterprise-rest" "^3.6.1"
-    "@octokit/rest" "^16.28.4"
-    git-url-parse "^11.1.2"
-    npmlog "^4.1.2"
-
-"@lerna/gitlab-client@3.15.0":
-  version "3.15.0"
-  resolved "https://registry.yarnpkg.com/@lerna/gitlab-client/-/gitlab-client-3.15.0.tgz#91f4ec8c697b5ac57f7f25bd50fe659d24aa96a6"
-  integrity sha512-OsBvRSejHXUBMgwWQqNoioB8sgzL/Pf1pOUhHKtkiMl6aAWjklaaq5HPMvTIsZPfS6DJ9L5OK2GGZuooP/5c8Q==
-  dependencies:
-    node-fetch "^2.5.0"
-    npmlog "^4.1.2"
-    whatwg-url "^7.0.0"
-
-"@lerna/global-options@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/global-options/-/global-options-3.13.0.tgz#217662290db06ad9cf2c49d8e3100ee28eaebae1"
-  integrity sha512-SlZvh1gVRRzYLVluz9fryY1nJpZ0FHDGB66U9tFfvnnxmueckRQxLopn3tXj3NU1kc3QANT2I5BsQkOqZ4TEFQ==
-
-"@lerna/has-npm-version@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/has-npm-version/-/has-npm-version-3.16.0.tgz#55764a4ce792f0c8553cf996a17f554b9e843288"
-  integrity sha512-TIY036dA9J8OyTrZq9J+it2DVKifL65k7hK8HhkUPpitJkw6jwbMObA/8D40LOGgWNPweJWqmlrTbRSwsR7DrQ==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    semver "^6.2.0"
-
-"@lerna/import@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/import/-/import-3.16.0.tgz#b57cb453f4acfc60f6541fcbba10674055cb179d"
-  integrity sha512-trsOmGHzw0rL/f8BLNvd+9PjoTkXq2Dt4/V2UCha254hMQaYutbxcYu8iKPxz9x86jSPlH7FpbTkkHXDsoY7Yg==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    "@lerna/command" "3.16.0"
-    "@lerna/prompt" "3.13.0"
-    "@lerna/pulse-till-done" "3.13.0"
-    "@lerna/validation-error" "3.13.0"
-    dedent "^0.7.0"
-    fs-extra "^8.1.0"
-    p-map-series "^1.0.0"
-
-"@lerna/init@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/init/-/init-3.16.0.tgz#31e0d66bbededee603338b487a42674a072b7a7d"
-  integrity sha512-Ybol/x5xMtBgokx4j7/Y3u0ZmNh0NiSWzBFVaOs2NOJKvuqrWimF67DKVz7yYtTYEjtaMdug64ohFF4jcT/iag==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    "@lerna/command" "3.16.0"
-    fs-extra "^8.1.0"
-    p-map "^2.1.0"
-    write-json-file "^3.2.0"
-
-"@lerna/link@3.16.2":
-  version "3.16.2"
-  resolved "https://registry.yarnpkg.com/@lerna/link/-/link-3.16.2.tgz#6c3a5658f6448a64dddca93d9348ac756776f6f6"
-  integrity sha512-eCPg5Lo8HT525fIivNoYF3vWghO3UgEVFdbsiPmhzwI7IQyZro5HWYzLtywSAdEog5XZpd2Bbn0CsoHWBB3gww==
-  dependencies:
-    "@lerna/command" "3.16.0"
-    "@lerna/package-graph" "3.16.0"
-    "@lerna/symlink-dependencies" "3.16.2"
-    p-map "^2.1.0"
-    slash "^2.0.0"
-
-"@lerna/list@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/list/-/list-3.16.0.tgz#883c00b2baf1e03c93e54391372f67a01b773c2f"
-  integrity sha512-TkvstoPsgKqqQ0KfRumpsdMXfRSEhdXqOLq519XyI5IRWYxhoqXqfi8gG37UoBPhBNoe64japn5OjphF3rOmQA==
-  dependencies:
-    "@lerna/command" "3.16.0"
-    "@lerna/filter-options" "3.16.0"
-    "@lerna/listable" "3.16.0"
-    "@lerna/output" "3.13.0"
-
-"@lerna/listable@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/listable/-/listable-3.16.0.tgz#e6dc47a2d5a6295222663486f50e5cffc580f043"
-  integrity sha512-mtdAT2EEECqrJSDm/aXlOUFr1MRE4p6hppzY//Klp05CogQy6uGaKk+iKG5yyCLaOXFFZvG4HfO11CmoGSDWzw==
-  dependencies:
-    "@lerna/query-graph" "3.16.0"
-    chalk "^2.3.1"
-    columnify "^1.5.4"
-
-"@lerna/log-packed@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/log-packed/-/log-packed-3.16.0.tgz#f83991041ee77b2495634e14470b42259fd2bc16"
-  integrity sha512-Fp+McSNBV/P2mnLUYTaSlG8GSmpXM7krKWcllqElGxvAqv6chk2K3c2k80MeVB4WvJ9tRjUUf+i7HUTiQ9/ckQ==
-  dependencies:
-    byte-size "^5.0.1"
-    columnify "^1.5.4"
-    has-unicode "^2.0.1"
-    npmlog "^4.1.2"
-
-"@lerna/npm-conf@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/npm-conf/-/npm-conf-3.16.0.tgz#1c10a89ae2f6c2ee96962557738685300d376827"
-  integrity sha512-HbO3DUrTkCAn2iQ9+FF/eisDpWY5POQAOF1m7q//CZjdC2HSW3UYbKEGsSisFxSfaF9Z4jtrV+F/wX6qWs3CuA==
-  dependencies:
-    config-chain "^1.1.11"
-    pify "^4.0.1"
-
-"@lerna/npm-dist-tag@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/npm-dist-tag/-/npm-dist-tag-3.16.0.tgz#b2184cee5e1f291277396854820e1117a544b7ee"
-  integrity sha512-MQrBkqJJB9+eNphuj9w90QPMOs4NQXMuSRk9NqzeFunOmdDopPCV0Q7IThSxEuWnhJ2n3B7G0vWUP7tNMPdqIQ==
-  dependencies:
-    "@evocateur/npm-registry-fetch" "^4.0.0"
-    "@lerna/otplease" "3.16.0"
-    figgy-pudding "^3.5.1"
-    npm-package-arg "^6.1.0"
-    npmlog "^4.1.2"
-
-"@lerna/npm-install@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/npm-install/-/npm-install-3.16.0.tgz#8ec76a7a13b183bde438fd46296bf7a0d6f86017"
-  integrity sha512-APUOIilZCzDzce92uLEwzt1r7AEMKT/hWA1ThGJL+PO9Rn8A95Km3o2XZAYG4W0hR+P4O2nSVuKbsjQtz8CjFQ==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    "@lerna/get-npm-exec-opts" "3.13.0"
-    fs-extra "^8.1.0"
-    npm-package-arg "^6.1.0"
-    npmlog "^4.1.2"
-    signal-exit "^3.0.2"
-    write-pkg "^3.1.0"
-
-"@lerna/npm-publish@3.16.2":
-  version "3.16.2"
-  resolved "https://registry.yarnpkg.com/@lerna/npm-publish/-/npm-publish-3.16.2.tgz#a850b54739446c4aa766a0ceabfa9283bb0be676"
-  integrity sha512-tGMb9vfTxP57vUV5svkBQxd5Tzc+imZbu9ZYf8Mtwe0+HYfDjNiiHLIQw7G95w4YRdc5KsCE8sQ0uSj+f2soIg==
-  dependencies:
-    "@evocateur/libnpmpublish" "^1.2.2"
-    "@lerna/otplease" "3.16.0"
-    "@lerna/run-lifecycle" "3.16.2"
-    figgy-pudding "^3.5.1"
-    fs-extra "^8.1.0"
-    npm-package-arg "^6.1.0"
-    npmlog "^4.1.2"
-    pify "^4.0.1"
-    read-package-json "^2.0.13"
-
-"@lerna/npm-run-script@3.14.2":
-  version "3.14.2"
-  resolved "https://registry.yarnpkg.com/@lerna/npm-run-script/-/npm-run-script-3.14.2.tgz#8c518ea9d241a641273e77aad6f6fddc16779c3f"
-  integrity sha512-LbVFv+nvAoRTYLMrJlJ8RiakHXrLslL7Jp/m1R18vYrB8LYWA3ey+nz5Tel2OELzmjUiemAKZsD9h6i+Re5egg==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    "@lerna/get-npm-exec-opts" "3.13.0"
-    npmlog "^4.1.2"
-
-"@lerna/otplease@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/otplease/-/otplease-3.16.0.tgz#de66aec4f3e835a465d7bea84b58a4ab6590a0fa"
-  integrity sha512-uqZ15wYOHC+/V0WnD2iTLXARjvx3vNrpiIeyIvVlDB7rWse9mL4egex/QSgZ+lDx1OID7l2kgvcUD9cFpbqB7Q==
-  dependencies:
-    "@lerna/prompt" "3.13.0"
-    figgy-pudding "^3.5.1"
-
-"@lerna/output@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/output/-/output-3.13.0.tgz#3ded7cc908b27a9872228a630d950aedae7a4989"
-  integrity sha512-7ZnQ9nvUDu/WD+bNsypmPG5MwZBwu86iRoiW6C1WBuXXDxM5cnIAC1m2WxHeFnjyMrYlRXM9PzOQ9VDD+C15Rg==
-  dependencies:
-    npmlog "^4.1.2"
-
-"@lerna/pack-directory@3.16.4":
-  version "3.16.4"
-  resolved "https://registry.yarnpkg.com/@lerna/pack-directory/-/pack-directory-3.16.4.tgz#3eae5f91bdf5acfe0384510ed53faddc4c074693"
-  integrity sha512-uxSF0HZeGyKaaVHz5FroDY9A5NDDiCibrbYR6+khmrhZtY0Bgn6hWq8Gswl9iIlymA+VzCbshWIMX4o2O8C8ng==
-  dependencies:
-    "@lerna/get-packed" "3.16.0"
-    "@lerna/package" "3.16.0"
-    "@lerna/run-lifecycle" "3.16.2"
-    figgy-pudding "^3.5.1"
-    npm-packlist "^1.4.4"
-    npmlog "^4.1.2"
-    tar "^4.4.10"
-    temp-write "^3.4.0"
-
-"@lerna/package-graph@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/package-graph/-/package-graph-3.16.0.tgz#909c90fb41e02f2c19387342d2a5eefc36d56836"
-  integrity sha512-A2mum/gNbv7zCtAwJqoxzqv89As73OQNK2MgSX1SHWya46qoxO9a9Z2c5lOFQ8UFN5ZxqWMfFYXRCz7qzwmFXw==
-  dependencies:
-    "@lerna/prerelease-id-from-version" "3.16.0"
-    "@lerna/validation-error" "3.13.0"
-    npm-package-arg "^6.1.0"
-    npmlog "^4.1.2"
-    semver "^6.2.0"
-
-"@lerna/package@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/package/-/package-3.16.0.tgz#7e0a46e4697ed8b8a9c14d59c7f890e0d38ba13c"
-  integrity sha512-2lHBWpaxcBoiNVbtyLtPUuTYEaB/Z+eEqRS9duxpZs6D+mTTZMNy6/5vpEVSCBmzvdYpyqhqaYjjSLvjjr5Riw==
-  dependencies:
-    load-json-file "^5.3.0"
-    npm-package-arg "^6.1.0"
-    write-pkg "^3.1.0"
-
-"@lerna/prerelease-id-from-version@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-3.16.0.tgz#b24bfa789f5e1baab914d7b08baae9b7bd7d83a1"
-  integrity sha512-qZyeUyrE59uOK8rKdGn7jQz+9uOpAaF/3hbslJVFL1NqF9ELDTqjCPXivuejMX/lN4OgD6BugTO4cR7UTq/sZA==
-  dependencies:
-    semver "^6.2.0"
-
-"@lerna/project@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/project/-/project-3.16.0.tgz#2469a4e346e623fd922f38f5a12931dfb8f2a946"
-  integrity sha512-NrKcKK1EqXqhrGvslz6Q36+ZHuK3zlDhGdghRqnxDcHxMPT01NgLcmsnymmQ+gjMljuLRmvKYYCuHrknzX8VrA==
-  dependencies:
-    "@lerna/package" "3.16.0"
-    "@lerna/validation-error" "3.13.0"
-    cosmiconfig "^5.1.0"
-    dedent "^0.7.0"
-    dot-prop "^4.2.0"
-    glob-parent "^5.0.0"
-    globby "^9.2.0"
-    load-json-file "^5.3.0"
-    npmlog "^4.1.2"
-    p-map "^2.1.0"
-    resolve-from "^4.0.0"
-    write-json-file "^3.2.0"
-
-"@lerna/prompt@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/prompt/-/prompt-3.13.0.tgz#53571462bb3f5399cc1ca6d335a411fe093426a5"
-  integrity sha512-P+lWSFokdyvYpkwC3it9cE0IF2U5yy2mOUbGvvE4iDb9K7TyXGE+7lwtx2thtPvBAfIb7O13POMkv7df03HJeA==
-  dependencies:
-    inquirer "^6.2.0"
-    npmlog "^4.1.2"
-
-"@lerna/publish@3.16.4":
-  version "3.16.4"
-  resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-3.16.4.tgz#4cd55d8be9943d9a68e316e930a90cda8590500e"
-  integrity sha512-XZY+gRuF7/v6PDQwl7lvZaGWs8CnX6WIPIu+OCcyFPSL/rdWegdN7HieKBHskgX798qRQc2GrveaY7bNoTKXAw==
-  dependencies:
-    "@evocateur/libnpmaccess" "^3.1.2"
-    "@evocateur/npm-registry-fetch" "^4.0.0"
-    "@evocateur/pacote" "^9.6.3"
-    "@lerna/check-working-tree" "3.14.2"
-    "@lerna/child-process" "3.14.2"
-    "@lerna/collect-updates" "3.16.0"
-    "@lerna/command" "3.16.0"
-    "@lerna/describe-ref" "3.14.2"
-    "@lerna/log-packed" "3.16.0"
-    "@lerna/npm-conf" "3.16.0"
-    "@lerna/npm-dist-tag" "3.16.0"
-    "@lerna/npm-publish" "3.16.2"
-    "@lerna/otplease" "3.16.0"
-    "@lerna/output" "3.13.0"
-    "@lerna/pack-directory" "3.16.4"
-    "@lerna/prerelease-id-from-version" "3.16.0"
-    "@lerna/prompt" "3.13.0"
-    "@lerna/pulse-till-done" "3.13.0"
-    "@lerna/run-lifecycle" "3.16.2"
-    "@lerna/run-topologically" "3.16.0"
-    "@lerna/validation-error" "3.13.0"
-    "@lerna/version" "3.16.4"
-    figgy-pudding "^3.5.1"
-    fs-extra "^8.1.0"
-    npm-package-arg "^6.1.0"
-    npmlog "^4.1.2"
-    p-finally "^1.0.0"
-    p-map "^2.1.0"
-    p-pipe "^1.2.0"
-    semver "^6.2.0"
-
-"@lerna/pulse-till-done@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/pulse-till-done/-/pulse-till-done-3.13.0.tgz#c8e9ce5bafaf10d930a67d7ed0ccb5d958fe0110"
-  integrity sha512-1SOHpy7ZNTPulzIbargrgaJX387csN7cF1cLOGZiJQA6VqnS5eWs2CIrG8i8wmaUavj2QlQ5oEbRMVVXSsGrzA==
-  dependencies:
-    npmlog "^4.1.2"
-
-"@lerna/query-graph@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/query-graph/-/query-graph-3.16.0.tgz#e6a46ebcd9d5b03f018a06eca2b471735353953c"
-  integrity sha512-p0RO+xmHDO95ChJdWkcy9TNLysLkoDARXeRHzY5U54VCwl3Ot/2q8fMCVlA5UeGXDutEyyByl3URqEpcQCWI7Q==
-  dependencies:
-    "@lerna/package-graph" "3.16.0"
-    figgy-pudding "^3.5.1"
-
-"@lerna/resolve-symlink@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/resolve-symlink/-/resolve-symlink-3.16.0.tgz#37fc7095fabdbcf317c26eb74e0d0bde8efd2386"
-  integrity sha512-Ibj5e7njVHNJ/NOqT4HlEgPFPtPLWsO7iu59AM5bJDcAJcR96mLZ7KGVIsS2tvaO7akMEJvt2P+ErwCdloG3jQ==
-  dependencies:
-    fs-extra "^8.1.0"
-    npmlog "^4.1.2"
-    read-cmd-shim "^1.0.1"
-
-"@lerna/rimraf-dir@3.14.2":
-  version "3.14.2"
-  resolved "https://registry.yarnpkg.com/@lerna/rimraf-dir/-/rimraf-dir-3.14.2.tgz#103a49882abd85d42285d05cc76869b89f21ffd2"
-  integrity sha512-eFNkZsy44Bu9v1Hrj5Zk6omzg8O9h/7W6QYK1TTUHeyrjTEwytaNQlqF0lrTLmEvq55sviV42NC/8P3M2cvq8Q==
-  dependencies:
-    "@lerna/child-process" "3.14.2"
-    npmlog "^4.1.2"
-    path-exists "^3.0.0"
-    rimraf "^2.6.2"
-
-"@lerna/run-lifecycle@3.16.2":
-  version "3.16.2"
-  resolved "https://registry.yarnpkg.com/@lerna/run-lifecycle/-/run-lifecycle-3.16.2.tgz#67b288f8ea964db9ea4fb1fbc7715d5bbb0bce00"
-  integrity sha512-RqFoznE8rDpyyF0rOJy3+KjZCeTkO8y/OB9orPauR7G2xQ7PTdCpgo7EO6ZNdz3Al+k1BydClZz/j78gNCmL2A==
-  dependencies:
-    "@lerna/npm-conf" "3.16.0"
-    figgy-pudding "^3.5.1"
-    npm-lifecycle "^3.1.2"
-    npmlog "^4.1.2"
-
-"@lerna/run-parallel-batches@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/run-parallel-batches/-/run-parallel-batches-3.16.0.tgz#5ace7911a2dd31dfd1e53c61356034e27df0e1fb"
-  integrity sha512-2J/Nyv+MvogmQEfC7VcS21ifk7w0HVvzo2yOZRPvkCzGRu/rducxtB4RTcr58XCZ8h/Bt1aqQYKExu3c/3GXwg==
-  dependencies:
-    p-map "^2.1.0"
-    p-map-series "^1.0.0"
-
-"@lerna/run-topologically@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/run-topologically/-/run-topologically-3.16.0.tgz#39e29cfc628bbc8e736d8e0d0e984997ac01bbf5"
-  integrity sha512-4Hlpv4zDtKWa5Z0tPkeu0sK+bxZEKgkNESMGmWrUCNfj7xwvAJurcraK8+a2Y0TFYwf0qjSLY/MzX+ZbJA3Cgw==
-  dependencies:
-    "@lerna/query-graph" "3.16.0"
-    figgy-pudding "^3.5.1"
-    p-queue "^4.0.0"
-
-"@lerna/run@3.16.0":
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/@lerna/run/-/run-3.16.0.tgz#1ea568c6f303e47fa00b3403a457836d40738fd2"
-  integrity sha512-woTeLlB1OAAz4zzjdI6RyIxSGuxiUPHJZm89E1pDEPoWwtQV6HMdMgrsQd9ATsJ5Ez280HH4bF/LStAlqW8Ufg==
-  dependencies:
-    "@lerna/command" "3.16.0"
-    "@lerna/filter-options" "3.16.0"
-    "@lerna/npm-run-script" "3.14.2"
-    "@lerna/output" "3.13.0"
-    "@lerna/run-topologically" "3.16.0"
-    "@lerna/timer" "3.13.0"
-    "@lerna/validation-error" "3.13.0"
-    p-map "^2.1.0"
-
-"@lerna/symlink-binary@3.16.2":
-  version "3.16.2"
-  resolved "https://registry.yarnpkg.com/@lerna/symlink-binary/-/symlink-binary-3.16.2.tgz#f98a3d9da9e56f1d302dc0d5c2efeb951483ee66"
-  integrity sha512-kz9XVoFOGSF83gg4gBqH+mG6uxfJfTp8Uy+Cam40CvMiuzfODrGkjuBEFoM/uO2QOAwZvbQDYOBpKUa9ZxHS1Q==
-  dependencies:
-    "@lerna/create-symlink" "3.16.2"
-    "@lerna/package" "3.16.0"
-    fs-extra "^8.1.0"
-    p-map "^2.1.0"
-
-"@lerna/symlink-dependencies@3.16.2":
-  version "3.16.2"
-  resolved "https://registry.yarnpkg.com/@lerna/symlink-dependencies/-/symlink-dependencies-3.16.2.tgz#91d9909d35897aebd76a03644a00cd03c4128240"
-  integrity sha512-wnZqGJQ+Jvr1I3inxrkffrFZfmQI7Ta8gySw/UWCy95QtZWF/f5yk8zVIocCAsjzD0wgb3jJE3CFJ9W5iwWk1A==
-  dependencies:
-    "@lerna/create-symlink" "3.16.2"
-    "@lerna/resolve-symlink" "3.16.0"
-    "@lerna/symlink-binary" "3.16.2"
-    fs-extra "^8.1.0"
-    p-finally "^1.0.0"
-    p-map "^2.1.0"
-    p-map-series "^1.0.0"
-
-"@lerna/timer@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/timer/-/timer-3.13.0.tgz#bcd0904551db16e08364d6c18e5e2160fc870781"
-  integrity sha512-RHWrDl8U4XNPqY5MQHkToWS9jHPnkLZEt5VD+uunCKTfzlxGnRCr3/zVr8VGy/uENMYpVP3wJa4RKGY6M0vkRw==
-
-"@lerna/validation-error@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/validation-error/-/validation-error-3.13.0.tgz#c86b8f07c5ab9539f775bd8a54976e926f3759c3"
-  integrity sha512-SiJP75nwB8GhgwLKQfdkSnDufAaCbkZWJqEDlKOUPUvVOplRGnfL+BPQZH5nvq2BYSRXsksXWZ4UHVnQZI/HYA==
-  dependencies:
-    npmlog "^4.1.2"
-
-"@lerna/version@3.16.4":
-  version "3.16.4"
-  resolved "https://registry.yarnpkg.com/@lerna/version/-/version-3.16.4.tgz#b5cc37f3ad98358d599c6196c30b6efc396d42bf"
-  integrity sha512-ikhbMeIn5ljCtWTlHDzO4YvTmpGTX1lWFFIZ79Vd1TNyOr+OUuKLo/+p06mCl2WEdZu0W2s5E9oxfAAQbyDxEg==
-  dependencies:
-    "@lerna/check-working-tree" "3.14.2"
-    "@lerna/child-process" "3.14.2"
-    "@lerna/collect-updates" "3.16.0"
-    "@lerna/command" "3.16.0"
-    "@lerna/conventional-commits" "3.16.4"
-    "@lerna/github-client" "3.16.0"
-    "@lerna/gitlab-client" "3.15.0"
-    "@lerna/output" "3.13.0"
-    "@lerna/prerelease-id-from-version" "3.16.0"
-    "@lerna/prompt" "3.13.0"
-    "@lerna/run-lifecycle" "3.16.2"
-    "@lerna/run-topologically" "3.16.0"
-    "@lerna/validation-error" "3.13.0"
-    chalk "^2.3.1"
-    dedent "^0.7.0"
-    minimatch "^3.0.4"
-    npmlog "^4.1.2"
-    p-map "^2.1.0"
-    p-pipe "^1.2.0"
-    p-reduce "^1.0.0"
-    p-waterfall "^1.0.0"
-    semver "^6.2.0"
-    slash "^2.0.0"
-    temp-write "^3.4.0"
-
-"@lerna/write-log-file@3.13.0":
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/@lerna/write-log-file/-/write-log-file-3.13.0.tgz#b78d9e4cfc1349a8be64d91324c4c8199e822a26"
-  integrity sha512-RibeMnDPvlL8bFYW5C8cs4mbI3AHfQef73tnJCQ/SgrXZHehmHnsyWUiE7qDQCAo+B1RfTapvSyFF69iPj326A==
-  dependencies:
-    npmlog "^4.1.2"
-    write-file-atomic "^2.3.0"
-
-"@mrmlnc/readdir-enhanced@^2.2.1":
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
-  integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==
-  dependencies:
-    call-me-maybe "^1.0.1"
-    glob-to-regexp "^0.3.0"
-
-"@nodelib/fs.stat@^1.1.2":
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
-  integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
-
-"@octokit/endpoint@^5.1.0":
-  version "5.3.2"
-  resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-5.3.2.tgz#2deda2d869cac9ba7f370287d55667be2a808d4b"
-  integrity sha512-gRjteEM9I6f4D8vtwU2iGUTn9RX/AJ0SVXiqBUEuYEWVGGAVjSXdT0oNmghH5lvQNWs8mwt6ZaultuG6yXivNw==
-  dependencies:
-    deepmerge "4.0.0"
-    is-plain-object "^3.0.0"
-    universal-user-agent "^3.0.0"
-    url-template "^2.0.8"
-
-"@octokit/plugin-enterprise-rest@^3.6.1":
-  version "3.6.2"
-  resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-3.6.2.tgz#74de25bef21e0182b4fa03a8678cd00a4e67e561"
-  integrity sha512-3wF5eueS5OHQYuAEudkpN+xVeUsg8vYEMMenEzLphUZ7PRZ8OJtDcsreL3ad9zxXmBbaFWzLmFcdob5CLyZftA==
-
-"@octokit/request-error@^1.0.1", "@octokit/request-error@^1.0.2":
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-1.0.4.tgz#15e1dc22123ba4a9a4391914d80ec1e5303a23be"
-  integrity sha512-L4JaJDXn8SGT+5G0uX79rZLv0MNJmfGa4vb4vy1NnpjSnWDLJRy6m90udGwvMmavwsStgbv2QNkPzzTCMmL+ig==
-  dependencies:
-    deprecation "^2.0.0"
-    once "^1.4.0"
-
-"@octokit/request@^5.0.0":
-  version "5.0.2"
-  resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.0.2.tgz#59a920451f24811c016ddc507adcc41aafb2dca5"
-  integrity sha512-z1BQr43g4kOL4ZrIVBMHwi68Yg9VbkRUyuAgqCp1rU3vbYa69+2gIld/+gHclw15bJWQnhqqyEb7h5a5EqgZ0A==
-  dependencies:
-    "@octokit/endpoint" "^5.1.0"
-    "@octokit/request-error" "^1.0.1"
-    deprecation "^2.0.0"
-    is-plain-object "^3.0.0"
-    node-fetch "^2.3.0"
-    once "^1.4.0"
-    universal-user-agent "^3.0.0"
-
-"@octokit/rest@^16.28.4":
-  version "16.28.7"
-  resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.28.7.tgz#a2c2db5b318da84144beba82d19c1a9dbdb1a1fa"
-  integrity sha512-cznFSLEhh22XD3XeqJw51OLSfyL2fcFKUO+v2Ep9MTAFfFLS1cK1Zwd1yEgQJmJoDnj4/vv3+fGGZweG+xsbIA==
-  dependencies:
-    "@octokit/request" "^5.0.0"
-    "@octokit/request-error" "^1.0.2"
-    atob-lite "^2.0.0"
-    before-after-hook "^2.0.0"
-    btoa-lite "^1.0.0"
-    deprecation "^2.0.0"
-    lodash.get "^4.4.2"
-    lodash.set "^4.3.2"
-    lodash.uniq "^4.5.0"
-    octokit-pagination-methods "^1.1.0"
-    once "^1.4.0"
-    universal-user-agent "^3.0.0"
-    url-template "^2.0.8"
-
-"@sindresorhus/is@^0.14.0":
-  version "0.14.0"
-  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
-  integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
-
-"@snyk/composer-lockfile-parser@1.0.3":
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.0.3.tgz#4b703883ec36f3cec63c64355031e06698c771f5"
-  integrity sha512-hb+6E7kMzWlcwfe//ILDoktBPKL2a3+RnJT/CXnzRXaiLQpsdkf5li4q2v0fmvd+4v7L3tTN8KM+//lJyviEkg==
-  dependencies:
-    lodash "^4.17.13"
-
-"@snyk/dep-graph@1.12.0":
-  version "1.12.0"
-  resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.12.0.tgz#aa0df8549849a4857bc5510e839da268293e4750"
-  integrity sha512-n7+PlHn3SqznHgsCpeBRfEvU1oiQydoGkXQlnSB2+tfImiKXvY7YZbrg4wlbvYgylYiTbpCi5CpPNkJG14S+UQ==
-  dependencies:
-    graphlib "^2.1.5"
-    lodash "^4.7.14"
-    object-hash "^1.3.1"
-    semver "^6.0.0"
-    source-map-support "^0.5.11"
-    tslib "^1.9.3"
-
-"@snyk/gemfile@1.2.0":
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/@snyk/gemfile/-/gemfile-1.2.0.tgz#919857944973cce74c650e5428aaf11bcd5c0457"
-  integrity sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA==
-
-"@szmarczak/http-timer@^1.1.2":
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"
-  integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==
-  dependencies:
-    defer-to-connect "^1.0.1"
-
-"@types/agent-base@^4.2.0":
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/@types/agent-base/-/agent-base-4.2.0.tgz#00644e8b395b40e1bf50aaf1d22cabc1200d5051"
-  integrity sha512-8mrhPstU+ZX0Ugya8tl5DsDZ1I5ZwQzbL/8PA0z8Gj0k9nql7nkaMzmPVLj+l/nixWaliXi+EBiLA8bptw3z7Q==
-  dependencies:
-    "@types/events" "*"
-    "@types/node" "*"
-
-"@types/debug@^4.1.4":
-  version "4.1.4"
-  resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.4.tgz#56eec47706f0fd0b7c694eae2f3172e6b0b769da"
-  integrity sha512-D9MyoQFI7iP5VdpEyPZyjjqIJ8Y8EDNQFIFVLOmeg1rI1xiHOChyUPMPRUVfqFCerxfE+yS3vMyj37F6IdtOoQ==
-
-"@types/events@*":
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
-  integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
-
-"@types/glob@^7.1.1":
-  version "7.1.1"
-  resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
-  integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
-  dependencies:
-    "@types/events" "*"
-    "@types/minimatch" "*"
-    "@types/node" "*"
-
-"@types/minimatch@*":
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
-  integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
-
-"@types/node@*":
-  version "12.7.1"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.1.tgz#3b5c3a26393c19b400844ac422bd0f631a94d69d"
-  integrity sha512-aK9jxMypeSrhiYofWWBf/T7O+KwaiAHzM4sveCdWPn71lzUSMimRnKzhXDKfKwV1kWoBo2P1aGgaIYGLf9/ljw==
-
-"@types/node@^6.14.4":
-  version "6.14.7"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-6.14.7.tgz#2173f79d7a61d97d3aad2feeaac7ac69a3df39af"
-  integrity sha512-YbPXbaynBTe0pVExPhL76TsWnxSPeFAvImIsmylpBWn/yfw+lHy+Q68aawvZHsgskT44ZAoeE67GM5f+Brekew==
-
-"@types/node@^8.0.7":
-  version "8.10.51"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.51.tgz#80600857c0a47a8e8bafc2dae6daed6db58e3627"
-  integrity sha512-cArrlJp3Yv6IyFT/DYe+rlO8o3SIHraALbBW/+CcCYW/a9QucpLI+n2p4sRxAvl2O35TiecpX2heSZtJjvEO+Q==
-
-"@types/normalize-package-data@^2.4.0":
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
-  integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
-
-"@types/package-json@^5.0.0":
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/@types/package-json/-/package-json-5.0.1.tgz#0157acd348cf3ec8834290367e7f24b85cf6e1d0"
-  integrity sha512-0M6UdBDyGmgWly5Dtenf1U9HPMNCXtAnvvxIKoK9u6b5CCrxiVxc32eoqBzLccH/1Z8ApY927UiYoQ5cwPKcJw==
-  dependencies:
-    package-json "*"
-
-"@types/semver@^5.5.0":
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
-  integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
-
-"@vue/component-compiler-utils@^3.0.0":
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.0.0.tgz#d16fa26b836c06df5baaeb45f3d80afc47e35634"
-  integrity sha512-am+04/0UX7ektcmvhYmrf84BDVAD8afFOf4asZjN84q8xzxFclbk5x0MtxuKGfp+zjN5WWPJn3fjFAWtDdIGSw==
-  dependencies:
-    consolidate "^0.15.1"
-    hash-sum "^1.0.2"
-    lru-cache "^4.1.2"
-    merge-source-map "^1.1.0"
-    postcss "^7.0.14"
-    postcss-selector-parser "^5.0.0"
-    prettier "1.16.3"
-    source-map "~0.6.1"
-    vue-template-es2015-compiler "^1.9.0"
-
-"@webassemblyjs/ast@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
-  integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==
-  dependencies:
-    "@webassemblyjs/helper-module-context" "1.8.5"
-    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
-    "@webassemblyjs/wast-parser" "1.8.5"
-
-"@webassemblyjs/floating-point-hex-parser@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721"
-  integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==
-
-"@webassemblyjs/helper-api-error@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7"
-  integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==
-
-"@webassemblyjs/helper-buffer@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204"
-  integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==
-
-"@webassemblyjs/helper-code-frame@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e"
-  integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==
-  dependencies:
-    "@webassemblyjs/wast-printer" "1.8.5"
-
-"@webassemblyjs/helper-fsm@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452"
-  integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==
-
-"@webassemblyjs/helper-module-context@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245"
-  integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==
-  dependencies:
-    "@webassemblyjs/ast" "1.8.5"
-    mamacro "^0.0.3"
-
-"@webassemblyjs/helper-wasm-bytecode@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61"
-  integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==
-
-"@webassemblyjs/helper-wasm-section@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf"
-  integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==
-  dependencies:
-    "@webassemblyjs/ast" "1.8.5"
-    "@webassemblyjs/helper-buffer" "1.8.5"
-    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
-    "@webassemblyjs/wasm-gen" "1.8.5"
-
-"@webassemblyjs/ieee754@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e"
-  integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==
-  dependencies:
-    "@xtuc/ieee754" "^1.2.0"
-
-"@webassemblyjs/leb128@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10"
-  integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==
-  dependencies:
-    "@xtuc/long" "4.2.2"
-
-"@webassemblyjs/utf8@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc"
-  integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==
-
-"@webassemblyjs/wasm-edit@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a"
-  integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==
-  dependencies:
-    "@webassemblyjs/ast" "1.8.5"
-    "@webassemblyjs/helper-buffer" "1.8.5"
-    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
-    "@webassemblyjs/helper-wasm-section" "1.8.5"
-    "@webassemblyjs/wasm-gen" "1.8.5"
-    "@webassemblyjs/wasm-opt" "1.8.5"
-    "@webassemblyjs/wasm-parser" "1.8.5"
-    "@webassemblyjs/wast-printer" "1.8.5"
-
-"@webassemblyjs/wasm-gen@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc"
-  integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==
-  dependencies:
-    "@webassemblyjs/ast" "1.8.5"
-    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
-    "@webassemblyjs/ieee754" "1.8.5"
-    "@webassemblyjs/leb128" "1.8.5"
-    "@webassemblyjs/utf8" "1.8.5"
-
-"@webassemblyjs/wasm-opt@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264"
-  integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==
-  dependencies:
-    "@webassemblyjs/ast" "1.8.5"
-    "@webassemblyjs/helper-buffer" "1.8.5"
-    "@webassemblyjs/wasm-gen" "1.8.5"
-    "@webassemblyjs/wasm-parser" "1.8.5"
-
-"@webassemblyjs/wasm-parser@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d"
-  integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==
-  dependencies:
-    "@webassemblyjs/ast" "1.8.5"
-    "@webassemblyjs/helper-api-error" "1.8.5"
-    "@webassemblyjs/helper-wasm-bytecode" "1.8.5"
-    "@webassemblyjs/ieee754" "1.8.5"
-    "@webassemblyjs/leb128" "1.8.5"
-    "@webassemblyjs/utf8" "1.8.5"
-
-"@webassemblyjs/wast-parser@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c"
-  integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==
-  dependencies:
-    "@webassemblyjs/ast" "1.8.5"
-    "@webassemblyjs/floating-point-hex-parser" "1.8.5"
-    "@webassemblyjs/helper-api-error" "1.8.5"
-    "@webassemblyjs/helper-code-frame" "1.8.5"
-    "@webassemblyjs/helper-fsm" "1.8.5"
-    "@xtuc/long" "4.2.2"
-
-"@webassemblyjs/wast-printer@1.8.5":
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc"
-  integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==
-  dependencies:
-    "@webassemblyjs/ast" "1.8.5"
-    "@webassemblyjs/wast-parser" "1.8.5"
-    "@xtuc/long" "4.2.2"
-
-"@xtuc/ieee754@^1.2.0":
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
-  integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==
-
-"@xtuc/long@4.2.2":
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
-  integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
-
-"@yarnpkg/lockfile@^1.0.2":
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
-  integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
-
-"@zkochan/cmd-shim@^3.1.0":
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/@zkochan/cmd-shim/-/cmd-shim-3.1.0.tgz#2ab8ed81f5bb5452a85f25758eb9b8681982fd2e"
-  integrity sha512-o8l0+x7C7sMZU3v9GuJIAU10qQLtwR1dtRQIOmlNMtyaqhmpXOzx1HWiYoWfmmf9HHZoAkXpc9TM9PQYF9d4Jg==
-  dependencies:
-    is-windows "^1.0.0"
-    mkdirp-promise "^5.0.1"
-    mz "^2.5.0"
-
-JSONStream@^1.0.4, JSONStream@^1.3.4:
-  version "1.3.5"
-  resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
-  integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
-  dependencies:
-    jsonparse "^1.2.0"
-    through ">=2.2.7 <3"
-
-abbrev@1, abbrev@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
-  integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
-
-accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
-  version "1.3.7"
-  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
-  integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
-  dependencies:
-    mime-types "~2.1.24"
-    negotiator "0.6.2"
-
-acorn-jsx@^5.0.0:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
-  integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==
-
-acorn-walk@^6.1.1:
-  version "6.2.0"
-  resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c"
-  integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==
-
-acorn@^6.0.2, acorn@^6.0.7, acorn@^6.2.1:
-  version "6.2.1"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.1.tgz#3ed8422d6dec09e6121cc7a843ca86a330a86b51"
-  integrity sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==
-
-after@0.8.2:
-  version "0.8.2"
-  resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
-  integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
-
-agent-base@4, agent-base@^4.2.0, agent-base@^4.3.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
-  integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
-  dependencies:
-    es6-promisify "^5.0.0"
-
-agent-base@~4.2.1:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
-  integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
-  dependencies:
-    es6-promisify "^5.0.0"
-
-agentkeepalive@^3.4.1:
-  version "3.5.2"
-  resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67"
-  integrity sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==
-  dependencies:
-    humanize-ms "^1.2.1"
-
-ajv-errors@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
-  integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==
-
-ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
-  version "3.4.1"
-  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
-  integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
-
-ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
-  version "6.10.2"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
-  integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
-  dependencies:
-    fast-deep-equal "^2.0.1"
-    fast-json-stable-stringify "^2.0.0"
-    json-schema-traverse "^0.4.1"
-    uri-js "^4.2.2"
-
-amdefine@>=0.0.4:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
-  integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=
-
-ansi-align@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f"
-  integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=
-  dependencies:
-    string-width "^2.0.0"
-
-ansi-colors@^3.0.0:
-  version "3.2.4"
-  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
-  integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
-
-ansi-escapes@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
-  integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
-
-ansi-escapes@^4.1.0:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.2.1.tgz#4dccdb846c3eee10f6d64dea66273eab90c37228"
-  integrity sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==
-  dependencies:
-    type-fest "^0.5.2"
-
-ansi-html@0.0.7:
-  version "0.0.7"
-  resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
-  integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4=
-
-ansi-regex@^2.0.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
-  integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
-
-ansi-regex@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
-  integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
-
-ansi-regex@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
-  integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
-
-ansi-styles@^2.2.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
-  integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
-
-ansi-styles@^3.2.0, ansi-styles@^3.2.1:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
-  integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
-  dependencies:
-    color-convert "^1.9.0"
-
-ansicolors@^0.3.2:
-  version "0.3.2"
-  resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979"
-  integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=
-
-any-promise@^1.0.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
-  integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
-
-anymatch@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
-  integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==
-  dependencies:
-    micromatch "^3.1.4"
-    normalize-path "^2.1.1"
-
-aproba@^1.0.3, aproba@^1.1.1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
-  integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
-
-aproba@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
-  integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
-
-archy@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40"
-  integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=
-
-are-we-there-yet@~1.1.2:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
-  integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==
-  dependencies:
-    delegates "^1.0.0"
-    readable-stream "^2.0.6"
-
-argparse@^1.0.7:
-  version "1.0.10"
-  resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
-  integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
-  dependencies:
-    sprintf-js "~1.0.2"
-
-arr-diff@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
-  integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=
-
-arr-flatten@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
-  integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==
-
-arr-union@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
-  integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
-
-array-differ@^2.0.3:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-2.1.0.tgz#4b9c1c3f14b906757082925769e8ab904f4801b1"
-  integrity sha512-KbUpJgx909ZscOc/7CLATBFam7P1Z1QRQInvgT0UztM9Q72aGKCunKASAl7WNW0tnPmPyEMeMhdsfWhfmW037w==
-
-array-filter@~0.0.0:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec"
-  integrity sha1-fajPLiZijtcygDWB/SH2fKzS7uw=
-
-array-find-index@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
-  integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=
-
-array-flatten@1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
-  integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
-
-array-flatten@^2.1.0:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
-  integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
-
-array-ify@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece"
-  integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=
-
-array-includes@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
-  integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=
-  dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.7.0"
-
-array-map@~0.0.0:
-  version "0.0.0"
-  resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
-  integrity sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=
-
-array-reduce@~0.0.0:
-  version "0.0.0"
-  resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b"
-  integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=
-
-array-union@^1.0.1, array-union@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
-  integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=
-  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"
-  integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
-
-array-unique@^0.3.2:
-  version "0.3.2"
-  resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
-  integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
-
-arraybuffer.slice@~0.0.7:
-  version "0.0.7"
-  resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
-  integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==
-
-arrify@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
-  integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
-
-asap@^2.0.0, asap@~2.0.3:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
-  integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
-
-asn1.js@^4.0.0:
-  version "4.10.1"
-  resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
-  integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==
-  dependencies:
-    bn.js "^4.0.0"
-    inherits "^2.0.1"
-    minimalistic-assert "^1.0.0"
-
-asn1@~0.2.3:
-  version "0.2.4"
-  resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
-  integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
-  dependencies:
-    safer-buffer "~2.1.0"
-
-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"
-  integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
-
-assert@^1.1.1:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb"
-  integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==
-  dependencies:
-    object-assign "^4.1.1"
-    util "0.10.3"
-
-assign-symbols@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
-  integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
-
-ast-types@0.x.x:
-  version "0.13.2"
-  resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.2.tgz#df39b677a911a83f3a049644fb74fdded23cea48"
-  integrity sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA==
-
-astral-regex@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
-  integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
-
-async-each@^1.0.1:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
-  integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
-
-async-foreach@^0.1.3:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
-  integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=
-
-async-limiter@~1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
-  integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
-
-async@2.6.2:
-  version "2.6.2"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381"
-  integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==
-  dependencies:
-    lodash "^4.17.11"
-
-async@3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/async/-/async-3.1.0.tgz#42b3b12ae1b74927b5217d8c0016baaf62463772"
-  integrity sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==
-
-async@^1.4.0, async@^1.5.2:
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-  integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
-
-async@^2.6.1:
-  version "2.6.3"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
-  integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
-  dependencies:
-    lodash "^4.17.14"
-
-asynckit@^0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
-  integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
-
-atob-lite@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696"
-  integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY=
-
-atob@^2.1.1:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
-  integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
-
-aws-sign2@~0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
-  integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
-
-aws4@^1.8.0:
-  version "1.8.0"
-  resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
-  integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
-
-babel-eslint@^10.0.2:
-  version "10.0.2"
-  resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456"
-  integrity sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==
-  dependencies:
-    "@babel/code-frame" "^7.0.0"
-    "@babel/parser" "^7.0.0"
-    "@babel/traverse" "^7.0.0"
-    "@babel/types" "^7.0.0"
-    eslint-scope "3.7.1"
-    eslint-visitor-keys "^1.0.0"
-
-babel-loader@^8.0.6:
-  version "8.0.6"
-  resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb"
-  integrity sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==
-  dependencies:
-    find-cache-dir "^2.0.0"
-    loader-utils "^1.0.2"
-    mkdirp "^0.5.1"
-    pify "^4.0.1"
-
-babel-plugin-dynamic-import-node@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f"
-  integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==
-  dependencies:
-    object.assign "^4.1.0"
-
-backo2@1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
-  integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
-
-balanced-match@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
-  integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
-
-base64-arraybuffer@0.1.5:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
-  integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg=
-
-base64-js@^1.0.2:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
-  integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
-
-base64id@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6"
-  integrity sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=
-
-base@^0.11.1:
-  version "0.11.2"
-  resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
-  integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==
-  dependencies:
-    cache-base "^1.0.1"
-    class-utils "^0.3.5"
-    component-emitter "^1.2.1"
-    define-property "^1.0.0"
-    isobject "^3.0.1"
-    mixin-deep "^1.2.0"
-    pascalcase "^0.1.1"
-
-batch@0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
-  integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=
-
-bcrypt-pbkdf@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
-  integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
-  dependencies:
-    tweetnacl "^0.14.3"
-
-bcrypt@^3.0.6:
-  version "3.0.6"
-  resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-3.0.6.tgz#f607846df62d27e60d5e795612c4f67d70206eb2"
-  integrity sha512-taA5bCTfXe7FUjKroKky9EXpdhkVvhE5owfxfLYodbrAR1Ul3juLmIQmIQBK4L9a5BuUcE6cqmwT+Da20lF9tg==
-  dependencies:
-    nan "2.13.2"
-    node-pre-gyp "0.12.0"
-
-before-after-hook@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635"
-  integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==
-
-better-assert@~1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
-  integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=
-  dependencies:
-    callsite "1.0.0"
-
-bfj@^6.1.1:
-  version "6.1.2"
-  resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.2.tgz#325c861a822bcb358a41c78a33b8e6e2086dde7f"
-  integrity sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==
-  dependencies:
-    bluebird "^3.5.5"
-    check-types "^8.0.3"
-    hoopy "^0.1.4"
-    tryer "^1.0.1"
-
-big.js@^3.1.3:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
-  integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==
-
-big.js@^5.2.2:
-  version "5.2.2"
-  resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
-  integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
-
-binary-extensions@^1.0.0:
-  version "1.13.1"
-  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
-  integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
-
-biskviit@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/biskviit/-/biskviit-1.0.1.tgz#037a0cd4b71b9e331fd90a1122de17dc49e420a7"
-  integrity sha1-A3oM1LcbnjMf2QoRIt4X3EnkIKc=
-  dependencies:
-    psl "^1.1.7"
-
-blob@0.0.5:
-  version "0.0.5"
-  resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
-  integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==
-
-block-stream@*:
-  version "0.0.9"
-  resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
-  integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=
-  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"
-  integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==
-
-bluebird@^3.1.1, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5:
-  version "3.5.5"
-  resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
-  integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==
-
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
-  version "4.11.8"
-  resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
-  integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
-
-body-parser@1.19.0, body-parser@^1.19.0:
-  version "1.19.0"
-  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
-  integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
-  dependencies:
-    bytes "3.1.0"
-    content-type "~1.0.4"
-    debug "2.6.9"
-    depd "~1.1.2"
-    http-errors "1.7.2"
-    iconv-lite "0.4.24"
-    on-finished "~2.3.0"
-    qs "6.7.0"
-    raw-body "2.4.0"
-    type-is "~1.6.17"
-
-bonjour@^3.5.0:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
-  integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU=
-  dependencies:
-    array-flatten "^2.1.0"
-    deep-equal "^1.0.1"
-    dns-equal "^1.0.0"
-    dns-txt "^2.0.2"
-    multicast-dns "^6.0.1"
-    multicast-dns-service-types "^1.1.0"
-
-boolbase@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
-  integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
-
-boxen@^1.2.1:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
-  integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==
-  dependencies:
-    ansi-align "^2.0.0"
-    camelcase "^4.0.0"
-    chalk "^2.0.1"
-    cli-boxes "^1.0.0"
-    string-width "^2.0.0"
-    term-size "^1.2.0"
-    widest-line "^2.0.0"
-
-brace-expansion@^1.1.7:
-  version "1.1.11"
-  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
-  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
-  dependencies:
-    balanced-match "^1.0.0"
-    concat-map "0.0.1"
-
-braces@^2.3.1, braces@^2.3.2:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
-  integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==
-  dependencies:
-    arr-flatten "^1.1.0"
-    array-unique "^0.3.2"
-    extend-shallow "^2.0.1"
-    fill-range "^4.0.0"
-    isobject "^3.0.1"
-    repeat-element "^1.1.2"
-    snapdragon "^0.8.1"
-    snapdragon-node "^2.0.1"
-    split-string "^3.0.2"
-    to-regex "^3.0.1"
-
-brorand@^1.0.1:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
-  integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
-
-browserify-aes@^1.0.0, browserify-aes@^1.0.4:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48"
-  integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==
-  dependencies:
-    buffer-xor "^1.0.3"
-    cipher-base "^1.0.0"
-    create-hash "^1.1.0"
-    evp_bytestokey "^1.0.3"
-    inherits "^2.0.1"
-    safe-buffer "^5.0.1"
-
-browserify-cipher@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0"
-  integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==
-  dependencies:
-    browserify-aes "^1.0.4"
-    browserify-des "^1.0.0"
-    evp_bytestokey "^1.0.0"
-
-browserify-des@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c"
-  integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==
-  dependencies:
-    cipher-base "^1.0.1"
-    des.js "^1.0.0"
-    inherits "^2.0.1"
-    safe-buffer "^5.1.2"
-
-browserify-rsa@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
-  integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=
-  dependencies:
-    bn.js "^4.1.0"
-    randombytes "^2.0.1"
-
-browserify-sign@^4.0.0:
-  version "4.0.4"
-  resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298"
-  integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=
-  dependencies:
-    bn.js "^4.1.1"
-    browserify-rsa "^4.0.0"
-    create-hash "^1.1.0"
-    create-hmac "^1.1.2"
-    elliptic "^6.0.0"
-    inherits "^2.0.1"
-    parse-asn1 "^5.0.0"
-
-browserify-zlib@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
-  integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==
-  dependencies:
-    pako "~1.0.5"
-
-browserslist@^4.6.0, browserslist@^4.6.2:
-  version "4.6.6"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.6.tgz#6e4bf467cde520bc9dbdf3747dafa03531cec453"
-  integrity sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==
-  dependencies:
-    caniuse-lite "^1.0.30000984"
-    electron-to-chromium "^1.3.191"
-    node-releases "^1.1.25"
-
-bson@^1.1.1, bson@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.1.tgz#4330f5e99104c4e751e7351859e2d408279f2f13"
-  integrity sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg==
-
-btoa-lite@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337"
-  integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc=
-
-buffer-from@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
-  integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
-
-buffer-indexof@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
-  integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==
-
-buffer-xor@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
-  integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=
-
-buffer@^4.3.0:
-  version "4.9.1"
-  resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
-  integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=
-  dependencies:
-    base64-js "^1.0.2"
-    ieee754 "^1.1.4"
-    isarray "^1.0.0"
-
-builtin-status-codes@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
-  integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
-
-builtins@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88"
-  integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og=
-
-byline@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1"
-  integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=
-
-byte-size@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-5.0.1.tgz#4b651039a5ecd96767e71a3d7ed380e48bed4191"
-  integrity sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==
-
-bytes@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
-  integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
-
-bytes@3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
-  integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
-
-cacache@^12.0.0, cacache@^12.0.2:
-  version "12.0.2"
-  resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.2.tgz#8db03205e36089a3df6954c66ce92541441ac46c"
-  integrity sha512-ifKgxH2CKhJEg6tNdAwziu6Q33EvuG26tYcda6PT3WKisZcYDXsnEdnRv67Po3yCzFfaSoMjGZzJyD2c3DT1dg==
-  dependencies:
-    bluebird "^3.5.5"
-    chownr "^1.1.1"
-    figgy-pudding "^3.5.1"
-    glob "^7.1.4"
-    graceful-fs "^4.1.15"
-    infer-owner "^1.0.3"
-    lru-cache "^5.1.1"
-    mississippi "^3.0.0"
-    mkdirp "^0.5.1"
-    move-concurrently "^1.0.1"
-    promise-inflight "^1.0.1"
-    rimraf "^2.6.3"
-    ssri "^6.0.1"
-    unique-filename "^1.1.1"
-    y18n "^4.0.0"
-
-cache-base@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
-  integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==
-  dependencies:
-    collection-visit "^1.0.0"
-    component-emitter "^1.2.1"
-    get-value "^2.0.6"
-    has-value "^1.0.0"
-    isobject "^3.0.1"
-    set-value "^2.0.0"
-    to-object-path "^0.3.0"
-    union-value "^1.0.0"
-    unset-value "^1.0.0"
-
-cacheable-request@^6.0.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
-  integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==
-  dependencies:
-    clone-response "^1.0.2"
-    get-stream "^5.1.0"
-    http-cache-semantics "^4.0.0"
-    keyv "^3.0.0"
-    lowercase-keys "^2.0.0"
-    normalize-url "^4.1.0"
-    responselike "^1.0.2"
-
-call-me-maybe@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
-  integrity sha1-JtII6onje1y95gJQoV8DHBak1ms=
-
-caller-callsite@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
-  integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=
-  dependencies:
-    callsites "^2.0.0"
-
-caller-path@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
-  integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=
-  dependencies:
-    caller-callsite "^2.0.0"
-
-callsite@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
-  integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA=
-
-callsites@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
-  integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=
-
-callsites@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
-  integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
-
-camel-case@3.0.x:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73"
-  integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=
-  dependencies:
-    no-case "^2.2.0"
-    upper-case "^1.1.1"
-
-camelcase-keys@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
-  integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc=
-  dependencies:
-    camelcase "^2.0.0"
-    map-obj "^1.0.0"
-
-camelcase-keys@^4.0.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77"
-  integrity sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=
-  dependencies:
-    camelcase "^4.1.0"
-    map-obj "^2.0.0"
-    quick-lru "^1.0.0"
-
-camelcase@^2.0.0, camelcase@^2.0.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
-  integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
-
-camelcase@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
-  integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo=
-
-camelcase@^4.0.0, camelcase@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
-  integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
-
-camelcase@^5.0.0, camelcase@^5.3.1:
-  version "5.3.1"
-  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
-  integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-
-caniuse-lite@^1.0.30000984:
-  version "1.0.30000989"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9"
-  integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==
-
-capture-stack-trace@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d"
-  integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==
-
-caseless@~0.12.0:
-  version "0.12.0"
-  resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
-  integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
-
-chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2:
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
-  integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
-  dependencies:
-    ansi-styles "^3.2.1"
-    escape-string-regexp "^1.0.5"
-    supports-color "^5.3.0"
-
-chalk@^1.1.1:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
-  integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
-  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"
-
-chardet@^0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
-  integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
-
-charenc@~0.0.1:
-  version "0.0.2"
-  resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
-  integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
-
-chart.js@^2.5.0:
-  version "2.8.0"
-  resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.8.0.tgz#b703b10d0f4ec5079eaefdcd6ca32dc8f826e0e9"
-  integrity sha512-Di3wUL4BFvqI5FB5K26aQ+hvWh8wnP9A3DWGvXHVkO13D3DSnaSsdZx29cXlEsYKVkn1E2az+ZYFS4t0zi8x0w==
-  dependencies:
-    chartjs-color "^2.1.0"
-    moment "^2.10.2"
-
-chartjs-adapter-date-fns@^0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-0.1.2.tgz#54b1184b12f631f02839031bee9daeb3fe6c64a9"
-  integrity sha512-Q/k6OmGtcJcPG7YHAF2TmKD0egsDd4d1fVKMCRtQkzBDhcAPVKraVGk2Th1JzT4j5cQ8wS6VTtnbTsq+6M2fag==
-
-chartjs-color-string@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"
-  integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==
-  dependencies:
-    color-name "^1.0.0"
-
-chartjs-color@^2.1.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.3.0.tgz#0e7e1e8dba37eae8415fd3db38bf572007dd958f"
-  integrity sha512-hEvVheqczsoHD+fZ+tfPUE+1+RbV6b+eksp2LwAhwRTVXEjCSEavvk+Hg3H6SZfGlPh/UfmWKGIvZbtobOEm3g==
-  dependencies:
-    chartjs-color-string "^0.6.0"
-    color-convert "^0.5.3"
-
-check-types@^8.0.3:
-  version "8.0.3"
-  resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
-  integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==
-
-chokidar@^2.0.2, chokidar@^2.1.6:
-  version "2.1.6"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5"
-  integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==
-  dependencies:
-    anymatch "^2.0.0"
-    async-each "^1.0.1"
-    braces "^2.3.2"
-    glob-parent "^3.1.0"
-    inherits "^2.0.3"
-    is-binary-path "^1.0.0"
-    is-glob "^4.0.0"
-    normalize-path "^3.0.0"
-    path-is-absolute "^1.0.0"
-    readdirp "^2.2.1"
-    upath "^1.1.1"
-  optionalDependencies:
-    fsevents "^1.2.7"
-
-chownr@^1.1.1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6"
-  integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==
-
-chrome-trace-event@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
-  integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==
-  dependencies:
-    tslib "^1.9.0"
-
-ci-info@^1.5.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
-  integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
-
-ci-info@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
-  integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
-
-cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
-  integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==
-  dependencies:
-    inherits "^2.0.1"
-    safe-buffer "^5.0.1"
-
-class-utils@^0.3.5:
-  version "0.3.6"
-  resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
-  integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==
-  dependencies:
-    arr-union "^3.1.0"
-    define-property "^0.2.5"
-    isobject "^3.0.0"
-    static-extend "^0.1.1"
-
-clean-css@4.2.x:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17"
-  integrity sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==
-  dependencies:
-    source-map "~0.6.0"
-
-cli-boxes@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
-  integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM=
-
-cli-cursor@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
-  integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=
-  dependencies:
-    restore-cursor "^2.0.0"
-
-cli-width@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
-  integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
-
-cliui@^3.0.3, cliui@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
-  integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=
-  dependencies:
-    string-width "^1.0.1"
-    strip-ansi "^3.0.1"
-    wrap-ansi "^2.0.0"
-
-cliui@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
-  integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==
-  dependencies:
-    string-width "^2.1.1"
-    strip-ansi "^4.0.0"
-    wrap-ansi "^2.0.0"
-
-cliui@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
-  integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
-  dependencies:
-    string-width "^3.1.0"
-    strip-ansi "^5.2.0"
-    wrap-ansi "^5.1.0"
-
-clone-deep@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.3.0.tgz#348c61ae9cdbe0edfe053d91ff4cc521d790ede8"
-  integrity sha1-NIxhrpzb4O3+BT2R/0zFIdeQ7eg=
-  dependencies:
-    for-own "^1.0.0"
-    is-plain-object "^2.0.1"
-    kind-of "^3.2.2"
-    shallow-clone "^0.1.2"
-
-clone-deep@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713"
-  integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==
-  dependencies:
-    for-own "^1.0.0"
-    is-plain-object "^2.0.4"
-    kind-of "^6.0.0"
-    shallow-clone "^1.0.0"
-
-clone-response@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
-  integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
-  dependencies:
-    mimic-response "^1.0.0"
-
-clone@^1.0.2:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
-  integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
-
-co@^4.6.0:
-  version "4.6.0"
-  resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
-  integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
-
-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"
-  integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
-
-collection-visit@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
-  integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=
-  dependencies:
-    map-visit "^1.0.0"
-    object-visit "^1.0.0"
-
-color-convert@^0.5.3:
-  version "0.5.3"
-  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd"
-  integrity sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=
-
-color-convert@^1.9.0:
-  version "1.9.3"
-  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
-  integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
-  dependencies:
-    color-name "1.1.3"
-
-color-name@1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
-  integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
-
-color-name@^1.0.0:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
-  integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-
-columnify@^1.5.4:
-  version "1.5.4"
-  resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb"
-  integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=
-  dependencies:
-    strip-ansi "^3.0.0"
-    wcwidth "^1.0.0"
-
-combined-stream@^1.0.6, combined-stream@~1.0.6:
-  version "1.0.8"
-  resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
-  integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
-  dependencies:
-    delayed-stream "~1.0.0"
-
-commander@2.17.x:
-  version "2.17.1"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
-  integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
-
-commander@^2.18.0, commander@^2.20.0, commander@~2.20.0:
-  version "2.20.0"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
-  integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
-
-commander@~2.19.0:
-  version "2.19.0"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
-  integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
-
-commondir@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
-  integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
-
-compare-func@^1.3.1:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-1.3.2.tgz#99dd0ba457e1f9bc722b12c08ec33eeab31fa648"
-  integrity sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=
-  dependencies:
-    array-ify "^1.0.0"
-    dot-prop "^3.0.0"
-
-component-bind@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
-  integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=
-
-component-emitter@1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
-  integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
-
-component-emitter@^1.2.1:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
-  integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
-
-component-inherit@0.0.3:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
-  integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=
-
-compressible@~2.0.16:
-  version "2.0.17"
-  resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.17.tgz#6e8c108a16ad58384a977f3a482ca20bff2f38c1"
-  integrity sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==
-  dependencies:
-    mime-db ">= 1.40.0 < 2"
-
-compression@^1.7.4:
-  version "1.7.4"
-  resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
-  integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
-  dependencies:
-    accepts "~1.3.5"
-    bytes "3.0.0"
-    compressible "~2.0.16"
-    debug "2.6.9"
-    on-headers "~1.0.2"
-    safe-buffer "5.1.2"
-    vary "~1.1.2"
-
-concat-map@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-
-concat-stream@^1.5.0:
-  version "1.6.2"
-  resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
-  integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
-  dependencies:
-    buffer-from "^1.0.0"
-    inherits "^2.0.3"
-    readable-stream "^2.2.2"
-    typedarray "^0.0.6"
-
-concat-stream@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1"
-  integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==
-  dependencies:
-    buffer-from "^1.0.0"
-    inherits "^2.0.3"
-    readable-stream "^3.0.2"
-    typedarray "^0.0.6"
-
-config-chain@^1.1.11:
-  version "1.1.12"
-  resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
-  integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==
-  dependencies:
-    ini "^1.3.4"
-    proto-list "~1.2.1"
-
-config@^3.2.0, config@^3.2.2:
-  version "3.2.2"
-  resolved "https://registry.yarnpkg.com/config/-/config-3.2.2.tgz#c7d31bb2a9d30013a905ff420101e3b1ba58eead"
-  integrity sha512-rOsfIOAcG82AWouK4/vBS/OKz3UPl2T/kP0irExmXJJOoWg2CmdfPLdx56bCoMUMFNh+7soQkQWCUC8DyemiwQ==
-  dependencies:
-    json5 "^1.0.1"
-
-configstore@^3.0.0, configstore@^3.1.2:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f"
-  integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==
-  dependencies:
-    dot-prop "^4.1.0"
-    graceful-fs "^4.1.2"
-    make-dir "^1.0.0"
-    unique-string "^1.0.0"
-    write-file-atomic "^2.0.0"
-    xdg-basedir "^3.0.0"
-
-confusing-browser-globals@^1.0.5:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.7.tgz#5ae852bd541a910e7ffb2dbb864a2d21a36ad29b"
-  integrity sha512-cgHI1azax5ATrZ8rJ+ODDML9Fvu67PimB6aNxBrc/QwSaDaM9eTfIEUHx3bBLJJ82ioSb+/5zfsMCCEJax3ByQ==
-
-confusing-browser-globals@^1.0.7:
-  version "1.0.9"
-  resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd"
-  integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==
-
-connect-history-api-fallback@^1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
-  integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
-
-console-browserify@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
-  integrity sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=
-  dependencies:
-    date-now "^0.1.4"
-
-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"
-  integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
-
-consolidate@^0.15.1:
-  version "0.15.1"
-  resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"
-  integrity sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==
-  dependencies:
-    bluebird "^3.1.1"
-
-constants-browserify@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
-  integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
-
-contains-path@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
-  integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=
-
-content-disposition@0.5.3:
-  version "0.5.3"
-  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
-  integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
-  dependencies:
-    safe-buffer "5.1.2"
-
-content-type@~1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
-  integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
-
-conventional-changelog-angular@^5.0.3:
-  version "5.0.3"
-  resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.3.tgz#299fdd43df5a1f095283ac16aeedfb0a682ecab0"
-  integrity sha512-YD1xzH7r9yXQte/HF9JBuEDfvjxxwDGGwZU1+ndanbY0oFgA+Po1T9JDSpPLdP0pZT6MhCAsdvFKC4TJ4MTJTA==
-  dependencies:
-    compare-func "^1.3.1"
-    q "^1.5.1"
-
-conventional-changelog-core@^3.1.6:
-  version "3.2.3"
-  resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-3.2.3.tgz#b31410856f431c847086a7dcb4d2ca184a7d88fb"
-  integrity sha512-LMMX1JlxPIq/Ez5aYAYS5CpuwbOk6QFp8O4HLAcZxe3vxoCtABkhfjetk8IYdRB9CDQGwJFLR3Dr55Za6XKgUQ==
-  dependencies:
-    conventional-changelog-writer "^4.0.6"
-    conventional-commits-parser "^3.0.3"
-    dateformat "^3.0.0"
-    get-pkg-repo "^1.0.0"
-    git-raw-commits "2.0.0"
-    git-remote-origin-url "^2.0.0"
-    git-semver-tags "^2.0.3"
-    lodash "^4.2.1"
-    normalize-package-data "^2.3.5"
-    q "^1.5.1"
-    read-pkg "^3.0.0"
-    read-pkg-up "^3.0.0"
-    through2 "^3.0.0"
-
-conventional-changelog-preset-loader@^2.1.1:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.2.0.tgz#571e2b3d7b53d65587bea9eedf6e37faa5db4fcc"
-  integrity sha512-zXB+5vF7D5Y3Cb/rJfSyCCvFphCVmF8mFqOdncX3BmjZwAtGAPfYrBcT225udilCKvBbHgyzgxqz2GWDB5xShQ==
-
-conventional-changelog-writer@^4.0.6:
-  version "4.0.7"
-  resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.0.7.tgz#e4b7d9cbea902394ad671f67108a71fa90c7095f"
-  integrity sha512-p/wzs9eYaxhFbrmX/mCJNwJuvvHR+j4Fd0SQa2xyAhYed6KBiZ780LvoqUUvsayP4R1DtC27czalGUhKV2oabw==
-  dependencies:
-    compare-func "^1.3.1"
-    conventional-commits-filter "^2.0.2"
-    dateformat "^3.0.0"
-    handlebars "^4.1.2"
-    json-stringify-safe "^5.0.1"
-    lodash "^4.2.1"
-    meow "^4.0.0"
-    semver "^6.0.0"
-    split "^1.0.0"
-    through2 "^3.0.0"
-
-conventional-commits-filter@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.2.tgz#f122f89fbcd5bb81e2af2fcac0254d062d1039c1"
-  integrity sha512-WpGKsMeXfs21m1zIw4s9H5sys2+9JccTzpN6toXtxhpw2VNF2JUXwIakthKBy+LN4DvJm+TzWhxOMWOs1OFCFQ==
-  dependencies:
-    lodash.ismatch "^4.4.0"
-    modify-values "^1.0.0"
-
-conventional-commits-parser@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.0.3.tgz#c3f972fd4e056aa8b9b4f5f3d0e540da18bf396d"
-  integrity sha512-KaA/2EeUkO4bKjinNfGUyqPTX/6w9JGshuQRik4r/wJz7rUw3+D3fDG6sZSEqJvKILzKXFQuFkpPLclcsAuZcg==
-  dependencies:
-    JSONStream "^1.0.4"
-    is-text-path "^2.0.0"
-    lodash "^4.2.1"
-    meow "^4.0.0"
-    split2 "^2.0.0"
-    through2 "^3.0.0"
-    trim-off-newlines "^1.0.0"
-
-conventional-recommended-bump@^5.0.0:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-5.0.1.tgz#5af63903947b6e089e77767601cb592cabb106ba"
-  integrity sha512-RVdt0elRcCxL90IrNP0fYCpq1uGt2MALko0eyeQ+zQuDVWtMGAy9ng6yYn3kax42lCj9+XBxQ8ZN6S9bdKxDhQ==
-  dependencies:
-    concat-stream "^2.0.0"
-    conventional-changelog-preset-loader "^2.1.1"
-    conventional-commits-filter "^2.0.2"
-    conventional-commits-parser "^3.0.3"
-    git-raw-commits "2.0.0"
-    git-semver-tags "^2.0.3"
-    meow "^4.0.0"
-    q "^1.5.1"
-
-convert-hex@~0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/convert-hex/-/convert-hex-0.1.0.tgz#08c04568922c27776b8a2e81a95d393362ea0b65"
-  integrity sha1-CMBFaJIsJ3drii6BqV05M2LqC2U=
-
-convert-source-map@^1.1.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
-  integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==
-  dependencies:
-    safe-buffer "~5.1.1"
-
-convert-string@~0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/convert-string/-/convert-string-0.1.0.tgz#79ce41a9bb0d03bcf72cdc6a8f3c56fbbc64410a"
-  integrity sha1-ec5BqbsNA7z3LNxqjzxW+7xkQQo=
-
-cookie-parser@^1.4.4:
-  version "1.4.4"
-  resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.4.tgz#e6363de4ea98c3def9697b93421c09f30cf5d188"
-  integrity sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==
-  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"
-  integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
-
-cookie@0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
-  integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
-
-cookie@0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
-  integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
-
-copy-concurrently@^1.0.0:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
-  integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==
-  dependencies:
-    aproba "^1.1.1"
-    fs-write-stream-atomic "^1.0.8"
-    iferr "^0.1.5"
-    mkdirp "^0.5.1"
-    rimraf "^2.5.4"
-    run-queue "^1.0.0"
-
-copy-descriptor@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
-  integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
-
-core-js-compat@^3.1.1:
-  version "3.1.4"
-  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.1.4.tgz#e4d0c40fbd01e65b1d457980fe4112d4358a7408"
-  integrity sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==
-  dependencies:
-    browserslist "^4.6.2"
-    core-js-pure "3.1.4"
-    semver "^6.1.1"
-
-core-js-pure@3.1.4:
-  version "3.1.4"
-  resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.1.4.tgz#5fa17dc77002a169a3566cc48dc774d2e13e3769"
-  integrity sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==
-
-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"
-  integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
-
-cors@^2.8.5:
-  version "2.8.5"
-  resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
-  integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
-  dependencies:
-    object-assign "^4"
-    vary "^1"
-
-cosmiconfig@^5.1.0, cosmiconfig@^5.2.1:
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
-  integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==
-  dependencies:
-    import-fresh "^2.0.0"
-    is-directory "^0.3.1"
-    js-yaml "^3.13.1"
-    parse-json "^4.0.0"
-
-create-ecdh@^4.0.0:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff"
-  integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==
-  dependencies:
-    bn.js "^4.1.0"
-    elliptic "^6.0.0"
-
-create-error-class@^3.0.0:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
-  integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=
-  dependencies:
-    capture-stack-trace "^1.0.0"
-
-create-hash@^1.1.0, create-hash@^1.1.2:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
-  integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
-  dependencies:
-    cipher-base "^1.0.1"
-    inherits "^2.0.1"
-    md5.js "^1.3.4"
-    ripemd160 "^2.0.1"
-    sha.js "^2.4.0"
-
-create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
-  version "1.1.7"
-  resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
-  integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==
-  dependencies:
-    cipher-base "^1.0.3"
-    create-hash "^1.1.0"
-    inherits "^2.0.1"
-    ripemd160 "^2.0.0"
-    safe-buffer "^5.0.1"
-    sha.js "^2.4.8"
-
-cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
-  version "6.0.5"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
-  integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
-  dependencies:
-    nice-try "^1.0.4"
-    path-key "^2.0.1"
-    semver "^5.5.0"
-    shebang-command "^1.2.0"
-    which "^1.2.9"
-
-cross-spawn@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
-  integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI=
-  dependencies:
-    lru-cache "^4.0.1"
-    which "^1.2.9"
-
-cross-spawn@^5.0.1:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
-  integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
-  dependencies:
-    lru-cache "^4.0.1"
-    shebang-command "^1.2.0"
-    which "^1.2.9"
-
-crypt@~0.0.1:
-  version "0.0.2"
-  resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
-  integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
-
-crypto-browserify@^3.11.0:
-  version "3.12.0"
-  resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
-  integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==
-  dependencies:
-    browserify-cipher "^1.0.0"
-    browserify-sign "^4.0.0"
-    create-ecdh "^4.0.0"
-    create-hash "^1.1.0"
-    create-hmac "^1.1.0"
-    diffie-hellman "^5.0.0"
-    inherits "^2.0.1"
-    pbkdf2 "^3.0.3"
-    public-encrypt "^4.0.0"
-    randombytes "^2.0.0"
-    randomfill "^1.0.3"
-
-crypto-random-string@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
-  integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
-
-css-loader@^3.0.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.2.0.tgz#bb570d89c194f763627fcf1f80059c6832d009b2"
-  integrity sha512-QTF3Ud5H7DaZotgdcJjGMvyDj5F3Pn1j/sC6VBEOVp94cbwqyIBdcs/quzj4MC1BKQSrTpQznegH/5giYbhnCQ==
-  dependencies:
-    camelcase "^5.3.1"
-    cssesc "^3.0.0"
-    icss-utils "^4.1.1"
-    loader-utils "^1.2.3"
-    normalize-path "^3.0.0"
-    postcss "^7.0.17"
-    postcss-modules-extract-imports "^2.0.0"
-    postcss-modules-local-by-default "^3.0.2"
-    postcss-modules-scope "^2.1.0"
-    postcss-modules-values "^3.0.0"
-    postcss-value-parser "^4.0.0"
-    schema-utils "^2.0.0"
-
-css-select@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
-  integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=
-  dependencies:
-    boolbase "~1.0.0"
-    css-what "2.1"
-    domutils "1.5.1"
-    nth-check "~1.0.1"
-
-css-what@2.1:
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
-  integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
-
-cssesc@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703"
-  integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==
-
-cssesc@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
-  integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
-
-currently-unhandled@^0.4.1:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
-  integrity sha1-mI3zP+qxke95mmE2nddsF635V+o=
-  dependencies:
-    array-find-index "^1.0.1"
-
-cyclist@~0.2.2:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
-  integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
-
-dargs@^4.0.1:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/dargs/-/dargs-4.1.0.tgz#03a9dbb4b5c2f139bf14ae53f0b8a2a6a86f4e17"
-  integrity sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=
-  dependencies:
-    number-is-nan "^1.0.0"
-
-dashdash@^1.12.0:
-  version "1.14.1"
-  resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
-  integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
-  dependencies:
-    assert-plus "^1.0.0"
-
-data-uri-to-buffer@2:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.1.tgz#ca8f56fe38b1fd329473e9d1b4a9afcd8ce1c045"
-  integrity sha512-OkVVLrerfAKZlW2ZZ3Ve2y65jgiWqBKsTfUIAFbn8nVbPcCZg6l6gikKlEYv0kXcmzqGm6mFq/Jf2vriuEkv8A==
-  dependencies:
-    "@types/node" "^8.0.7"
-
-date-fns@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.1.tgz#c5f30e31d3294918e6b6a82753a4e719120e203d"
-  integrity sha512-C14oTzTZy8DH1Eq8N78owrCWvf3+cnJw88BTK/N3DYWVxDJuJzPaNdplzYxDYuuXXGvqBcO4Vy5SOrwAooXSWw==
-
-date-now@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
-  integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=
-
-dateformat@^3.0.0:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
-  integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
-
-de-indent@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
-  integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
-
-debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
-  version "2.6.9"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
-  integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
-  dependencies:
-    ms "2.0.0"
-
-debug@3.1.0, debug@~3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
-  integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
-  dependencies:
-    ms "2.0.0"
-
-debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
-  integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
-  dependencies:
-    ms "^2.1.1"
-
-debug@^3.1.0, debug@^3.2.5, debug@^3.2.6:
-  version "3.2.6"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
-  integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
-  dependencies:
-    ms "^2.1.1"
-
-debuglog@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
-  integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
-
-decamelize-keys@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
-  integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=
-  dependencies:
-    decamelize "^1.1.0"
-    map-obj "^1.0.0"
-
-decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
-  integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
-
-decode-uri-component@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
-  integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
-
-decompress-response@^3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
-  integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
-  dependencies:
-    mimic-response "^1.0.0"
-
-dedent@^0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
-  integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
-
-deep-equal@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
-  integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=
-
-deep-extend@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
-  integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
-
-deep-is@~0.1.3:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
-  integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
-
-deepmerge@4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.0.0.tgz#3e3110ca29205f120d7cb064960a39c3d2087c09"
-  integrity sha512-YZ1rOP5+kHor4hMAH+HRQnBQHg+wvS1un1hAOuIcxcBy0hzcUf6Jg2a1w65kpoOUnurOfZbERwjI1TfZxNjcww==
-
-default-gateway@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
-  integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==
-  dependencies:
-    execa "^1.0.0"
-    ip-regex "^2.1.0"
-
-defaults@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
-  integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=
-  dependencies:
-    clone "^1.0.2"
-
-defer-to-connect@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.0.2.tgz#4bae758a314b034ae33902b5aac25a8dd6a8633e"
-  integrity sha512-k09hcQcTDY+cwgiwa6PYKLm3jlagNzQ+RSvhjzESOGOx+MNOuXkxTfEvPrO1IOQ81tArCFYQgi631clB70RpQw==
-
-define-properties@^1.1.2, define-properties@^1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
-  integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
-  dependencies:
-    object-keys "^1.0.12"
-
-define-property@^0.2.5:
-  version "0.2.5"
-  resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
-  integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=
-  dependencies:
-    is-descriptor "^0.1.0"
-
-define-property@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6"
-  integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY=
-  dependencies:
-    is-descriptor "^1.0.0"
-
-define-property@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d"
-  integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==
-  dependencies:
-    is-descriptor "^1.0.2"
-    isobject "^3.0.1"
-
-degenerator@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095"
-  integrity sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=
-  dependencies:
-    ast-types "0.x.x"
-    escodegen "1.x.x"
-    esprima "3.x.x"
-
-del@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4"
-  integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==
-  dependencies:
-    "@types/glob" "^7.1.1"
-    globby "^6.1.0"
-    is-path-cwd "^2.0.0"
-    is-path-in-cwd "^2.0.0"
-    p-map "^2.0.0"
-    pify "^4.0.1"
-    rimraf "^2.6.3"
-
-delayed-stream@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
-  integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
-
-delegates@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
-  integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
-
-depd@~1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
-  integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
-
-deprecation@^2.0.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
-  integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
-
-des.js@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
-  integrity sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=
-  dependencies:
-    inherits "^2.0.1"
-    minimalistic-assert "^1.0.0"
-
-destroy@~1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
-  integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
-
-detect-file@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
-  integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
-
-detect-indent@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
-  integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
-
-detect-libc@^1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
-  integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
-
-detect-node@^2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
-  integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
-
-dezalgo@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456"
-  integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=
-  dependencies:
-    asap "^2.0.0"
-    wrappy "1"
-
-diff@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
-  integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
-
-diffie-hellman@^5.0.0:
-  version "5.0.3"
-  resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
-  integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==
-  dependencies:
-    bn.js "^4.1.0"
-    miller-rabin "^4.0.0"
-    randombytes "^2.0.0"
-
-dir-glob@^2.2.2:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
-  integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==
-  dependencies:
-    path-type "^3.0.0"
-
-discord.js@^11.5.1:
-  version "11.5.1"
-  resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-11.5.1.tgz#910fb9f6410328581093e044cafb661783a4d9e8"
-  integrity sha512-tGhV5xaZXE3Z+4uXJb3hYM6gQ1NmnSxp9PClcsSAYFVRzH6AJH74040mO3afPDMWEAlj8XsoPXXTJHTxesqcGw==
-  dependencies:
-    long "^4.0.0"
-    prism-media "^0.0.3"
-    snekfetch "^3.6.4"
-    tweetnacl "^1.0.0"
-    ws "^6.0.0"
-
-dns-equal@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
-  integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0=
-
-dns-packet@^1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a"
-  integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==
-  dependencies:
-    ip "^1.1.0"
-    safe-buffer "^5.0.1"
-
-dns-txt@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6"
-  integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=
-  dependencies:
-    buffer-indexof "^1.0.0"
-
-dockerfile-ast@0.0.16:
-  version "0.0.16"
-  resolved "https://registry.yarnpkg.com/dockerfile-ast/-/dockerfile-ast-0.0.16.tgz#10b329d343329dab1de70375833495f85ad65913"
-  integrity sha512-+HZToHjjiLPl46TqBrok5dMrg5oCkZFPSROMQjRmvin0zG4FxK0DJXTpV/CUPYY2zpmEvVza55XLwSHFx/xZMw==
-  dependencies:
-    vscode-languageserver-types "^3.5.0"
-
-doctrine@1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
-  integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=
-  dependencies:
-    esutils "^2.0.2"
-    isarray "^1.0.0"
-
-doctrine@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
-  integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
-  dependencies:
-    esutils "^2.0.2"
-
-dom-converter@^0.2:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
-  integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==
-  dependencies:
-    utila "~0.4"
-
-dom-serializer@0:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.1.tgz#13650c850daffea35d8b626a4cfc4d3a17643fdb"
-  integrity sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==
-  dependencies:
-    domelementtype "^2.0.1"
-    entities "^2.0.0"
-
-domain-browser@^1.1.1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
-  integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==
-
-domelementtype@1, domelementtype@^1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
-  integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
-
-domelementtype@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d"
-  integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==
-
-domhandler@^2.3.0:
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
-  integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==
-  dependencies:
-    domelementtype "1"
-
-domutils@1.5.1:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
-  integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=
-  dependencies:
-    dom-serializer "0"
-    domelementtype "1"
-
-domutils@^1.5.1:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
-  integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
-  dependencies:
-    dom-serializer "0"
-    domelementtype "1"
-
-dot-prop@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177"
-  integrity sha1-G3CK8JSknJoOfbyteQq6U52sEXc=
-  dependencies:
-    is-obj "^1.0.0"
-
-dot-prop@^4.1.0, dot-prop@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57"
-  integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==
-  dependencies:
-    is-obj "^1.0.0"
-
-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"
-  integrity sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=
-
-duplexer3@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
-  integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
-
-duplexer@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
-  integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=
-
-duplexify@^3.4.2, duplexify@^3.6.0:
-  version "3.7.1"
-  resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
-  integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
-  dependencies:
-    end-of-stream "^1.0.0"
-    inherits "^2.0.1"
-    readable-stream "^2.0.0"
-    stream-shift "^1.0.0"
-
-ecc-jsbn@~0.1.1:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
-  integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
-  dependencies:
-    jsbn "~0.1.0"
-    safer-buffer "^2.1.0"
-
-ee-first@1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
-  integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
-
-ejs@^2.6.1:
-  version "2.6.2"
-  resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.2.tgz#3a32c63d1cd16d11266cd4703b14fec4e74ab4f6"
-  integrity sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q==
-
-electron-to-chromium@^1.3.191:
-  version "1.3.219"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.219.tgz#b8bc7c72fc6d5d5eeee57288eba4bfec5f070fa8"
-  integrity sha512-xANtM7YNFQGCMl+a0ZceXnPedpAatcIIyDNM56nQKzJFwuCyIzKVtBvLzyMOU0cczwO900TP309EkSeudrGRbQ==
-
-elliptic@^6.0.0:
-  version "6.5.0"
-  resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.0.tgz#2b8ed4c891b7de3200e14412a5b8248c7af505ca"
-  integrity sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==
-  dependencies:
-    bn.js "^4.4.0"
-    brorand "^1.0.1"
-    hash.js "^1.0.0"
-    hmac-drbg "^1.0.0"
-    inherits "^2.0.1"
-    minimalistic-assert "^1.0.0"
-    minimalistic-crypto-utils "^1.0.0"
-
-email-validator@^2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed"
-  integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ==
-
-emoji-regex@^7.0.1:
-  version "7.0.3"
-  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
-  integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
-
-emojis-list@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
-  integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k=
-
-encodeurl@~1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
-  integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
-
-encoding@0.1.12, encoding@^0.1.11:
-  version "0.1.12"
-  resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
-  integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
-  dependencies:
-    iconv-lite "~0.4.13"
-
-end-of-stream@^1.0.0, end-of-stream@^1.1.0:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
-  integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==
-  dependencies:
-    once "^1.4.0"
-
-engine.io-client@~3.3.1:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.3.2.tgz#04e068798d75beda14375a264bb3d742d7bc33aa"
-  integrity sha512-y0CPINnhMvPuwtqXfsGuWE8BB66+B6wTtCofQDRecMQPYX3MYUZXFNKDhdrSe3EVjgOu4V3rxdeqN/Tr91IgbQ==
-  dependencies:
-    component-emitter "1.2.1"
-    component-inherit "0.0.3"
-    debug "~3.1.0"
-    engine.io-parser "~2.1.1"
-    has-cors "1.1.0"
-    indexof "0.0.1"
-    parseqs "0.0.5"
-    parseuri "0.0.5"
-    ws "~6.1.0"
-    xmlhttprequest-ssl "~1.5.4"
-    yeast "0.1.2"
-
-engine.io-parser@~2.1.0, engine.io-parser@~2.1.1:
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.3.tgz#757ab970fbf2dfb32c7b74b033216d5739ef79a6"
-  integrity sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==
-  dependencies:
-    after "0.8.2"
-    arraybuffer.slice "~0.0.7"
-    base64-arraybuffer "0.1.5"
-    blob "0.0.5"
-    has-binary2 "~1.0.2"
-
-engine.io@~3.3.1:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.3.2.tgz#18cbc8b6f36e9461c5c0f81df2b830de16058a59"
-  integrity sha512-AsaA9KG7cWPXWHp5FvHdDWY3AMWeZ8x+2pUVLcn71qE5AtAzgGbxuclOytygskw8XGmiQafTmnI9Bix3uihu2w==
-  dependencies:
-    accepts "~1.3.4"
-    base64id "1.0.0"
-    cookie "0.3.1"
-    debug "~3.1.0"
-    engine.io-parser "~2.1.0"
-    ws "~6.1.0"
-
-enhanced-resolve@4.1.0, enhanced-resolve@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
-  integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==
-  dependencies:
-    graceful-fs "^4.1.2"
-    memory-fs "^0.4.0"
-    tapable "^1.0.0"
-
-entities@^1.1.1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
-  integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
-
-entities@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
-  integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
-
-env-paths@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
-  integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=
-
-err-code@^1.0.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960"
-  integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=
-
-errno@^0.1.3, errno@~0.1.7:
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
-  integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==
-  dependencies:
-    prr "~1.0.1"
-
-error-ex@^1.2.0, error-ex@^1.3.1:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
-  integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
-  dependencies:
-    is-arrayish "^0.2.1"
-
-es-abstract@^1.12.0, es-abstract@^1.4.3, es-abstract@^1.5.1, es-abstract@^1.7.0:
-  version "1.13.0"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
-  integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==
-  dependencies:
-    es-to-primitive "^1.2.0"
-    function-bind "^1.1.1"
-    has "^1.0.3"
-    is-callable "^1.1.4"
-    is-regex "^1.0.4"
-    object-keys "^1.0.12"
-
-es-to-primitive@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
-  integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==
-  dependencies:
-    is-callable "^1.1.4"
-    is-date-object "^1.0.1"
-    is-symbol "^1.0.2"
-
-es6-promise@^4.0.3:
-  version "4.2.8"
-  resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
-  integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
-
-es6-promisify@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
-  integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
-  dependencies:
-    es6-promise "^4.0.3"
-
-escape-html@~1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
-  integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
-
-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"
-  integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
-
-escodegen@1.x.x:
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510"
-  integrity sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==
-  dependencies:
-    esprima "^3.1.3"
-    estraverse "^4.2.0"
-    esutils "^2.0.2"
-    optionator "^0.8.1"
-  optionalDependencies:
-    source-map "~0.6.1"
-
-eslint-config-airbnb-base@14.0.0:
-  version "14.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.0.0.tgz#8a7bcb9643d13c55df4dd7444f138bf4efa61e17"
-  integrity sha512-2IDHobw97upExLmsebhtfoD3NAKhV4H0CJWP3Uprd/uk+cHuWYOczPVxQ8PxLFUAw7o3Th1RAU8u1DoUpr+cMA==
-  dependencies:
-    confusing-browser-globals "^1.0.7"
-    object.assign "^4.1.0"
-    object.entries "^1.1.0"
-
-eslint-config-airbnb-base@^13.2.0:
-  version "13.2.0"
-  resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.2.0.tgz#f6ea81459ff4dec2dda200c35f1d8f7419d57943"
-  integrity sha512-1mg/7eoB4AUeB0X1c/ho4vb2gYkNH8Trr/EgCT/aGmKhhG+F6vF5s8+iRBlWAzFIAphxIdp3YfEKgEl0f9Xg+w==
-  dependencies:
-    confusing-browser-globals "^1.0.5"
-    object.assign "^4.1.0"
-    object.entries "^1.1.0"
-
-eslint-config-prettier@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.0.0.tgz#f429a53bde9fc7660e6353910fd996d6284d3c25"
-  integrity sha512-vDrcCFE3+2ixNT5H83g28bO/uYAwibJxerXPj+E7op4qzBCsAV36QfvdAyVOoNxKAH2Os/e01T/2x++V0LPukA==
-  dependencies:
-    get-stdin "^6.0.0"
-
-eslint-import-resolver-node@^0.3.2:
-  version "0.3.2"
-  resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a"
-  integrity sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==
-  dependencies:
-    debug "^2.6.9"
-    resolve "^1.5.0"
-
-eslint-loader@^2.2.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.2.1.tgz#28b9c12da54057af0845e2a6112701a2f6bf8337"
-  integrity sha512-RLgV9hoCVsMLvOxCuNjdqOrUqIj9oJg8hF44vzJaYqsAHuY9G2YAeN3joQ9nxP0p5Th9iFSIpKo+SD8KISxXRg==
-  dependencies:
-    loader-fs-cache "^1.0.0"
-    loader-utils "^1.0.2"
-    object-assign "^4.0.1"
-    object-hash "^1.1.4"
-    rimraf "^2.6.1"
-
-eslint-module-utils@^2.4.0:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz#7b4675875bf96b0dbf1b21977456e5bb1f5e018c"
-  integrity sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==
-  dependencies:
-    debug "^2.6.8"
-    pkg-dir "^2.0.0"
-
-eslint-plugin-import@^2.17.2, eslint-plugin-import@^2.18.2:
-  version "2.18.2"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6"
-  integrity sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==
-  dependencies:
-    array-includes "^3.0.3"
-    contains-path "^0.1.0"
-    debug "^2.6.9"
-    doctrine "1.5.0"
-    eslint-import-resolver-node "^0.3.2"
-    eslint-module-utils "^2.4.0"
-    has "^1.0.3"
-    minimatch "^3.0.4"
-    object.values "^1.1.0"
-    read-pkg-up "^2.0.0"
-    resolve "^1.11.0"
-
-eslint-plugin-prettier@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.0.tgz#8695188f95daa93b0dc54b249347ca3b79c4686d"
-  integrity sha512-XWX2yVuwVNLOUhQijAkXz+rMPPoCr7WFiAl8ig6I7Xn+pPVhDhzg4DxHpmbeb0iqjO9UronEA3Tb09ChnFVHHA==
-  dependencies:
-    prettier-linter-helpers "^1.0.0"
-
-eslint-plugin-vue@^5.2.3:
-  version "5.2.3"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.2.3.tgz#3ee7597d823b5478804b2feba9863b1b74273961"
-  integrity sha512-mGwMqbbJf0+VvpGR5Lllq0PMxvTdrZ/ZPjmhkacrCHbubJeJOt+T6E3HUzAifa2Mxi7RSdJfC9HFpOeSYVMMIw==
-  dependencies:
-    vue-eslint-parser "^5.0.0"
-
-eslint-scope@3.7.1:
-  version "3.7.1"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
-  integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=
-  dependencies:
-    esrecurse "^4.1.0"
-    estraverse "^4.1.1"
-
-eslint-scope@^4.0.0, eslint-scope@^4.0.3:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
-  integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==
-  dependencies:
-    esrecurse "^4.1.0"
-    estraverse "^4.1.1"
-
-eslint-scope@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
-  integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
-  dependencies:
-    esrecurse "^4.1.0"
-    estraverse "^4.1.1"
-
-eslint-utils@^1.3.1:
-  version "1.4.2"
-  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab"
-  integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==
-  dependencies:
-    eslint-visitor-keys "^1.0.0"
-
-eslint-visitor-keys@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
-  integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
-
-eslint@6.1.0, eslint@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.1.0.tgz#06438a4a278b1d84fb107d24eaaa35471986e646"
-  integrity sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==
-  dependencies:
-    "@babel/code-frame" "^7.0.0"
-    ajv "^6.10.0"
-    chalk "^2.1.0"
-    cross-spawn "^6.0.5"
-    debug "^4.0.1"
-    doctrine "^3.0.0"
-    eslint-scope "^5.0.0"
-    eslint-utils "^1.3.1"
-    eslint-visitor-keys "^1.0.0"
-    espree "^6.0.0"
-    esquery "^1.0.1"
-    esutils "^2.0.2"
-    file-entry-cache "^5.0.1"
-    functional-red-black-tree "^1.0.1"
-    glob-parent "^5.0.0"
-    globals "^11.7.0"
-    ignore "^4.0.6"
-    import-fresh "^3.0.0"
-    imurmurhash "^0.1.4"
-    inquirer "^6.4.1"
-    is-glob "^4.0.0"
-    js-yaml "^3.13.1"
-    json-stable-stringify-without-jsonify "^1.0.1"
-    levn "^0.3.0"
-    lodash "^4.17.14"
-    minimatch "^3.0.4"
-    mkdirp "^0.5.1"
-    natural-compare "^1.4.0"
-    optionator "^0.8.2"
-    progress "^2.0.0"
-    regexpp "^2.0.1"
-    semver "^6.1.2"
-    strip-ansi "^5.2.0"
-    strip-json-comments "^3.0.1"
-    table "^5.2.3"
-    text-table "^0.2.0"
-    v8-compile-cache "^2.0.3"
-
-espree@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-4.1.0.tgz#728d5451e0fd156c04384a7ad89ed51ff54eb25f"
-  integrity sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==
-  dependencies:
-    acorn "^6.0.2"
-    acorn-jsx "^5.0.0"
-    eslint-visitor-keys "^1.0.0"
-
-espree@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-6.0.0.tgz#716fc1f5a245ef5b9a7fdb1d7b0d3f02322e75f6"
-  integrity sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==
-  dependencies:
-    acorn "^6.0.7"
-    acorn-jsx "^5.0.0"
-    eslint-visitor-keys "^1.0.0"
-
-esprima@3.x.x, esprima@^3.1.3:
-  version "3.1.3"
-  resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
-  integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=
-
-esprima@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
-  integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
-
-esquery@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708"
-  integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==
-  dependencies:
-    estraverse "^4.0.0"
-
-esrecurse@^4.1.0:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
-  integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==
-  dependencies:
-    estraverse "^4.1.0"
-
-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"
-  integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
-
-esutils@^2.0.2:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
-  integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
-
-etag@~1.8.1:
-  version "1.8.1"
-  resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
-  integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
-
-eventemitter3@^3.0.0, eventemitter3@^3.1.0:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
-  integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
-
-events@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88"
-  integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==
-
-eventsource@^1.0.7:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0"
-  integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==
-  dependencies:
-    original "^1.0.0"
-
-evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02"
-  integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==
-  dependencies:
-    md5.js "^1.3.4"
-    safe-buffer "^5.1.1"
-
-execa@^0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
-  integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=
-  dependencies:
-    cross-spawn "^5.0.1"
-    get-stream "^3.0.0"
-    is-stream "^1.1.0"
-    npm-run-path "^2.0.0"
-    p-finally "^1.0.0"
-    signal-exit "^3.0.0"
-    strip-eof "^1.0.0"
-
-execa@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
-  integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
-  dependencies:
-    cross-spawn "^6.0.0"
-    get-stream "^4.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"
-
-expand-brackets@^2.1.4:
-  version "2.1.4"
-  resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
-  integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI=
-  dependencies:
-    debug "^2.3.3"
-    define-property "^0.2.5"
-    extend-shallow "^2.0.1"
-    posix-character-classes "^0.1.0"
-    regex-not "^1.0.0"
-    snapdragon "^0.8.1"
-    to-regex "^3.0.1"
-
-expand-tilde@^2.0.0, expand-tilde@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
-  integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=
-  dependencies:
-    homedir-polyfill "^1.0.1"
-
-express@^4.16.3, express@^4.17.1:
-  version "4.17.1"
-  resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
-  integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
-  dependencies:
-    accepts "~1.3.7"
-    array-flatten "1.1.1"
-    body-parser "1.19.0"
-    content-disposition "0.5.3"
-    content-type "~1.0.4"
-    cookie "0.4.0"
-    cookie-signature "1.0.6"
-    debug "2.6.9"
-    depd "~1.1.2"
-    encodeurl "~1.0.2"
-    escape-html "~1.0.3"
-    etag "~1.8.1"
-    finalhandler "~1.1.2"
-    fresh "0.5.2"
-    merge-descriptors "1.0.1"
-    methods "~1.1.2"
-    on-finished "~2.3.0"
-    parseurl "~1.3.3"
-    path-to-regexp "0.1.7"
-    proxy-addr "~2.0.5"
-    qs "6.7.0"
-    range-parser "~1.2.1"
-    safe-buffer "5.1.2"
-    send "0.17.1"
-    serve-static "1.14.1"
-    setprototypeof "1.1.1"
-    statuses "~1.5.0"
-    type-is "~1.6.18"
-    utils-merge "1.0.1"
-    vary "~1.1.2"
-
-extend-shallow@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
-  integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=
-  dependencies:
-    is-extendable "^0.1.0"
-
-extend-shallow@^3.0.0, extend-shallow@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8"
-  integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=
-  dependencies:
-    assign-symbols "^1.0.0"
-    is-extendable "^1.0.1"
-
-extend@~3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
-  integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
-
-external-editor@^3.0.3:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495"
-  integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==
-  dependencies:
-    chardet "^0.7.0"
-    iconv-lite "^0.4.24"
-    tmp "^0.0.33"
-
-extglob@^2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
-  integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==
-  dependencies:
-    array-unique "^0.3.2"
-    define-property "^1.0.0"
-    expand-brackets "^2.1.4"
-    extend-shallow "^2.0.1"
-    fragment-cache "^0.2.1"
-    regex-not "^1.0.0"
-    snapdragon "^0.8.1"
-    to-regex "^3.0.1"
-
-extsprintf@1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
-  integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
-
-extsprintf@^1.2.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
-  integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
-
-fast-deep-equal@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
-  integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
-
-fast-diff@^1.1.2:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
-  integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
-
-fast-glob@^2.2.6:
-  version "2.2.7"
-  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d"
-  integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==
-  dependencies:
-    "@mrmlnc/readdir-enhanced" "^2.2.1"
-    "@nodelib/fs.stat" "^1.1.2"
-    glob-parent "^3.1.0"
-    is-glob "^4.0.0"
-    merge2 "^1.2.3"
-    micromatch "^3.1.10"
-
-fast-json-stable-stringify@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
-  integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
-
-fast-levenshtein@~2.0.4:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
-  integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
-
-faye-websocket@^0.10.0:
-  version "0.10.0"
-  resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
-  integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=
-  dependencies:
-    websocket-driver ">=0.5.1"
-
-faye-websocket@~0.11.1:
-  version "0.11.3"
-  resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
-  integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==
-  dependencies:
-    websocket-driver ">=0.5.1"
-
-fetch@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/fetch/-/fetch-1.1.0.tgz#0a8279f06be37f9f0ebb567560a30a480da59a2e"
-  integrity sha1-CoJ58Gvjf58Ou1Z1YKMKSA2lmi4=
-  dependencies:
-    biskviit "1.0.1"
-    encoding "0.1.12"
-
-figgy-pudding@^3.4.1, figgy-pudding@^3.5.1:
-  version "3.5.1"
-  resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
-  integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==
-
-figures@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
-  integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
-  dependencies:
-    escape-string-regexp "^1.0.5"
-
-file-entry-cache@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c"
-  integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==
-  dependencies:
-    flat-cache "^2.0.1"
-
-file-uri-to-path@1:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
-  integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
-
-filesize@^3.6.1:
-  version "3.6.1"
-  resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317"
-  integrity sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==
-
-fill-range@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
-  integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=
-  dependencies:
-    extend-shallow "^2.0.1"
-    is-number "^3.0.0"
-    repeat-string "^1.6.1"
-    to-regex-range "^2.1.0"
-
-finalhandler@~1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
-  integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
-  dependencies:
-    debug "2.6.9"
-    encodeurl "~1.0.2"
-    escape-html "~1.0.3"
-    on-finished "~2.3.0"
-    parseurl "~1.3.3"
-    statuses "~1.5.0"
-    unpipe "~1.0.0"
-
-find-cache-dir@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9"
-  integrity sha1-yN765XyKUqinhPnjHFfHQumToLk=
-  dependencies:
-    commondir "^1.0.1"
-    mkdirp "^0.5.1"
-    pkg-dir "^1.0.0"
-
-find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
-  integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
-  dependencies:
-    commondir "^1.0.1"
-    make-dir "^2.0.0"
-    pkg-dir "^3.0.0"
-
-find-up@^1.0.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
-  integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=
-  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"
-  integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
-  dependencies:
-    locate-path "^2.0.0"
-
-find-up@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
-  integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
-  dependencies:
-    locate-path "^3.0.0"
-
-find-up@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
-  integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
-  dependencies:
-    locate-path "^5.0.0"
-    path-exists "^4.0.0"
-
-findup-sync@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1"
-  integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==
-  dependencies:
-    detect-file "^1.0.0"
-    is-glob "^4.0.0"
-    micromatch "^3.0.4"
-    resolve-dir "^1.0.1"
-
-flat-cache@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
-  integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==
-  dependencies:
-    flatted "^2.0.0"
-    rimraf "2.6.3"
-    write "1.0.3"
-
-flatted@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
-  integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==
-
-flush-write-stream@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
-  integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==
-  dependencies:
-    inherits "^2.0.3"
-    readable-stream "^2.3.6"
-
-follow-redirects@^1.0.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"
-  integrity sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==
-  dependencies:
-    debug "^3.2.6"
-
-for-in@^0.1.3:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
-  integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=
-
-for-in@^1.0.1, for-in@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
-  integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
-
-for-own@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b"
-  integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=
-  dependencies:
-    for-in "^1.0.1"
-
-forever-agent@~0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
-  integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
-
-form-data@^2.3.3:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.0.tgz#094ec359dc4b55e7d62e0db4acd76e89fe874d37"
-  integrity sha512-WXieX3G/8side6VIqx44ablyULoGruSde5PNTxoUyo5CeyAMX6nVWUd0rgist/EuX655cjhUhTo1Fo3tRYqbcA==
-  dependencies:
-    asynckit "^0.4.0"
-    combined-stream "^1.0.6"
-    mime-types "^2.1.12"
-
-form-data@~2.3.2:
-  version "2.3.3"
-  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
-  integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
-  dependencies:
-    asynckit "^0.4.0"
-    combined-stream "^1.0.6"
-    mime-types "^2.1.12"
-
-forwarded@~0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
-  integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
-
-fragment-cache@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
-  integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=
-  dependencies:
-    map-cache "^0.2.2"
-
-fresh@0.5.2:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
-  integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
-
-from2@^2.1.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
-  integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=
-  dependencies:
-    inherits "^2.0.1"
-    readable-stream "^2.0.0"
-
-fs-extra@^8.1.0:
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
-  integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
-  dependencies:
-    graceful-fs "^4.2.0"
-    jsonfile "^4.0.0"
-    universalify "^0.1.0"
-
-fs-minipass@^1.2.5:
-  version "1.2.6"
-  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07"
-  integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==
-  dependencies:
-    minipass "^2.2.1"
-
-fs-write-stream-atomic@^1.0.8:
-  version "1.0.10"
-  resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
-  integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=
-  dependencies:
-    graceful-fs "^4.1.2"
-    iferr "^0.1.5"
-    imurmurhash "^0.1.4"
-    readable-stream "1 || 2"
-
-fs.realpath@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-  integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
-
-fsevents@^1.2.7:
-  version "1.2.9"
-  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f"
-  integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==
-  dependencies:
-    nan "^2.12.1"
-    node-pre-gyp "^0.12.0"
-
-fstream@^1.0.0, fstream@^1.0.12:
-  version "1.0.12"
-  resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
-  integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
-  dependencies:
-    graceful-fs "^4.1.2"
-    inherits "~2.0.0"
-    mkdirp ">=0.5 0"
-    rimraf "2"
-
-ftp@~0.3.10:
-  version "0.3.10"
-  resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d"
-  integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=
-  dependencies:
-    readable-stream "1.1.x"
-    xregexp "2.0.0"
-
-function-bind@^1.0.2, function-bind@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
-  integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
-
-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"
-  integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
-
-gauge@~2.7.3:
-  version "2.7.4"
-  resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
-  integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=
-  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"
-
-gaze@^1.0.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a"
-  integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==
-  dependencies:
-    globule "^1.0.0"
-
-genfun@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537"
-  integrity sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==
-
-get-caller-file@^1.0.1:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
-  integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
-
-get-caller-file@^2.0.1:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
-  integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
-
-get-pkg-repo@^1.0.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d"
-  integrity sha1-xztInAbYDMVTbCyFP54FIyBWly0=
-  dependencies:
-    hosted-git-info "^2.1.4"
-    meow "^3.3.0"
-    normalize-package-data "^2.3.0"
-    parse-github-repo-url "^1.3.0"
-    through2 "^2.0.0"
-
-get-port@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119"
-  integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==
-
-get-stdin@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
-  integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=
-
-get-stdin@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
-  integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
-
-get-stdin@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6"
-  integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==
-
-get-stream@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
-  integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
-
-get-stream@^4.0.0, get-stream@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
-  integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
-  dependencies:
-    pump "^3.0.0"
-
-get-stream@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9"
-  integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==
-  dependencies:
-    pump "^3.0.0"
-
-get-uri@^2.0.0:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.3.tgz#fa13352269781d75162c6fc813c9e905323fbab5"
-  integrity sha512-x5j6Ks7FOgLD/GlvjKwgu7wdmMR55iuRHhn8hj/+gA+eSbxQvZ+AEomq+3MgVEZj1vpi738QahGbCCSIDtXtkw==
-  dependencies:
-    data-uri-to-buffer "2"
-    debug "4"
-    extend "~3.0.2"
-    file-uri-to-path "1"
-    ftp "~0.3.10"
-    readable-stream "3"
-
-get-value@^2.0.3, get-value@^2.0.6:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
-  integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
-
-getpass@^0.1.1:
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
-  integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
-  dependencies:
-    assert-plus "^1.0.0"
-
-git-raw-commits@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.0.tgz#d92addf74440c14bcc5c83ecce3fb7f8a79118b5"
-  integrity sha512-w4jFEJFgKXMQJ0H0ikBk2S+4KP2VEjhCvLCNqbNRQC8BgGWgLKNCO7a9K9LI+TVT7Gfoloje502sEnctibffgg==
-  dependencies:
-    dargs "^4.0.1"
-    lodash.template "^4.0.2"
-    meow "^4.0.0"
-    split2 "^2.0.0"
-    through2 "^2.0.0"
-
-git-remote-origin-url@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f"
-  integrity sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=
-  dependencies:
-    gitconfiglocal "^1.0.0"
-    pify "^2.3.0"
-
-git-semver-tags@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-2.0.3.tgz#48988a718acf593800f99622a952a77c405bfa34"
-  integrity sha512-tj4FD4ww2RX2ae//jSrXZzrocla9db5h0V7ikPl1P/WwoZar9epdUhwR7XHXSgc+ZkNq72BEEerqQuicoEQfzA==
-  dependencies:
-    meow "^4.0.0"
-    semver "^6.0.0"
-
-git-up@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.1.tgz#cb2ef086653640e721d2042fe3104857d89007c0"
-  integrity sha512-LFTZZrBlrCrGCG07/dm1aCjjpL1z9L3+5aEeI9SBhAqSc+kiA9Or1bgZhQFNppJX6h/f5McrvJt1mQXTFm6Qrw==
-  dependencies:
-    is-ssh "^1.3.0"
-    parse-url "^5.0.0"
-
-git-url-parse@11.1.2, git-url-parse@^11.1.2:
-  version "11.1.2"
-  resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.1.2.tgz#aff1a897c36cc93699270587bea3dbcbbb95de67"
-  integrity sha512-gZeLVGY8QVKMIkckncX+iCq2/L8PlwncvDFKiWkBn9EtCfYDbliRTTp6qzyQ1VMdITUfq7293zDzfpjdiGASSQ==
-  dependencies:
-    git-up "^4.0.0"
-
-gitconfiglocal@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b"
-  integrity sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=
-  dependencies:
-    ini "^1.3.2"
-
-glob-parent@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
-  integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=
-  dependencies:
-    is-glob "^3.1.0"
-    path-dirname "^1.0.0"
-
-glob-parent@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954"
-  integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==
-  dependencies:
-    is-glob "^4.0.1"
-
-glob-to-regexp@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
-  integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
-
-glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1:
-  version "7.1.4"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
-  integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
-  dependencies:
-    fs.realpath "^1.0.0"
-    inflight "^1.0.4"
-    inherits "2"
-    minimatch "^3.0.4"
-    once "^1.3.0"
-    path-is-absolute "^1.0.0"
-
-global-dirs@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445"
-  integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=
-  dependencies:
-    ini "^1.3.4"
-
-global-modules@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
-  integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==
-  dependencies:
-    global-prefix "^3.0.0"
-
-global-modules@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
-  integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==
-  dependencies:
-    global-prefix "^1.0.1"
-    is-windows "^1.0.1"
-    resolve-dir "^1.0.0"
-
-global-prefix@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe"
-  integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=
-  dependencies:
-    expand-tilde "^2.0.2"
-    homedir-polyfill "^1.0.1"
-    ini "^1.3.4"
-    is-windows "^1.0.1"
-    which "^1.2.14"
-
-global-prefix@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97"
-  integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==
-  dependencies:
-    ini "^1.3.5"
-    kind-of "^6.0.2"
-    which "^1.3.1"
-
-globals@^11.1.0, globals@^11.7.0:
-  version "11.12.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
-  integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
-
-globby@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
-  integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=
-  dependencies:
-    array-union "^1.0.1"
-    glob "^7.0.3"
-    object-assign "^4.0.1"
-    pify "^2.0.0"
-    pinkie-promise "^2.0.0"
-
-globby@^9.2.0:
-  version "9.2.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
-  integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==
-  dependencies:
-    "@types/glob" "^7.1.1"
-    array-union "^1.0.2"
-    dir-glob "^2.2.2"
-    fast-glob "^2.2.6"
-    glob "^7.1.3"
-    ignore "^4.0.3"
-    pify "^4.0.1"
-    slash "^2.0.0"
-
-globule@^1.0.0:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d"
-  integrity sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==
-  dependencies:
-    glob "~7.1.1"
-    lodash "~4.17.10"
-    minimatch "~3.0.2"
-
-got@^6.7.1:
-  version "6.7.1"
-  resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
-  integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=
-  dependencies:
-    create-error-class "^3.0.0"
-    duplexer3 "^0.1.4"
-    get-stream "^3.0.0"
-    is-redirect "^1.0.0"
-    is-retry-allowed "^1.0.0"
-    is-stream "^1.0.0"
-    lowercase-keys "^1.0.0"
-    safe-buffer "^5.0.1"
-    timed-out "^4.0.0"
-    unzip-response "^2.0.1"
-    url-parse-lax "^1.0.0"
-
-got@^9.6.0:
-  version "9.6.0"
-  resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
-  integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==
-  dependencies:
-    "@sindresorhus/is" "^0.14.0"
-    "@szmarczak/http-timer" "^1.1.2"
-    cacheable-request "^6.0.0"
-    decompress-response "^3.3.0"
-    duplexer3 "^0.1.4"
-    get-stream "^4.1.0"
-    lowercase-keys "^1.0.1"
-    mimic-response "^1.0.1"
-    p-cancelable "^1.0.0"
-    to-readable-stream "^1.0.0"
-    url-parse-lax "^3.0.0"
-
-graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.1.tgz#1c1f0c364882c868f5bff6512146328336a11b1d"
-  integrity sha512-b9usnbDGnD928gJB3LrCmxoibr3VE4U2SMo5PBuBnokWyDADTqDPXg4YpwKF1trpH+UbGp7QLicO3+aWEy0+mw==
-
-graphlib@^2.1.1, graphlib@^2.1.5:
-  version "2.1.7"
-  resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.7.tgz#b6a69f9f44bd9de3963ce6804a2fc9e73d86aecc"
-  integrity sha512-TyI9jIy2J4j0qgPmOOrHTCtpPqJGN/aurBwc6ZT+bRii+di1I+Wv3obRhVrmBEXet+qkMaEX67dXrwsd3QQM6w==
-  dependencies:
-    lodash "^4.17.5"
-
-gzip-size@^5.0.0:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"
-  integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==
-  dependencies:
-    duplexer "^0.1.1"
-    pify "^4.0.1"
-
-handle-thing@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754"
-  integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==
-
-handlebars@^4.1.2:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
-  integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==
-  dependencies:
-    neo-async "^2.6.0"
-    optimist "^0.6.1"
-    source-map "^0.6.1"
-  optionalDependencies:
-    uglify-js "^3.1.4"
-
-har-schema@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
-  integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
-
-har-validator@~5.1.0:
-  version "5.1.3"
-  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
-  integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
-  dependencies:
-    ajv "^6.5.5"
-    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"
-  integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=
-  dependencies:
-    ansi-regex "^2.0.0"
-
-has-binary2@~1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
-  integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==
-  dependencies:
-    isarray "2.0.1"
-
-has-cors@1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
-  integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=
-
-has-flag@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
-  integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
-
-has-symbols@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
-  integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=
-
-has-unicode@^2.0.0, has-unicode@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
-  integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
-
-has-value@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
-  integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=
-  dependencies:
-    get-value "^2.0.3"
-    has-values "^0.1.4"
-    isobject "^2.0.0"
-
-has-value@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177"
-  integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=
-  dependencies:
-    get-value "^2.0.6"
-    has-values "^1.0.0"
-    isobject "^3.0.0"
-
-has-values@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771"
-  integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E=
-
-has-values@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f"
-  integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=
-  dependencies:
-    is-number "^3.0.0"
-    kind-of "^4.0.0"
-
-has@^1.0.1, has@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
-  integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
-  dependencies:
-    function-bind "^1.1.1"
-
-hash-base@^3.0.0:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918"
-  integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=
-  dependencies:
-    inherits "^2.0.1"
-    safe-buffer "^5.0.1"
-
-hash-sum@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
-  integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=
-
-hash.js@^1.0.0, hash.js@^1.0.3:
-  version "1.1.7"
-  resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
-  integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
-  dependencies:
-    inherits "^2.0.3"
-    minimalistic-assert "^1.0.1"
-
-he@1.2.x, he@^1.1.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
-  integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
-
-hmac-drbg@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
-  integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
-  dependencies:
-    hash.js "^1.0.3"
-    minimalistic-assert "^1.0.0"
-    minimalistic-crypto-utils "^1.0.1"
-
-homedir-polyfill@^1.0.1:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
-  integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
-  dependencies:
-    parse-passwd "^1.0.0"
-
-hoopy@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
-  integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==
-
-hosted-git-info@^2.1.4, hosted-git-info@^2.6.0, hosted-git-info@^2.7.1:
-  version "2.8.2"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.2.tgz#a35c3f355ac1249f1093c0c2a542ace8818c171a"
-  integrity sha512-CyjlXII6LMsPMyUzxpTt8fzh5QwzGqPmQXgY/Jyf4Zfp27t/FvfhwoE/8laaMUcMy816CkWF20I7NeQhwwY88w==
-  dependencies:
-    lru-cache "^5.1.1"
-
-hpack.js@^2.1.6:
-  version "2.1.6"
-  resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2"
-  integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=
-  dependencies:
-    inherits "^2.0.1"
-    obuf "^1.0.0"
-    readable-stream "^2.0.1"
-    wbuf "^1.1.0"
-
-html-entities@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
-  integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=
-
-html-minifier@^3.2.3:
-  version "3.5.21"
-  resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.21.tgz#d0040e054730e354db008463593194015212d20c"
-  integrity sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==
-  dependencies:
-    camel-case "3.0.x"
-    clean-css "4.2.x"
-    commander "2.17.x"
-    he "1.2.x"
-    param-case "2.1.x"
-    relateurl "0.2.x"
-    uglify-js "3.4.x"
-
-html-webpack-plugin@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b"
-  integrity sha1-sBq71yOsqqeze2r0SS69oD2d03s=
-  dependencies:
-    html-minifier "^3.2.3"
-    loader-utils "^0.2.16"
-    lodash "^4.17.3"
-    pretty-error "^2.0.2"
-    tapable "^1.0.0"
-    toposort "^1.0.0"
-    util.promisify "1.0.0"
-
-htmlparser2@^3.3.0:
-  version "3.10.1"
-  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f"
-  integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==
-  dependencies:
-    domelementtype "^1.3.1"
-    domhandler "^2.3.0"
-    domutils "^1.5.1"
-    entities "^1.1.1"
-    inherits "^2.0.1"
-    readable-stream "^3.1.1"
-
-http-cache-semantics@^3.8.1:
-  version "3.8.1"
-  resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
-  integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==
-
-http-cache-semantics@^4.0.0:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#495704773277eeef6e43f9ab2c2c7d259dda25c5"
-  integrity sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew==
-
-http-deceiver@^1.2.7:
-  version "1.2.7"
-  resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
-  integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=
-
-http-errors@1.7.2:
-  version "1.7.2"
-  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
-  integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
-  dependencies:
-    depd "~1.1.2"
-    inherits "2.0.3"
-    setprototypeof "1.1.1"
-    statuses ">= 1.5.0 < 2"
-    toidentifier "1.0.0"
-
-http-errors@1.7.3, http-errors@~1.7.2:
-  version "1.7.3"
-  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
-  integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
-  dependencies:
-    depd "~1.1.2"
-    inherits "2.0.4"
-    setprototypeof "1.1.1"
-    statuses ">= 1.5.0 < 2"
-    toidentifier "1.0.0"
-
-http-errors@~1.6.2:
-  version "1.6.3"
-  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
-  integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=
-  dependencies:
-    depd "~1.1.2"
-    inherits "2.0.3"
-    setprototypeof "1.1.0"
-    statuses ">= 1.4.0 < 2"
-
-"http-parser-js@>=0.4.0 <0.4.11":
-  version "0.4.10"
-  resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4"
-  integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=
-
-http-proxy-agent@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
-  integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
-  dependencies:
-    agent-base "4"
-    debug "3.1.0"
-
-http-proxy-middleware@^0.19.1:
-  version "0.19.1"
-  resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a"
-  integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==
-  dependencies:
-    http-proxy "^1.17.0"
-    is-glob "^4.0.0"
-    lodash "^4.17.11"
-    micromatch "^3.1.10"
-
-http-proxy@^1.17.0:
-  version "1.17.0"
-  resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a"
-  integrity sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==
-  dependencies:
-    eventemitter3 "^3.0.0"
-    follow-redirects "^1.0.0"
-    requires-port "^1.0.0"
-
-http-signature@~1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
-  integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
-  dependencies:
-    assert-plus "^1.0.0"
-    jsprim "^1.2.2"
-    sshpk "^1.7.0"
-
-https-browserify@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
-  integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
-
-https-proxy-agent@^2.2.1:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz#271ea8e90f836ac9f119daccd39c19ff7dfb0793"
-  integrity sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==
-  dependencies:
-    agent-base "^4.3.0"
-    debug "^3.1.0"
-
-humanize-ms@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
-  integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=
-  dependencies:
-    ms "^2.0.0"
-
-husky@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/husky/-/husky-3.0.4.tgz#10a48ac11ab50859b0939750fa0b4e07ad0bf669"
-  integrity sha512-7Rnt8aJfy+MlV28snmYK7O7vWwtOfeVxV6KhLpUFXlmx5ukQ1nQmNUB7QsAwSgdySB5X+bm7q7JIRgazqBUzKA==
-  dependencies:
-    chalk "^2.4.2"
-    cosmiconfig "^5.2.1"
-    execa "^1.0.0"
-    get-stdin "^7.0.0"
-    is-ci "^2.0.0"
-    opencollective-postinstall "^2.0.2"
-    pkg-dir "^4.2.0"
-    please-upgrade-node "^3.2.0"
-    read-pkg "^5.1.1"
-    run-node "^1.0.0"
-    slash "^3.0.0"
-
-iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
-  version "0.4.24"
-  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
-  integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
-  dependencies:
-    safer-buffer ">= 2.1.2 < 3"
-
-icss-utils@^4.0.0, icss-utils@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467"
-  integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==
-  dependencies:
-    postcss "^7.0.14"
-
-ieee754@^1.1.4:
-  version "1.1.13"
-  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
-  integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
-
-iferr@^0.1.5:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
-  integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
-
-ignore-walk@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8"
-  integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==
-  dependencies:
-    minimatch "^3.0.4"
-
-ignore@^4.0.3, ignore@^4.0.6:
-  version "4.0.6"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
-  integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
-
-immediate@~3.0.5:
-  version "3.0.6"
-  resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
-  integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
-
-import-fresh@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
-  integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY=
-  dependencies:
-    caller-path "^2.0.0"
-    resolve-from "^3.0.0"
-
-import-fresh@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.1.0.tgz#6d33fa1dcef6df930fae003446f33415af905118"
-  integrity sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==
-  dependencies:
-    parent-module "^1.0.0"
-    resolve-from "^4.0.0"
-
-import-lazy@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
-  integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=
-
-import-local@2.0.0, import-local@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
-  integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==
-  dependencies:
-    pkg-dir "^3.0.0"
-    resolve-cwd "^2.0.0"
-
-imurmurhash@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
-  integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
-
-in-publish@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51"
-  integrity sha1-4g/146KvwmkDILbcVSaCqcf631E=
-
-indent-string@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
-  integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
-  dependencies:
-    repeating "^2.0.0"
-
-indent-string@^3.0.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
-  integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
-
-indexes-of@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
-  integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
-
-indexof@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
-  integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
-
-infer-owner@^1.0.3:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
-  integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
-
-inflection@~1.12.0:
-  version "1.12.0"
-  resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416"
-  integrity sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=
-
-inflection@~1.3.0:
-  version "1.3.8"
-  resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.3.8.tgz#cbd160da9f75b14c3cc63578d4f396784bf3014e"
-  integrity sha1-y9Fg2p91sUw8xjV41POWeEvzAU4=
-
-inflight@^1.0.4:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
-  integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
-  dependencies:
-    once "^1.3.0"
-    wrappy "1"
-
-inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
-  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
-
-inherits@2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
-  integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
-
-inherits@2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
-  integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-
-ini@^1.3.0, ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
-  version "1.3.5"
-  resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
-  integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
-
-init-package-json@^1.10.3:
-  version "1.10.3"
-  resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-1.10.3.tgz#45ffe2f610a8ca134f2bd1db5637b235070f6cbe"
-  integrity sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==
-  dependencies:
-    glob "^7.1.1"
-    npm-package-arg "^4.0.0 || ^5.0.0 || ^6.0.0"
-    promzard "^0.3.0"
-    read "~1.0.1"
-    read-package-json "1 || 2"
-    semver "2.x || 3.x || 4 || 5"
-    validate-npm-package-license "^3.0.1"
-    validate-npm-package-name "^3.0.0"
-
-inquirer@^6.2.0, inquirer@^6.2.2, inquirer@^6.4.1:
-  version "6.5.0"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42"
-  integrity sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==
-  dependencies:
-    ansi-escapes "^3.2.0"
-    chalk "^2.4.2"
-    cli-cursor "^2.1.0"
-    cli-width "^2.0.0"
-    external-editor "^3.0.3"
-    figures "^2.0.0"
-    lodash "^4.17.12"
-    mute-stream "0.0.7"
-    run-async "^2.2.0"
-    rxjs "^6.4.0"
-    string-width "^2.1.0"
-    strip-ansi "^5.1.0"
-    through "^2.3.6"
-
-internal-ip@^4.3.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
-  integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==
-  dependencies:
-    default-gateway "^4.2.0"
-    ipaddr.js "^1.9.0"
-
-interpret@1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
-  integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==
-
-invariant@^2.2.2:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
-  integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
-  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"
-  integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
-
-invert-kv@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
-  integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
-
-ip-regex@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
-  integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
-
-ip@^1.1.0, ip@^1.1.5:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
-  integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
-
-ipaddr.js@1.9.0:
-  version "1.9.0"
-  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
-  integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
-
-ipaddr.js@^1.9.0:
-  version "1.9.1"
-  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
-  integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
-
-is-accessor-descriptor@^0.1.6:
-  version "0.1.6"
-  resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
-  integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=
-  dependencies:
-    kind-of "^3.0.2"
-
-is-accessor-descriptor@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656"
-  integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==
-  dependencies:
-    kind-of "^6.0.0"
-
-is-arrayish@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
-  integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
-
-is-binary-path@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
-  integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=
-  dependencies:
-    binary-extensions "^1.0.0"
-
-is-buffer@^1.0.2, is-buffer@^1.1.5, is-buffer@~1.1.1:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
-  integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
-
-is-callable@^1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
-  integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==
-
-is-ci@^1.0.10:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c"
-  integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==
-  dependencies:
-    ci-info "^1.5.0"
-
-is-ci@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
-  integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==
-  dependencies:
-    ci-info "^2.0.0"
-
-is-data-descriptor@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
-  integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=
-  dependencies:
-    kind-of "^3.0.2"
-
-is-data-descriptor@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7"
-  integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==
-  dependencies:
-    kind-of "^6.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"
-  integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=
-
-is-descriptor@^0.1.0:
-  version "0.1.6"
-  resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
-  integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==
-  dependencies:
-    is-accessor-descriptor "^0.1.6"
-    is-data-descriptor "^0.1.4"
-    kind-of "^5.0.0"
-
-is-descriptor@^1.0.0, is-descriptor@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec"
-  integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==
-  dependencies:
-    is-accessor-descriptor "^1.0.0"
-    is-data-descriptor "^1.0.0"
-    kind-of "^6.0.2"
-
-is-directory@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
-  integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
-
-is-extendable@^0.1.0, is-extendable@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
-  integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=
-
-is-extendable@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4"
-  integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==
-  dependencies:
-    is-plain-object "^2.0.4"
-
-is-extglob@^2.1.0, is-extglob@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
-  integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
-
-is-finite@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
-  integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=
-  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"
-  integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
-  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"
-  integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
-
-is-glob@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
-  integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=
-  dependencies:
-    is-extglob "^2.1.0"
-
-is-glob@^4.0.0, is-glob@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
-  integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
-  dependencies:
-    is-extglob "^2.1.1"
-
-is-installed-globally@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
-  integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=
-  dependencies:
-    global-dirs "^0.1.0"
-    is-path-inside "^1.0.0"
-
-is-npm@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
-  integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ=
-
-is-number@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
-  integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=
-  dependencies:
-    kind-of "^3.0.2"
-
-is-obj@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
-  integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
-
-is-path-cwd@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb"
-  integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==
-
-is-path-in-cwd@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb"
-  integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==
-  dependencies:
-    is-path-inside "^2.1.0"
-
-is-path-inside@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
-  integrity sha1-jvW33lBDej/cprToZe96pVy0gDY=
-  dependencies:
-    path-is-inside "^1.0.1"
-
-is-path-inside@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2"
-  integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==
-  dependencies:
-    path-is-inside "^1.0.2"
-
-is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
-  integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
-
-is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
-  integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
-  dependencies:
-    isobject "^3.0.1"
-
-is-plain-object@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.0.tgz#47bfc5da1b5d50d64110806c199359482e75a928"
-  integrity sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==
-  dependencies:
-    isobject "^4.0.0"
-
-is-promise@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
-  integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=
-
-is-redirect@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
-  integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=
-
-is-regex@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
-  integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=
-  dependencies:
-    has "^1.0.1"
-
-is-retry-allowed@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
-  integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=
-
-is-ssh@^1.3.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.1.tgz#f349a8cadd24e65298037a522cf7520f2e81a0f3"
-  integrity sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg==
-  dependencies:
-    protocols "^1.1.0"
-
-is-stream@^1.0.0, is-stream@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
-  integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
-
-is-symbol@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38"
-  integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==
-  dependencies:
-    has-symbols "^1.0.0"
-
-is-text-path@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-2.0.0.tgz#b2484e2b720a633feb2e85b67dc193ff72c75636"
-  integrity sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==
-  dependencies:
-    text-extensions "^2.0.0"
-
-is-typedarray@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
-  integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
-
-is-utf8@^0.2.0:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
-  integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
-
-is-windows@^1.0.0, is-windows@^1.0.1, is-windows@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
-  integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-
-is-wsl@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
-  integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
-
-isarray@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
-  integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
-
-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"
-  integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
-
-isarray@2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
-  integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=
-
-isexe@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
-  integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
-
-isobject@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
-  integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=
-  dependencies:
-    isarray "1.0.0"
-
-isobject@^3.0.0, isobject@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
-  integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
-
-isobject@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0"
-  integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==
-
-isstream@~0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
-  integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
-
-js-base64@^2.1.8:
-  version "2.5.1"
-  resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121"
-  integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==
-
-js-levenshtein@^1.1.3:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
-  integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
-
-"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
-  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-
-js-yaml@^3.13.1:
-  version "3.13.1"
-  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
-  integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
-  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"
-  integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
-
-jsesc@^2.5.1:
-  version "2.5.2"
-  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
-  integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
-
-jsesc@~0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
-  integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
-
-json-buffer@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
-  integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
-
-json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
-  integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
-
-json-schema-traverse@^0.4.1:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
-  integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
-
-json-schema@0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
-  integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
-
-json-stable-stringify-without-jsonify@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
-  integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
-
-json-stringify-safe@^5.0.1, 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"
-  integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
-
-json3@^3.3.2:
-  version "3.3.3"
-  resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81"
-  integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==
-
-json5@^0.5.0:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
-  integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
-
-json5@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
-  integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
-  dependencies:
-    minimist "^1.2.0"
-
-json5@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850"
-  integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==
-  dependencies:
-    minimist "^1.2.0"
-
-jsonfile@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
-  integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
-  optionalDependencies:
-    graceful-fs "^4.1.6"
-
-jsonify@~0.0.0:
-  version "0.0.0"
-  resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
-  integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
-
-jsonparse@^1.2.0:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
-  integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
-
-jsprim@^1.2.2:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
-  integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
-  dependencies:
-    assert-plus "1.0.0"
-    extsprintf "1.3.0"
-    json-schema "0.2.3"
-    verror "1.10.0"
-
-jszip@^3.1.5:
-  version "3.2.2"
-  resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.2.2.tgz#b143816df7e106a9597a94c77493385adca5bd1d"
-  integrity sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==
-  dependencies:
-    lie "~3.3.0"
-    pako "~1.0.2"
-    readable-stream "~2.3.6"
-    set-immediate-shim "~1.0.1"
-
-kareem@2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.0.tgz#ef33c42e9024dce511eeaf440cd684f3af1fc769"
-  integrity sha512-6hHxsp9e6zQU8nXsP+02HGWXwTkOEw6IROhF2ZA28cYbUk4eJ6QbtZvdqZOdD9YPKghG3apk5eOCvs+tLl3lRg==
-
-keyv@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
-  integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==
-  dependencies:
-    json-buffer "3.0.0"
-
-killable@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
-  integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==
-
-kind-of@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5"
-  integrity sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=
-  dependencies:
-    is-buffer "^1.0.2"
-
-kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0, kind-of@^3.2.2:
-  version "3.2.2"
-  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
-  integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=
-  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"
-  integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc=
-  dependencies:
-    is-buffer "^1.1.5"
-
-kind-of@^5.0.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
-  integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
-
-kind-of@^6.0.0, kind-of@^6.0.2:
-  version "6.0.2"
-  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
-  integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
-
-latest-version@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15"
-  integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=
-  dependencies:
-    package-json "^4.0.0"
-
-lazy-cache@^0.2.3:
-  version "0.2.7"
-  resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65"
-  integrity sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=
-
-lcid@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
-  integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=
-  dependencies:
-    invert-kv "^1.0.0"
-
-lcid@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
-  integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==
-  dependencies:
-    invert-kv "^2.0.0"
-
-lerna@^3.16.4:
-  version "3.16.4"
-  resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.16.4.tgz#158cb4f478b680f46f871d5891f531f3a2cb31ec"
-  integrity sha512-0HfwXIkqe72lBLZcNO9NMRfylh5Ng1l8tETgYQ260ZdHRbPuaLKE3Wqnd2YYRRkWfwPyEyZO8mZweBR+slVe1A==
-  dependencies:
-    "@lerna/add" "3.16.2"
-    "@lerna/bootstrap" "3.16.2"
-    "@lerna/changed" "3.16.4"
-    "@lerna/clean" "3.16.0"
-    "@lerna/cli" "3.13.0"
-    "@lerna/create" "3.16.0"
-    "@lerna/diff" "3.16.0"
-    "@lerna/exec" "3.16.0"
-    "@lerna/import" "3.16.0"
-    "@lerna/init" "3.16.0"
-    "@lerna/link" "3.16.2"
-    "@lerna/list" "3.16.0"
-    "@lerna/publish" "3.16.4"
-    "@lerna/run" "3.16.0"
-    "@lerna/version" "3.16.4"
-    import-local "^2.0.0"
-    npmlog "^4.1.2"
-
-levn@^0.3.0, levn@~0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
-  integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=
-  dependencies:
-    prelude-ls "~1.1.2"
-    type-check "~0.3.2"
-
-lie@~3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
-  integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
-  dependencies:
-    immediate "~3.0.5"
-
-lines-and-columns@^1.1.6:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
-  integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
-
-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"
-  integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=
-  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"
-  integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=
-  dependencies:
-    graceful-fs "^4.1.2"
-    parse-json "^2.2.0"
-    pify "^2.0.0"
-    strip-bom "^3.0.0"
-
-load-json-file@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
-  integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs=
-  dependencies:
-    graceful-fs "^4.1.2"
-    parse-json "^4.0.0"
-    pify "^3.0.0"
-    strip-bom "^3.0.0"
-
-load-json-file@^5.3.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-5.3.0.tgz#4d3c1e01fa1c03ea78a60ac7af932c9ce53403f3"
-  integrity sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==
-  dependencies:
-    graceful-fs "^4.1.15"
-    parse-json "^4.0.0"
-    pify "^4.0.1"
-    strip-bom "^3.0.0"
-    type-fest "^0.3.0"
-
-loader-fs-cache@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.2.tgz#54cedf6b727e1779fd8f01205f05f6e88706f086"
-  integrity sha512-70IzT/0/L+M20jUlEqZhZyArTU6VKLRTYRDAYN26g4jfzpJqjipLL3/hgYpySqI9PwsVRHHFja0LfEmsx9X2Cw==
-  dependencies:
-    find-cache-dir "^0.1.1"
-    mkdirp "0.5.1"
-
-loader-runner@^2.4.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
-  integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
-
-loader-utils@1.2.3, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
-  integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
-  dependencies:
-    big.js "^5.2.2"
-    emojis-list "^2.0.0"
-    json5 "^1.0.1"
-
-loader-utils@^0.2.16:
-  version "0.2.17"
-  resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
-  integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=
-  dependencies:
-    big.js "^3.1.3"
-    emojis-list "^2.0.0"
-    json5 "^0.5.0"
-    object-assign "^4.0.1"
-
-locate-path@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
-  integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
-  dependencies:
-    p-locate "^2.0.0"
-    path-exists "^3.0.0"
-
-locate-path@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
-  integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
-  dependencies:
-    p-locate "^3.0.0"
-    path-exists "^3.0.0"
-
-locate-path@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
-  integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
-  dependencies:
-    p-locate "^4.1.0"
-
-lodash._reinterpolate@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
-  integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
-
-lodash.assign@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
-  integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=
-
-lodash.assignin@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2"
-  integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI=
-
-lodash.clone@^4.5.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6"
-  integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=
-
-lodash.clonedeep@^4.3.0, lodash.clonedeep@^4.5.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
-  integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
-
-lodash.flatten@^4.4.0:
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
-  integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
-
-lodash.get@^4.4.2:
-  version "4.4.2"
-  resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
-  integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
-
-lodash.ismatch@^4.4.0:
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37"
-  integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=
-
-lodash.set@^4.3.2:
-  version "4.3.2"
-  resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
-  integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=
-
-lodash.sortby@^4.7.0:
-  version "4.7.0"
-  resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
-  integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
-
-lodash.tail@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
-  integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=
-
-lodash.template@^4.0.2, lodash.template@^4.5.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
-  integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==
-  dependencies:
-    lodash._reinterpolate "^3.0.0"
-    lodash.templatesettings "^4.0.0"
-
-lodash.templatesettings@^4.0.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33"
-  integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==
-  dependencies:
-    lodash._reinterpolate "^3.0.0"
-
-lodash.uniq@^4.5.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
-  integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
-
-lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.3, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.7.14, lodash@~4.17.10:
-  version "4.17.15"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
-  integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
-
-loglevel@^1.6.3:
-  version "1.6.3"
-  resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.3.tgz#77f2eb64be55a404c9fd04ad16d57c1d6d6b1280"
-  integrity sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA==
-
-long@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
-  integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
-
-loose-envify@^1.0.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
-  integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
-  dependencies:
-    js-tokens "^3.0.0 || ^4.0.0"
-
-loud-rejection@^1.0.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
-  integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=
-  dependencies:
-    currently-unhandled "^0.4.1"
-    signal-exit "^3.0.0"
-
-lower-case@^1.1.1:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
-  integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
-
-lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
-  integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
-
-lowercase-keys@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
-  integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
-
-lru-cache@^4.0.0, lru-cache@^4.0.1, lru-cache@^4.1.2:
-  version "4.1.5"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
-  integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
-  dependencies:
-    pseudomap "^1.0.2"
-    yallist "^2.1.2"
-
-lru-cache@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
-  integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
-  dependencies:
-    yallist "^3.0.2"
-
-macos-release@^2.2.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f"
-  integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==
-
-mailgun-js@^0.22.0:
-  version "0.22.0"
-  resolved "https://registry.yarnpkg.com/mailgun-js/-/mailgun-js-0.22.0.tgz#128942b5e47a364a470791608852bf68c96b3a05"
-  integrity sha512-a2alg5nuTZA9Psa1pSEIEsbxr1Zrmqx4VkgGCQ30xVh0kIH7Bu57AYILo+0v8QLSdXtCyLaS+KVmdCrQo0uWFA==
-  dependencies:
-    async "^2.6.1"
-    debug "^4.1.0"
-    form-data "^2.3.3"
-    inflection "~1.12.0"
-    is-stream "^1.1.0"
-    path-proxy "~1.0.0"
-    promisify-call "^2.0.2"
-    proxy-agent "^3.0.3"
-    tsscmp "^1.0.6"
-
-make-dir@^1.0.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
-  integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
-  dependencies:
-    pify "^3.0.0"
-
-make-dir@^2.0.0, make-dir@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
-  integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
-  dependencies:
-    pify "^4.0.1"
-    semver "^5.6.0"
-
-make-fetch-happen@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-5.0.0.tgz#a8e3fe41d3415dd656fe7b8e8172e1fb4458b38d"
-  integrity sha512-nFr/vpL1Jc60etMVKeaLOqfGjMMb3tAHFVJWxHOFCFS04Zmd7kGlMxo0l1tzfhoQje0/UPnd0X8OeGUiXXnfPA==
-  dependencies:
-    agentkeepalive "^3.4.1"
-    cacache "^12.0.0"
-    http-cache-semantics "^3.8.1"
-    http-proxy-agent "^2.1.0"
-    https-proxy-agent "^2.2.1"
-    lru-cache "^5.1.1"
-    mississippi "^3.0.0"
-    node-fetch-npm "^2.0.2"
-    promise-retry "^1.1.1"
-    socks-proxy-agent "^4.0.0"
-    ssri "^6.0.0"
-
-mamacro@^0.0.3:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4"
-  integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==
-
-map-age-cleaner@^0.1.1:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
-  integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==
-  dependencies:
-    p-defer "^1.0.0"
-
-map-cache@^0.2.2:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
-  integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=
-
-map-obj@^1.0.0, map-obj@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
-  integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=
-
-map-obj@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9"
-  integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk=
-
-map-visit@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
-  integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=
-  dependencies:
-    object-visit "^1.0.0"
-
-md5.js@^1.3.4:
-  version "1.3.5"
-  resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
-  integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==
-  dependencies:
-    hash-base "^3.0.0"
-    inherits "^2.0.1"
-    safe-buffer "^5.1.2"
-
-md5@^2.0.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9"
-  integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=
-  dependencies:
-    charenc "~0.0.1"
-    crypt "~0.0.1"
-    is-buffer "~1.1.1"
-
-media-typer@0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
-  integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
-
-mem@^4.0.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
-  integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==
-  dependencies:
-    map-age-cleaner "^0.1.1"
-    mimic-fn "^2.0.0"
-    p-is-promise "^2.0.0"
-
-memory-fs@^0.4.0, memory-fs@^0.4.1:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
-  integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=
-  dependencies:
-    errno "^0.1.3"
-    readable-stream "^2.0.1"
-
-memory-pager@^1.0.2:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"
-  integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==
-
-memorystream@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
-  integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI=
-
-meow@^3.3.0, meow@^3.7.0:
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
-  integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=
-  dependencies:
-    camelcase-keys "^2.0.0"
-    decamelize "^1.1.2"
-    loud-rejection "^1.0.0"
-    map-obj "^1.0.1"
-    minimist "^1.1.3"
-    normalize-package-data "^2.3.4"
-    object-assign "^4.0.1"
-    read-pkg-up "^1.0.1"
-    redent "^1.0.0"
-    trim-newlines "^1.0.0"
-
-meow@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/meow/-/meow-4.0.1.tgz#d48598f6f4b1472f35bf6317a95945ace347f975"
-  integrity sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==
-  dependencies:
-    camelcase-keys "^4.0.0"
-    decamelize-keys "^1.0.0"
-    loud-rejection "^1.0.0"
-    minimist "^1.1.3"
-    minimist-options "^3.0.1"
-    normalize-package-data "^2.3.4"
-    read-pkg-up "^3.0.0"
-    redent "^2.0.0"
-    trim-newlines "^2.0.0"
-
-merge-descriptors@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
-  integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
-
-merge-source-map@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646"
-  integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==
-  dependencies:
-    source-map "^0.6.1"
-
-merge2@^1.2.3:
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.4.tgz#c9269589e6885a60cf80605d9522d4b67ca646e3"
-  integrity sha512-FYE8xI+6pjFOhokZu0We3S5NKCirLbCzSh2Usf3qEyr4X8U+0jNg9P8RZ4qz+V2UoECLVwSyzU3LxXBaLGtD3A==
-
-methods@~1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
-  integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
-
-micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4:
-  version "3.1.10"
-  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
-  integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
-  dependencies:
-    arr-diff "^4.0.0"
-    array-unique "^0.3.2"
-    braces "^2.3.1"
-    define-property "^2.0.2"
-    extend-shallow "^3.0.2"
-    extglob "^2.0.4"
-    fragment-cache "^0.2.1"
-    kind-of "^6.0.2"
-    nanomatch "^1.2.9"
-    object.pick "^1.3.0"
-    regex-not "^1.0.0"
-    snapdragon "^0.8.1"
-    to-regex "^3.0.2"
-
-miller-rabin@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
-  integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==
-  dependencies:
-    bn.js "^4.0.0"
-    brorand "^1.0.1"
-
-mime-db@1.40.0, "mime-db@>= 1.40.0 < 2":
-  version "1.40.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
-  integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
-
-mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
-  version "2.1.24"
-  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
-  integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
-  dependencies:
-    mime-db "1.40.0"
-
-mime@1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
-  integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
-
-mime@^2.4.2:
-  version "2.4.4"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5"
-  integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==
-
-mimic-fn@^1.0.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
-  integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
-
-mimic-fn@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
-  integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
-
-mimic-response@^1.0.0, mimic-response@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
-  integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
-
-minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
-  integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
-
-minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
-  integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
-
-minimatch@^3.0.4, minimatch@~3.0.2:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
-  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
-  dependencies:
-    brace-expansion "^1.1.7"
-
-minimist-options@^3.0.1:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954"
-  integrity sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==
-  dependencies:
-    arrify "^1.0.1"
-    is-plain-obj "^1.1.0"
-
-minimist@0.0.8:
-  version "0.0.8"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-  integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
-
-minimist@^1.1.3, minimist@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
-  integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
-
-minimist@~0.0.1:
-  version "0.0.10"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
-  integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
-
-minipass@^2.2.1, minipass@^2.3.5:
-  version "2.3.5"
-  resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
-  integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==
-  dependencies:
-    safe-buffer "^5.1.2"
-    yallist "^3.0.0"
-
-minizlib@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614"
-  integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==
-  dependencies:
-    minipass "^2.2.1"
-
-mississippi@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
-  integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==
-  dependencies:
-    concat-stream "^1.5.0"
-    duplexify "^3.4.2"
-    end-of-stream "^1.1.0"
-    flush-write-stream "^1.0.0"
-    from2 "^2.1.0"
-    parallel-transform "^1.1.0"
-    pump "^3.0.0"
-    pumpify "^1.3.3"
-    stream-each "^1.1.0"
-    through2 "^2.0.0"
-
-mixin-deep@^1.2.0:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
-  integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
-  dependencies:
-    for-in "^1.0.2"
-    is-extendable "^1.0.1"
-
-mixin-object@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e"
-  integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=
-  dependencies:
-    for-in "^0.1.3"
-    is-extendable "^0.1.1"
-
-mkdirp-promise@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1"
-  integrity sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE=
-  dependencies:
-    mkdirp "*"
-
-mkdirp@*, mkdirp@0.5.1, mkdirp@0.5.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"
-  integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
-  dependencies:
-    minimist "0.0.8"
-
-modify-values@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
-  integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==
-
-moment@^2.10.2, moment@^2.24.0:
-  version "2.24.0"
-  resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
-  integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
-
-mongodb-core@3.2.7:
-  version "3.2.7"
-  resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-3.2.7.tgz#a8ef1fe764a192c979252dacbc600dc88d77e28f"
-  integrity sha512-WypKdLxFNPOH/Jy6i9z47IjG2wIldA54iDZBmHMINcgKOUcWJh8og+Wix76oGd7EyYkHJKssQ2FAOw5Su/n4XQ==
-  dependencies:
-    bson "^1.1.1"
-    require_optional "^1.0.1"
-    safe-buffer "^5.1.2"
-  optionalDependencies:
-    saslprep "^1.0.0"
-
-mongodb@3.2.7:
-  version "3.2.7"
-  resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.2.7.tgz#8ba149e4be708257cad0dea72aebb2bbb311a7ac"
-  integrity sha512-2YdWrdf1PJgxcCrT1tWoL6nHuk6hCxhddAAaEh8QJL231ci4+P9FLyqopbTm2Z2sAU6mhCri+wd9r1hOcHdoMw==
-  dependencies:
-    mongodb-core "3.2.7"
-    safe-buffer "^5.1.2"
-
-mongoose-legacy-pluralize@1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4"
-  integrity sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==
-
-mongoose@^5.6.4:
-  version "5.6.8"
-  resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.6.8.tgz#9b6d2575c21402def45f50072a3900c54b697cc8"
-  integrity sha512-BhgGU/KvnVX8WbamcWgtG/45rp+xZnaF9MhNbzESIIYxK7g5QurXYcaGGCm/JFiIdIxkVUgBycWG7UzRUEzvDg==
-  dependencies:
-    async "2.6.2"
-    bson "~1.1.1"
-    kareem "2.3.0"
-    mongodb "3.2.7"
-    mongodb-core "3.2.7"
-    mongoose-legacy-pluralize "1.0.2"
-    mpath "0.6.0"
-    mquery "3.2.1"
-    ms "2.1.2"
-    regexp-clone "1.0.0"
-    safe-buffer "5.1.2"
-    sift "7.0.1"
-    sliced "1.0.1"
-
-move-concurrently@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
-  integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=
-  dependencies:
-    aproba "^1.1.1"
-    copy-concurrently "^1.0.0"
-    fs-write-stream-atomic "^1.0.8"
-    mkdirp "^0.5.1"
-    rimraf "^2.5.4"
-    run-queue "^1.0.3"
-
-mpath@0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.6.0.tgz#aa922029fca4f0f641f360e74c5c1b6a4c47078e"
-  integrity sha512-i75qh79MJ5Xo/sbhxrDrPSEG0H/mr1kcZXJ8dH6URU5jD/knFxCVqVC/gVSW7GIXL/9hHWlT9haLbCXWOll3qw==
-
-mquery@3.2.1:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.2.1.tgz#8b059a49cdae0a8a9e804284ef64c2f58d3ac05d"
-  integrity sha512-kY/K8QToZWTTocm0U+r8rqcJCp5PRl6e8tPmoDs5OeSO3DInZE2rAL6AYH+V406JTo8305LdASOQcxRDqHojyw==
-  dependencies:
-    bluebird "3.5.1"
-    debug "3.1.0"
-    regexp-clone "^1.0.0"
-    safe-buffer "5.1.2"
-    sliced "1.0.1"
-
-ms@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
-  integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
-
-ms@2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
-  integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
-
-ms@2.1.2, ms@^2.0.0, ms@^2.1.1:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
-  integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-
-multicast-dns-service-types@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
-  integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=
-
-multicast-dns@^6.0.1:
-  version "6.2.3"
-  resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229"
-  integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==
-  dependencies:
-    dns-packet "^1.3.1"
-    thunky "^1.0.2"
-
-multimatch@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-3.0.0.tgz#0e2534cc6bc238d9ab67e1b9cd5fcd85a6dbf70b"
-  integrity sha512-22foS/gqQfANZ3o+W7ST2x25ueHDVNWl/b9OlGcLpy/iKxjCpvcNCM51YCenUi7Mt/jAjjqv8JwZRs8YP5sRjA==
-  dependencies:
-    array-differ "^2.0.3"
-    array-union "^1.0.2"
-    arrify "^1.0.1"
-    minimatch "^3.0.4"
-
-mute-stream@0.0.7:
-  version "0.0.7"
-  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
-  integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
-
-mute-stream@~0.0.4:
-  version "0.0.8"
-  resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
-  integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
-
-mz@^2.5.0:
-  version "2.7.0"
-  resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
-  integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
-  dependencies:
-    any-promise "^1.0.0"
-    object-assign "^4.0.1"
-    thenify-all "^1.0.0"
-
-nan@2.13.2:
-  version "2.13.2"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7"
-  integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==
-
-nan@^2.12.1, nan@^2.13.2:
-  version "2.14.0"
-  resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
-  integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
-
-nanomatch@^1.2.9:
-  version "1.2.13"
-  resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
-  integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==
-  dependencies:
-    arr-diff "^4.0.0"
-    array-unique "^0.3.2"
-    define-property "^2.0.2"
-    extend-shallow "^3.0.2"
-    fragment-cache "^0.2.1"
-    is-windows "^1.0.2"
-    kind-of "^6.0.2"
-    object.pick "^1.3.0"
-    regex-not "^1.0.0"
-    snapdragon "^0.8.1"
-    to-regex "^3.0.1"
-
-natural-compare@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
-  integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
-
-nconf@^0.10.0:
-  version "0.10.0"
-  resolved "https://registry.yarnpkg.com/nconf/-/nconf-0.10.0.tgz#da1285ee95d0a922ca6cee75adcf861f48205ad2"
-  integrity sha512-fKiXMQrpP7CYWJQzKkPPx9hPgmq+YLDyxcG9N8RpiE9FoCkCbzD0NyW0YhE3xn3Aupe7nnDeIx4PFzYehpHT9Q==
-  dependencies:
-    async "^1.4.0"
-    ini "^1.3.0"
-    secure-keys "^1.0.0"
-    yargs "^3.19.0"
-
-needle@^2.2.1, needle@^2.2.4:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
-  integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
-  dependencies:
-    debug "^3.2.6"
-    iconv-lite "^0.4.4"
-    sax "^1.2.4"
-
-negotiator@0.6.2:
-  version "0.6.2"
-  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
-  integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
-
-neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
-  integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
-
-netmask@^1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
-  integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=
-
-nice-try@^1.0.4:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
-  integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
-
-no-case@^2.2.0:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
-  integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==
-  dependencies:
-    lower-case "^1.1.1"
-
-node-fetch-npm@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7"
-  integrity sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==
-  dependencies:
-    encoding "^0.1.11"
-    json-parse-better-errors "^1.0.0"
-    safe-buffer "^5.1.1"
-
-node-fetch@^2.3.0, node-fetch@^2.5.0:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
-  integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
-
-node-forge@0.7.5:
-  version "0.7.5"
-  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df"
-  integrity sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==
-
-node-gyp@^3.8.0:
-  version "3.8.0"
-  resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
-  integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==
-  dependencies:
-    fstream "^1.0.0"
-    glob "^7.0.3"
-    graceful-fs "^4.1.2"
-    mkdirp "^0.5.0"
-    nopt "2 || 3"
-    npmlog "0 || 1 || 2 || 3 || 4"
-    osenv "0"
-    request "^2.87.0"
-    rimraf "2"
-    semver "~5.3.0"
-    tar "^2.0.0"
-    which "1"
-
-node-gyp@^5.0.2:
-  version "5.0.3"
-  resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.0.3.tgz#80d64c23790244991b6d44532f0a351bedd3dd45"
-  integrity sha512-z/JdtkFGUm0QaQUusvloyYuGDub3nUbOo5de1Fz57cM++osBTvQatBUSTlF1k/w8vFHPxxXW6zxGvkxXSpaBkQ==
-  dependencies:
-    env-paths "^1.0.0"
-    glob "^7.0.3"
-    graceful-fs "^4.1.2"
-    mkdirp "^0.5.0"
-    nopt "2 || 3"
-    npmlog "0 || 1 || 2 || 3 || 4"
-    request "^2.87.0"
-    rimraf "2"
-    semver "~5.3.0"
-    tar "^4.4.8"
-    which "1"
-
-node-libs-browser@^2.2.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
-  integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==
-  dependencies:
-    assert "^1.1.1"
-    browserify-zlib "^0.2.0"
-    buffer "^4.3.0"
-    console-browserify "^1.1.0"
-    constants-browserify "^1.0.0"
-    crypto-browserify "^3.11.0"
-    domain-browser "^1.1.1"
-    events "^3.0.0"
-    https-browserify "^1.0.0"
-    os-browserify "^0.3.0"
-    path-browserify "0.0.1"
-    process "^0.11.10"
-    punycode "^1.2.4"
-    querystring-es3 "^0.2.0"
-    readable-stream "^2.3.3"
-    stream-browserify "^2.0.1"
-    stream-http "^2.7.2"
-    string_decoder "^1.0.0"
-    timers-browserify "^2.0.4"
-    tty-browserify "0.0.0"
-    url "^0.11.0"
-    util "^0.11.0"
-    vm-browserify "^1.0.1"
-
-node-pre-gyp@0.12.0, node-pre-gyp@^0.12.0:
-  version "0.12.0"
-  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149"
-  integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==
-  dependencies:
-    detect-libc "^1.0.2"
-    mkdirp "^0.5.1"
-    needle "^2.2.1"
-    nopt "^4.0.1"
-    npm-packlist "^1.1.6"
-    npmlog "^4.0.2"
-    rc "^1.2.7"
-    rimraf "^2.6.1"
-    semver "^5.3.0"
-    tar "^4"
-
-node-releases@^1.1.25:
-  version "1.1.26"
-  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.26.tgz#f30563edc5c7dc20cf524cc8652ffa7be0762937"
-  integrity sha512-fZPsuhhUHMTlfkhDLGtfY80DSJTjOcx+qD1j5pqPkuhUHVS7xHZIg9EE4DHK8O3f0zTxXHX5VIkDG8pu98/wfQ==
-  dependencies:
-    semver "^5.3.0"
-
-node-sass@^4.12.0:
-  version "4.12.0"
-  resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017"
-  integrity sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==
-  dependencies:
-    async-foreach "^0.1.3"
-    chalk "^1.1.1"
-    cross-spawn "^3.0.0"
-    gaze "^1.0.0"
-    get-stdin "^4.0.1"
-    glob "^7.0.3"
-    in-publish "^2.0.0"
-    lodash "^4.17.11"
-    meow "^3.7.0"
-    mkdirp "^0.5.1"
-    nan "^2.13.2"
-    node-gyp "^3.8.0"
-    npmlog "^4.0.0"
-    request "^2.88.0"
-    sass-graph "^2.2.4"
-    stdout-stream "^1.4.0"
-    "true-case-path" "^1.0.2"
-
-"nopt@2 || 3":
-  version "3.0.6"
-  resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
-  integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k=
-  dependencies:
-    abbrev "1"
-
-nopt@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
-  integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
-  dependencies:
-    abbrev "1"
-    osenv "^0.1.4"
-
-normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
-  integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
-  dependencies:
-    hosted-git-info "^2.1.4"
-    resolve "^1.10.0"
-    semver "2 || 3 || 4 || 5"
-    validate-npm-package-license "^3.0.1"
-
-normalize-path@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
-  integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=
-  dependencies:
-    remove-trailing-separator "^1.0.1"
-
-normalize-path@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
-  integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
-
-normalize-url@^3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
-  integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
-
-normalize-url@^4.1.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.3.0.tgz#9c49e10fc1876aeb76dba88bf1b2b5d9fa57b2ee"
-  integrity sha512-0NLtR71o4k6GLP+mr6Ty34c5GA6CMoEsncKJxvQd8NzPxaHRJNnb5gZE8R1XF4CPIS7QPHLJ74IFszwtNVAHVQ==
-
-npm-bundled@^1.0.1:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd"
-  integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
-
-npm-lifecycle@^3.1.2:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-3.1.2.tgz#06f2253ea3b9e122ce3e55e3496670a810afcc84"
-  integrity sha512-nhfOcoTHrW1lJJlM2o77vTE2RWR4YOVyj7YzmY0y5itsMjEuoJHteio/ez0BliENEPsNxIUQgwhyEW9dShj3Ww==
-  dependencies:
-    byline "^5.0.0"
-    graceful-fs "^4.1.15"
-    node-gyp "^5.0.2"
-    resolve-from "^4.0.0"
-    slide "^1.1.6"
-    uid-number "0.0.6"
-    umask "^1.1.0"
-    which "^1.3.1"
-
-"npm-package-arg@^4.0.0 || ^5.0.0 || ^6.0.0", npm-package-arg@^6.0.0, npm-package-arg@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.0.tgz#15ae1e2758a5027efb4c250554b85a737db7fcc1"
-  integrity sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==
-  dependencies:
-    hosted-git-info "^2.6.0"
-    osenv "^0.1.5"
-    semver "^5.5.0"
-    validate-npm-package-name "^3.0.0"
-
-npm-packlist@^1.1.6, npm-packlist@^1.4.4:
-  version "1.4.4"
-  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44"
-  integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==
-  dependencies:
-    ignore-walk "^3.0.1"
-    npm-bundled "^1.0.1"
-
-npm-pick-manifest@^2.2.3:
-  version "2.2.3"
-  resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz#32111d2a9562638bb2c8f2bf27f7f3092c8fae40"
-  integrity sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA==
-  dependencies:
-    figgy-pudding "^3.5.1"
-    npm-package-arg "^6.0.0"
-    semver "^5.4.1"
-
-npm-run-all@^4.1.5:
-  version "4.1.5"
-  resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
-  integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==
-  dependencies:
-    ansi-styles "^3.2.1"
-    chalk "^2.4.1"
-    cross-spawn "^6.0.5"
-    memorystream "^0.3.1"
-    minimatch "^3.0.4"
-    pidtree "^0.3.0"
-    read-pkg "^3.0.0"
-    shell-quote "^1.6.1"
-    string.prototype.padend "^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"
-  integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=
-  dependencies:
-    path-key "^2.0.0"
-
-"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2, npmlog@^4.1.2:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
-  integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
-  dependencies:
-    are-we-there-yet "~1.1.2"
-    console-control-strings "~1.1.0"
-    gauge "~2.7.3"
-    set-blocking "~2.0.0"
-
-nth-check@~1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
-  integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==
-  dependencies:
-    boolbase "~1.0.0"
-
-number-is-nan@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
-  integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
-
-oauth-sign@~0.9.0:
-  version "0.9.0"
-  resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
-  integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
-
-oauth@^0.9.15:
-  version "0.9.15"
-  resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
-  integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE=
-
-object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
-  integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
-
-object-component@0.0.3:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
-  integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=
-
-object-copy@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
-  integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw=
-  dependencies:
-    copy-descriptor "^0.1.0"
-    define-property "^0.2.5"
-    kind-of "^3.0.3"
-
-object-hash@^1.1.4, object-hash@^1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
-  integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==
-
-object-keys@^1.0.11, object-keys@^1.0.12:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
-  integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
-
-object-visit@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
-  integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=
-  dependencies:
-    isobject "^3.0.0"
-
-object.assign@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
-  integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
-  dependencies:
-    define-properties "^1.1.2"
-    function-bind "^1.1.1"
-    has-symbols "^1.0.0"
-    object-keys "^1.0.11"
-
-object.entries@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519"
-  integrity sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.12.0"
-    function-bind "^1.1.1"
-    has "^1.0.3"
-
-object.getownpropertydescriptors@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
-  integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=
-  dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.5.1"
-
-object.pick@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
-  integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=
-  dependencies:
-    isobject "^3.0.1"
-
-object.values@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.0.tgz#bf6810ef5da3e5325790eaaa2be213ea84624da9"
-  integrity sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.12.0"
-    function-bind "^1.1.1"
-    has "^1.0.3"
-
-obuf@^1.0.0, obuf@^1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
-  integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
-
-octokit-pagination-methods@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4"
-  integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==
-
-on-finished@~2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
-  integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
-  dependencies:
-    ee-first "1.1.1"
-
-on-headers@~1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
-  integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
-
-once@^1.3.0, once@^1.3.1, once@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
-  integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
-  dependencies:
-    wrappy "1"
-
-onetime@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
-  integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=
-  dependencies:
-    mimic-fn "^1.0.0"
-
-opencollective-postinstall@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89"
-  integrity sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==
-
-opener@^1.5.1:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
-  integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
-
-opn@^5.5.0:
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc"
-  integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==
-  dependencies:
-    is-wsl "^1.1.0"
-
-optimist@^0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
-  integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
-  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"
-  integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=
-  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.2"
-  resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
-  integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==
-  dependencies:
-    url-parse "^1.4.3"
-
-os-browserify@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27"
-  integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=
-
-os-homedir@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
-  integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
-
-os-locale@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
-  integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=
-  dependencies:
-    lcid "^1.0.0"
-
-os-locale@^3.0.0, os-locale@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
-  integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
-  dependencies:
-    execa "^1.0.0"
-    lcid "^2.0.0"
-    mem "^4.0.0"
-
-os-name@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801"
-  integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==
-  dependencies:
-    macos-release "^2.2.0"
-    windows-release "^3.1.0"
-
-os-tmpdir@^1.0.0, os-tmpdir@~1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
-  integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
-
-osenv@0, osenv@^0.1.4, osenv@^0.1.5:
-  version "0.1.5"
-  resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
-  integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
-  dependencies:
-    os-homedir "^1.0.0"
-    os-tmpdir "^1.0.0"
-
-p-cancelable@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
-  integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
-
-p-defer@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
-  integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=
-
-p-finally@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
-  integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
-
-p-is-promise@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
-  integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
-
-p-limit@^1.1.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
-  integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
-  dependencies:
-    p-try "^1.0.0"
-
-p-limit@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2"
-  integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==
-  dependencies:
-    p-try "^2.0.0"
-
-p-limit@^2.2.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537"
-  integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==
-  dependencies:
-    p-try "^2.0.0"
-
-p-locate@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
-  integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
-  dependencies:
-    p-limit "^1.1.0"
-
-p-locate@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
-  integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
-  dependencies:
-    p-limit "^2.0.0"
-
-p-locate@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
-  integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
-  dependencies:
-    p-limit "^2.2.0"
-
-p-map-series@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-1.0.0.tgz#bf98fe575705658a9e1351befb85ae4c1f07bdca"
-  integrity sha1-v5j+V1cFZYqeE1G++4WuTB8Hvco=
-  dependencies:
-    p-reduce "^1.0.0"
-
-p-map@^2.0.0, p-map@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
-  integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
-
-p-pipe@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9"
-  integrity sha1-SxoROZoRUgpneQ7loMHViB1r7+k=
-
-p-queue@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-4.0.0.tgz#ed0eee8798927ed6f2c2f5f5b77fdb2061a5d346"
-  integrity sha512-3cRXXn3/O0o3+eVmUroJPSj/esxoEFIm0ZOno/T+NzG/VZgPOqQ8WKmlNqubSEpZmCIngEy34unkHGg83ZIBmg==
-  dependencies:
-    eventemitter3 "^3.1.0"
-
-p-reduce@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa"
-  integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=
-
-p-retry@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328"
-  integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==
-  dependencies:
-    retry "^0.12.0"
-
-p-try@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
-  integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
-
-p-try@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
-  integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
-
-p-waterfall@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/p-waterfall/-/p-waterfall-1.0.0.tgz#7ed94b3ceb3332782353af6aae11aa9fc235bb00"
-  integrity sha1-ftlLPOszMngjU69qrhGqn8I1uwA=
-  dependencies:
-    p-reduce "^1.0.0"
-
-pac-proxy-agent@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-3.0.0.tgz#11d578b72a164ad74bf9d5bac9ff462a38282432"
-  integrity sha512-AOUX9jES/EkQX2zRz0AW7lSx9jD//hQS8wFXBvcnd/J2Py9KaMJMqV/LPqJssj1tgGufotb2mmopGPR15ODv1Q==
-  dependencies:
-    agent-base "^4.2.0"
-    debug "^3.1.0"
-    get-uri "^2.0.0"
-    http-proxy-agent "^2.1.0"
-    https-proxy-agent "^2.2.1"
-    pac-resolver "^3.0.0"
-    raw-body "^2.2.0"
-    socks-proxy-agent "^4.0.1"
-
-pac-resolver@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-3.0.0.tgz#6aea30787db0a891704deb7800a722a7615a6f26"
-  integrity sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==
-  dependencies:
-    co "^4.6.0"
-    degenerator "^1.0.4"
-    ip "^1.1.5"
-    netmask "^1.0.6"
-    thunkify "^2.1.2"
-
-package-json@*:
-  version "6.5.0"
-  resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0"
-  integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==
-  dependencies:
-    got "^9.6.0"
-    registry-auth-token "^4.0.0"
-    registry-url "^5.0.0"
-    semver "^6.2.0"
-
-package-json@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed"
-  integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=
-  dependencies:
-    got "^6.7.1"
-    registry-auth-token "^3.0.1"
-    registry-url "^3.0.3"
-    semver "^5.1.0"
-
-pako@~1.0.2, pako@~1.0.5:
-  version "1.0.10"
-  resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
-  integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==
-
-parallel-transform@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
-  integrity sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=
-  dependencies:
-    cyclist "~0.2.2"
-    inherits "^2.0.3"
-    readable-stream "^2.1.5"
-
-param-case@2.1.x:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247"
-  integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc=
-  dependencies:
-    no-case "^2.2.0"
-
-parent-module@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
-  integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
-  dependencies:
-    callsites "^3.0.0"
-
-parse-asn1@^5.0.0:
-  version "5.1.4"
-  resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc"
-  integrity sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==
-  dependencies:
-    asn1.js "^4.0.0"
-    browserify-aes "^1.0.0"
-    create-hash "^1.1.0"
-    evp_bytestokey "^1.0.0"
-    pbkdf2 "^3.0.3"
-    safe-buffer "^5.1.1"
-
-parse-github-repo-url@^1.3.0:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50"
-  integrity sha1-nn2LslKmy2ukJZUGC3v23z28H1A=
-
-parse-json@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
-  integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=
-  dependencies:
-    error-ex "^1.2.0"
-
-parse-json@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
-  integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
-  dependencies:
-    error-ex "^1.3.1"
-    json-parse-better-errors "^1.0.1"
-
-parse-json@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f"
-  integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==
-  dependencies:
-    "@babel/code-frame" "^7.0.0"
-    error-ex "^1.3.1"
-    json-parse-better-errors "^1.0.1"
-    lines-and-columns "^1.1.6"
-
-parse-passwd@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
-  integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
-
-parse-path@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.1.tgz#0ec769704949778cb3b8eda5e994c32073a1adff"
-  integrity sha512-d7yhga0Oc+PwNXDvQ0Jv1BuWkLVPXcAoQ/WREgd6vNNoKYaW52KI+RdOFjI63wjkmps9yUE8VS4veP+AgpQ/hA==
-  dependencies:
-    is-ssh "^1.3.0"
-    protocols "^1.4.0"
-
-parse-url@^5.0.0:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-5.0.1.tgz#99c4084fc11be14141efa41b3d117a96fcb9527f"
-  integrity sha512-flNUPP27r3vJpROi0/R3/2efgKkyXqnXwyP1KQ2U0SfFRgdizOdWfvrrvJg1LuOoxs7GQhmxJlq23IpQ/BkByg==
-  dependencies:
-    is-ssh "^1.3.0"
-    normalize-url "^3.3.0"
-    parse-path "^4.0.0"
-    protocols "^1.4.0"
-
-parseqs@0.0.5:
-  version "0.0.5"
-  resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
-  integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=
-  dependencies:
-    better-assert "~1.0.0"
-
-parseuri@0.0.5:
-  version "0.0.5"
-  resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a"
-  integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=
-  dependencies:
-    better-assert "~1.0.0"
-
-parseurl@~1.3.2, parseurl@~1.3.3:
-  version "1.3.3"
-  resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
-  integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
-
-pascalcase@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
-  integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
-
-path-browserify@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
-  integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==
-
-path-dirname@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
-  integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=
-
-path-exists@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
-  integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=
-  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"
-  integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
-
-path-exists@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
-  integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
-
-path-is-absolute@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
-  integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
-
-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"
-  integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
-
-path-key@^2.0.0, path-key@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
-  integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
-
-path-parse@^1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
-  integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
-
-path-proxy@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/path-proxy/-/path-proxy-1.0.0.tgz#18e8a36859fc9d2f1a53b48dee138543c020de5e"
-  integrity sha1-GOijaFn8nS8aU7SN7hOFQ8Ag3l4=
-  dependencies:
-    inflection "~1.3.0"
-
-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"
-  integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
-
-path-type@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
-  integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=
-  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"
-  integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=
-  dependencies:
-    pify "^2.0.0"
-
-path-type@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
-  integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==
-  dependencies:
-    pify "^3.0.0"
-
-pbkdf2@^3.0.3:
-  version "3.0.17"
-  resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6"
-  integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==
-  dependencies:
-    create-hash "^1.1.2"
-    create-hmac "^1.1.4"
-    ripemd160 "^2.0.1"
-    safe-buffer "^5.0.1"
-    sha.js "^2.4.8"
-
-performance-now@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
-  integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
-
-pidtree@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.0.tgz#f6fada10fccc9f99bf50e90d0b23d72c9ebc2e6b"
-  integrity sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg==
-
-pify@^2.0.0, pify@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
-  integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
-
-pify@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
-  integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
-
-pify@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
-  integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
-
-pinkie-promise@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
-  integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
-  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"
-  integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
-
-pkg-dir@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
-  integrity sha1-ektQio1bstYp1EcFb/TpyTFM89Q=
-  dependencies:
-    find-up "^1.0.0"
-
-pkg-dir@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
-  integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=
-  dependencies:
-    find-up "^2.1.0"
-
-pkg-dir@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
-  integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==
-  dependencies:
-    find-up "^3.0.0"
-
-pkg-dir@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
-  integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
-  dependencies:
-    find-up "^4.0.0"
-
-please-upgrade-node@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
-  integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
-  dependencies:
-    semver-compare "^1.0.0"
-
-portfinder@^1.0.20:
-  version "1.0.21"
-  resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.21.tgz#60e1397b95ac170749db70034ece306b9a27e324"
-  integrity sha512-ESabpDCzmBS3ekHbmpAIiESq3udRsCBGiBZLsC+HgBKv2ezb0R4oG+7RnYEVZ/ZCfhel5Tx3UzdNWA0Lox2QCA==
-  dependencies:
-    async "^1.5.2"
-    debug "^2.2.0"
-    mkdirp "0.5.x"
-
-posix-character-classes@^0.1.0:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
-  integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
-
-postcss-modules-extract-imports@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e"
-  integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==
-  dependencies:
-    postcss "^7.0.5"
-
-postcss-modules-local-by-default@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915"
-  integrity sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ==
-  dependencies:
-    icss-utils "^4.1.1"
-    postcss "^7.0.16"
-    postcss-selector-parser "^6.0.2"
-    postcss-value-parser "^4.0.0"
-
-postcss-modules-scope@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz#ad3f5bf7856114f6fcab901b0502e2a2bc39d4eb"
-  integrity sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A==
-  dependencies:
-    postcss "^7.0.6"
-    postcss-selector-parser "^6.0.0"
-
-postcss-modules-values@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10"
-  integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==
-  dependencies:
-    icss-utils "^4.0.0"
-    postcss "^7.0.6"
-
-postcss-selector-parser@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c"
-  integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==
-  dependencies:
-    cssesc "^2.0.0"
-    indexes-of "^1.0.1"
-    uniq "^1.0.1"
-
-postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
-  version "6.0.2"
-  resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c"
-  integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==
-  dependencies:
-    cssesc "^3.0.0"
-    indexes-of "^1.0.1"
-    uniq "^1.0.1"
-
-postcss-value-parser@^4.0.0:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9"
-  integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==
-
-postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.5, postcss@^7.0.6:
-  version "7.0.17"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f"
-  integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==
-  dependencies:
-    chalk "^2.4.2"
-    source-map "^0.6.1"
-    supports-color "^6.1.0"
-
-prelude-ls@~1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
-  integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
-
-prepend-http@^1.0.1:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
-  integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
-
-prepend-http@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
-  integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
-
-prettier-linter-helpers@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
-  integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
-  dependencies:
-    fast-diff "^1.1.2"
-
-prettier@1.16.3:
-  version "1.16.3"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d"
-  integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==
-
-prettier@1.18.2:
-  version "1.18.2"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
-  integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
-
-pretty-error@^2.0.2:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3"
-  integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=
-  dependencies:
-    renderkid "^2.0.1"
-    utila "~0.4"
-
-prism-media@^0.0.3:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-0.0.3.tgz#8842d4fae804f099d3b48a9a38e3c2bab6f4855b"
-  integrity sha512-c9KkNifSMU/iXT8FFTaBwBMr+rdVcN+H/uNv1o+CuFeTThNZNTOrQ+RgXA1yL/DeLk098duAeRPP3QNPNbhxYQ==
-
-private@^0.1.6:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
-  integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
-
-process-nextick-args@~2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
-  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
-
-process@^0.11.10:
-  version "0.11.10"
-  resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
-  integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
-
-progress@^2.0.0:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
-  integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
-
-promise-inflight@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
-  integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
-
-promise-retry@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d"
-  integrity sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=
-  dependencies:
-    err-code "^1.0.0"
-    retry "^0.10.0"
-
-"promise@>=3.2 <8":
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
-  integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
-  dependencies:
-    asap "~2.0.3"
-
-promisify-call@^2.0.2:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/promisify-call/-/promisify-call-2.0.4.tgz#d48c2d45652ccccd52801ddecbd533a6d4bd5fba"
-  integrity sha1-1IwtRWUszM1SgB3ey9UzptS9X7o=
-  dependencies:
-    with-callback "^1.0.2"
-
-promzard@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee"
-  integrity sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=
-  dependencies:
-    read "1"
-
-proto-list@~1.2.1:
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
-  integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
-
-protocols@^1.1.0, protocols@^1.4.0:
-  version "1.4.7"
-  resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.7.tgz#95f788a4f0e979b291ffefcf5636ad113d037d32"
-  integrity sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg==
-
-protoduck@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/protoduck/-/protoduck-5.0.1.tgz#03c3659ca18007b69a50fd82a7ebcc516261151f"
-  integrity sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==
-  dependencies:
-    genfun "^5.0.0"
-
-proxy-addr@~2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
-  integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
-  dependencies:
-    forwarded "~0.1.2"
-    ipaddr.js "1.9.0"
-
-proxy-agent@^3.0.3, proxy-agent@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-3.1.0.tgz#3cf86ee911c94874de4359f37efd9de25157c113"
-  integrity sha512-IkbZL4ClW3wwBL/ABFD2zJ8iP84CY0uKMvBPk/OceQe/cEjrxzN1pMHsLwhbzUoRhG9QbSxYC+Z7LBkTiBNvrA==
-  dependencies:
-    agent-base "^4.2.0"
-    debug "^3.1.0"
-    http-proxy-agent "^2.1.0"
-    https-proxy-agent "^2.2.1"
-    lru-cache "^4.1.2"
-    pac-proxy-agent "^3.0.0"
-    proxy-from-env "^1.0.0"
-    socks-proxy-agent "^4.0.1"
-
-proxy-from-env@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee"
-  integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=
-
-prr@~1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
-  integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY=
-
-pseudomap@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
-  integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
-
-psl@^1.1.24, psl@^1.1.7:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/psl/-/psl-1.3.0.tgz#e1ebf6a3b5564fa8376f3da2275da76d875ca1bd"
-  integrity sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==
-
-public-encrypt@^4.0.0:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
-  integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==
-  dependencies:
-    bn.js "^4.1.0"
-    browserify-rsa "^4.0.0"
-    create-hash "^1.1.0"
-    parse-asn1 "^5.0.0"
-    randombytes "^2.0.1"
-    safe-buffer "^5.1.2"
-
-pump@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
-  integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==
-  dependencies:
-    end-of-stream "^1.1.0"
-    once "^1.3.1"
-
-pump@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
-  integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
-  dependencies:
-    end-of-stream "^1.1.0"
-    once "^1.3.1"
-
-pumpify@^1.3.3:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
-  integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==
-  dependencies:
-    duplexify "^3.6.0"
-    inherits "^2.0.3"
-    pump "^2.0.0"
-
-punycode@1.3.2:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
-  integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
-
-punycode@^1.2.4, punycode@^1.4.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
-  integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
-
-punycode@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
-  integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
-
-q@^1.5.1:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
-  integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
-
-qs@6.7.0:
-  version "6.7.0"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
-  integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
-
-qs@~6.5.2:
-  version "6.5.2"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
-  integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
-
-querystring-es3@^0.2.0:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
-  integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=
-
-querystring@0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
-  integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
-
-querystringify@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
-  integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
-
-quick-lru@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8"
-  integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=
-
-randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
-  integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
-  dependencies:
-    safe-buffer "^5.1.0"
-
-randomfill@^1.0.3:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458"
-  integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==
-  dependencies:
-    randombytes "^2.0.5"
-    safe-buffer "^5.1.0"
-
-range-parser@^1.2.1, range-parser@~1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
-  integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
-
-raw-body@2.4.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
-  integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
-  dependencies:
-    bytes "3.1.0"
-    http-errors "1.7.2"
-    iconv-lite "0.4.24"
-    unpipe "1.0.0"
-
-raw-body@^2.2.0:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
-  integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==
-  dependencies:
-    bytes "3.1.0"
-    http-errors "1.7.3"
-    iconv-lite "0.4.24"
-    unpipe "1.0.0"
-
-rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8:
-  version "1.2.8"
-  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
-  integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
-  dependencies:
-    deep-extend "^0.6.0"
-    ini "~1.3.0"
-    minimist "^1.2.0"
-    strip-json-comments "~2.0.1"
-
-read-cmd-shim@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz#2d5d157786a37c055d22077c32c53f8329e91c7b"
-  integrity sha1-LV0Vd4ajfAVdIgd8MsU/gynpHHs=
-  dependencies:
-    graceful-fs "^4.1.2"
-
-"read-package-json@1 || 2", read-package-json@^2.0.0, read-package-json@^2.0.13:
-  version "2.0.13"
-  resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.0.13.tgz#2e82ebd9f613baa6d2ebe3aa72cefe3f68e41f4a"
-  integrity sha512-/1dZ7TRZvGrYqE0UAfN6qQb5GYBsNcqS1C0tNK601CFOJmtHI7NIGXwetEPU/OtoFHZL3hDxm4rolFFVE9Bnmg==
-  dependencies:
-    glob "^7.1.1"
-    json-parse-better-errors "^1.0.1"
-    normalize-package-data "^2.0.0"
-    slash "^1.0.0"
-  optionalDependencies:
-    graceful-fs "^4.1.2"
-
-read-package-tree@^5.1.6:
-  version "5.3.1"
-  resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.3.1.tgz#a32cb64c7f31eb8a6f31ef06f9cedf74068fe636"
-  integrity sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==
-  dependencies:
-    read-package-json "^2.0.0"
-    readdir-scoped-modules "^1.0.0"
-    util-promisify "^2.1.0"
-
-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"
-  integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=
-  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"
-  integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=
-  dependencies:
-    find-up "^2.0.0"
-    read-pkg "^2.0.0"
-
-read-pkg-up@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07"
-  integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=
-  dependencies:
-    find-up "^2.0.0"
-    read-pkg "^3.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"
-  integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=
-  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"
-  integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=
-  dependencies:
-    load-json-file "^2.0.0"
-    normalize-package-data "^2.3.2"
-    path-type "^2.0.0"
-
-read-pkg@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
-  integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=
-  dependencies:
-    load-json-file "^4.0.0"
-    normalize-package-data "^2.3.2"
-    path-type "^3.0.0"
-
-read-pkg@^5.1.1:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc"
-  integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==
-  dependencies:
-    "@types/normalize-package-data" "^2.4.0"
-    normalize-package-data "^2.5.0"
-    parse-json "^5.0.0"
-    type-fest "^0.6.0"
-
-read@1, read@~1.0.1:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4"
-  integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=
-  dependencies:
-    mute-stream "~0.0.4"
-
-"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6:
-  version "2.3.6"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
-  integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.3"
-    isarray "~1.0.0"
-    process-nextick-args "~2.0.0"
-    safe-buffer "~5.1.1"
-    string_decoder "~1.1.1"
-    util-deprecate "~1.0.1"
-
-readable-stream@1.1.x:
-  version "1.1.14"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
-  integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.1"
-    isarray "0.0.1"
-    string_decoder "~0.10.x"
-
-"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
-  integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==
-  dependencies:
-    inherits "^2.0.3"
-    string_decoder "^1.1.1"
-    util-deprecate "^1.0.1"
-
-readdir-scoped-modules@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309"
-  integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==
-  dependencies:
-    debuglog "^1.0.1"
-    dezalgo "^1.0.0"
-    graceful-fs "^4.1.2"
-    once "^1.3.0"
-
-readdirp@^2.2.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
-  integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==
-  dependencies:
-    graceful-fs "^4.1.11"
-    micromatch "^3.1.10"
-    readable-stream "^2.0.2"
-
-redent@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
-  integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=
-  dependencies:
-    indent-string "^2.1.0"
-    strip-indent "^1.0.1"
-
-redent@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa"
-  integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=
-  dependencies:
-    indent-string "^3.0.0"
-    strip-indent "^2.0.0"
-
-redis-commands@^1.2.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.5.0.tgz#80d2e20698fe688f227127ff9e5164a7dd17e785"
-  integrity sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==
-
-redis-parser@^2.6.0:
-  version "2.6.0"
-  resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b"
-  integrity sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=
-
-redis@^2.8.0:
-  version "2.8.0"
-  resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02"
-  integrity sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==
-  dependencies:
-    double-ended-queue "^2.1.0-0"
-    redis-commands "^1.2.0"
-    redis-parser "^2.6.0"
-
-regenerate-unicode-properties@^8.0.2:
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"
-  integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==
-  dependencies:
-    regenerate "^1.4.0"
-
-regenerate@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
-  integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
-
-regenerator-runtime@^0.13.2:
-  version "0.13.3"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
-  integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==
-
-regenerator-transform@^0.14.0:
-  version "0.14.1"
-  resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb"
-  integrity sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==
-  dependencies:
-    private "^0.1.6"
-
-regex-not@^1.0.0, regex-not@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
-  integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==
-  dependencies:
-    extend-shallow "^3.0.2"
-    safe-regex "^1.1.0"
-
-regexp-clone@1.0.0, regexp-clone@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63"
-  integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==
-
-regexp-tree@^0.1.6:
-  version "0.1.11"
-  resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.11.tgz#c9c7f00fcf722e0a56c7390983a7a63dd6c272f3"
-  integrity sha512-7/l/DgapVVDzZobwMCCgMlqiqyLFJ0cduo/j+3BcDJIB+yJdsYCfKuI3l/04NV+H/rfNRdPIDbXNZHM9XvQatg==
-
-regexpp@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
-  integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==
-
-regexpu-core@^4.5.4:
-  version "4.5.4"
-  resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae"
-  integrity sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==
-  dependencies:
-    regenerate "^1.4.0"
-    regenerate-unicode-properties "^8.0.2"
-    regjsgen "^0.5.0"
-    regjsparser "^0.6.0"
-    unicode-match-property-ecmascript "^1.0.4"
-    unicode-match-property-value-ecmascript "^1.1.0"
-
-registry-auth-token@^3.0.1:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e"
-  integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==
-  dependencies:
-    rc "^1.1.6"
-    safe-buffer "^5.0.1"
-
-registry-auth-token@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.0.0.tgz#30e55961eec77379da551ea5c4cf43cbf03522be"
-  integrity sha512-lpQkHxd9UL6tb3k/aHAVfnVtn+Bcs9ob5InuFLLEDqSqeq+AljB8GZW9xY0x7F+xYwEcjKe07nyoxzEYz6yvkw==
-  dependencies:
-    rc "^1.2.8"
-    safe-buffer "^5.0.1"
-
-registry-url@^3.0.3:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
-  integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI=
-  dependencies:
-    rc "^1.0.1"
-
-registry-url@^5.0.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009"
-  integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==
-  dependencies:
-    rc "^1.2.8"
-
-regjsgen@^0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.0.tgz#a7634dc08f89209c2049adda3525711fb97265dd"
-  integrity sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==
-
-regjsparser@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.0.tgz#f1e6ae8b7da2bae96c99399b868cd6c933a2ba9c"
-  integrity sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==
-  dependencies:
-    jsesc "~0.5.0"
-
-relateurl@0.2.x:
-  version "0.2.7"
-  resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
-  integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
-
-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"
-  integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
-
-renderkid@^2.0.1:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149"
-  integrity sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==
-  dependencies:
-    css-select "^1.1.0"
-    dom-converter "^0.2"
-    htmlparser2 "^3.3.0"
-    strip-ansi "^3.0.0"
-    utila "^0.4.0"
-
-repeat-element@^1.1.2:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
-  integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==
-
-repeat-string@^1.6.1:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
-  integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
-
-repeating@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
-  integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
-  dependencies:
-    is-finite "^1.0.0"
-
-request@^2.87.0, request@^2.88.0:
-  version "2.88.0"
-  resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
-  integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
-  dependencies:
-    aws-sign2 "~0.7.0"
-    aws4 "^1.8.0"
-    caseless "~0.12.0"
-    combined-stream "~1.0.6"
-    extend "~3.0.2"
-    forever-agent "~0.6.1"
-    form-data "~2.3.2"
-    har-validator "~5.1.0"
-    http-signature "~1.2.0"
-    is-typedarray "~1.0.0"
-    isstream "~0.1.2"
-    json-stringify-safe "~5.0.1"
-    mime-types "~2.1.19"
-    oauth-sign "~0.9.0"
-    performance-now "^2.1.0"
-    qs "~6.5.2"
-    safe-buffer "^5.1.2"
-    tough-cookie "~2.4.3"
-    tunnel-agent "^0.6.0"
-    uuid "^3.3.2"
-
-require-directory@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
-  integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
-
-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"
-  integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
-
-require-main-filename@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
-  integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
-
-require_optional@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e"
-  integrity sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==
-  dependencies:
-    resolve-from "^2.0.0"
-    semver "^5.1.0"
-
-requires-port@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
-  integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
-
-resolve-cwd@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
-  integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=
-  dependencies:
-    resolve-from "^3.0.0"
-
-resolve-dir@^1.0.0, resolve-dir@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43"
-  integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=
-  dependencies:
-    expand-tilde "^2.0.0"
-    global-modules "^1.0.0"
-
-resolve-from@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57"
-  integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=
-
-resolve-from@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
-  integrity sha1-six699nWiBvItuZTM17rywoYh0g=
-
-resolve-from@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
-  integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
-
-resolve-url@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
-  integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
-
-resolve@^1.10.0, resolve@^1.11.0, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1:
-  version "1.12.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
-  integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==
-  dependencies:
-    path-parse "^1.0.6"
-
-responselike@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
-  integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
-  dependencies:
-    lowercase-keys "^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"
-  integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368=
-  dependencies:
-    onetime "^2.0.0"
-    signal-exit "^3.0.2"
-
-ret@~0.1.10:
-  version "0.1.15"
-  resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
-  integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
-
-retry@^0.10.0:
-  version "0.10.1"
-  resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4"
-  integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=
-
-retry@^0.12.0:
-  version "0.12.0"
-  resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
-  integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
-
-rimraf@2, rimraf@2.6.3, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
-  version "2.6.3"
-  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
-  integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
-  dependencies:
-    glob "^7.1.3"
-
-ripemd160@^2.0.0, ripemd160@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
-  integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==
-  dependencies:
-    hash-base "^3.0.0"
-    inherits "^2.0.1"
-
-run-async@^2.2.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
-  integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
-  dependencies:
-    is-promise "^2.1.0"
-
-run-node@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e"
-  integrity sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==
-
-run-queue@^1.0.0, run-queue@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
-  integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=
-  dependencies:
-    aproba "^1.1.1"
-
-rxjs@^6.4.0:
-  version "6.5.2"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7"
-  integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==
-  dependencies:
-    tslib "^1.9.0"
-
-safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
-  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
-safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
-  integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
-
-safe-regex@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
-  integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4=
-  dependencies:
-    ret "~0.1.10"
-
-"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
-  integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-
-saslprep@^1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226"
-  integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==
-  dependencies:
-    sparse-bitfield "^3.0.3"
-
-sass-graph@^2.2.4:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
-  integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=
-  dependencies:
-    glob "^7.0.0"
-    lodash "^4.0.0"
-    scss-tokenizer "^0.2.3"
-    yargs "^7.0.0"
-
-sass-loader@^7.1.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.1.0.tgz#16fd5138cb8b424bf8a759528a1972d72aad069d"
-  integrity sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==
-  dependencies:
-    clone-deep "^2.0.1"
-    loader-utils "^1.0.1"
-    lodash.tail "^4.1.1"
-    neo-async "^2.5.0"
-    pify "^3.0.0"
-    semver "^5.5.0"
-
-sax@>=0.6.0, sax@^1.2.4:
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
-  integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
-
-schema-utils@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
-  integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==
-  dependencies:
-    ajv "^6.1.0"
-    ajv-errors "^1.0.0"
-    ajv-keywords "^3.1.0"
-
-schema-utils@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.1.0.tgz#940363b6b1ec407800a22951bdcc23363c039393"
-  integrity sha512-g6SViEZAfGNrToD82ZPUjq52KUPDYc+fN5+g6Euo5mLokl/9Yx14z0Cu4RR1m55HtBXejO0sBt+qw79axN+Fiw==
-  dependencies:
-    ajv "^6.1.0"
-    ajv-keywords "^3.1.0"
-
-scss-tokenizer@^0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
-  integrity sha1-jrBtualyMzOCTT9VMGQRSYR85dE=
-  dependencies:
-    js-base64 "^2.1.8"
-    source-map "^0.4.2"
-
-secure-keys@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/secure-keys/-/secure-keys-1.0.0.tgz#f0c82d98a3b139a8776a8808050b824431087fca"
-  integrity sha1-8MgtmKOxOah3aogIBQuCRDEIf8o=
-
-select-hose@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
-  integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
-
-selfsigned@^1.10.4:
-  version "1.10.4"
-  resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.4.tgz#cdd7eccfca4ed7635d47a08bf2d5d3074092e2cd"
-  integrity sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==
-  dependencies:
-    node-forge "0.7.5"
-
-semver-compare@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
-  integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
-
-semver-diff@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
-  integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=
-  dependencies:
-    semver "^5.0.3"
-
-"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0:
-  version "5.7.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
-  integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
-
-semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0:
-  version "6.3.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
-  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
-
-semver@~5.3.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
-  integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8=
-
-send@0.17.1:
-  version "0.17.1"
-  resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
-  integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
-  dependencies:
-    debug "2.6.9"
-    depd "~1.1.2"
-    destroy "~1.0.4"
-    encodeurl "~1.0.2"
-    escape-html "~1.0.3"
-    etag "~1.8.1"
-    fresh "0.5.2"
-    http-errors "~1.7.2"
-    mime "1.6.0"
-    ms "2.1.1"
-    on-finished "~2.3.0"
-    range-parser "~1.2.1"
-    statuses "~1.5.0"
-
-serialize-javascript@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65"
-  integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==
-
-serve-index@^1.9.1:
-  version "1.9.1"
-  resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
-  integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=
-  dependencies:
-    accepts "~1.3.4"
-    batch "0.6.1"
-    debug "2.6.9"
-    escape-html "~1.0.3"
-    http-errors "~1.6.2"
-    mime-types "~2.1.17"
-    parseurl "~1.3.2"
-
-serve-static@1.14.1:
-  version "1.14.1"
-  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
-  integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
-  dependencies:
-    encodeurl "~1.0.2"
-    escape-html "~1.0.3"
-    parseurl "~1.3.3"
-    send "0.17.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"
-  integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
-
-set-immediate-shim@~1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
-  integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=
-
-set-value@^2.0.0, set-value@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
-  integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==
-  dependencies:
-    extend-shallow "^2.0.1"
-    is-extendable "^0.1.1"
-    is-plain-object "^2.0.3"
-    split-string "^3.0.1"
-
-setimmediate@^1.0.4:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
-  integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
-
-setprototypeof@1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
-  integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
-
-setprototypeof@1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
-  integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
-
-sha.js@^2.4.0, sha.js@^2.4.8:
-  version "2.4.11"
-  resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
-  integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
-  dependencies:
-    inherits "^2.0.1"
-    safe-buffer "^5.0.1"
-
-sha256@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/sha256/-/sha256-0.2.0.tgz#73a0b418daab7035bff86e8491e363412fc2ab05"
-  integrity sha1-c6C0GNqrcDW/+G6EkeNjQS/CqwU=
-  dependencies:
-    convert-hex "~0.1.0"
-    convert-string "~0.1.0"
-
-shallow-clone@^0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060"
-  integrity sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=
-  dependencies:
-    is-extendable "^0.1.1"
-    kind-of "^2.0.1"
-    lazy-cache "^0.2.3"
-    mixin-object "^2.0.1"
-
-shallow-clone@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571"
-  integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==
-  dependencies:
-    is-extendable "^0.1.1"
-    kind-of "^5.0.0"
-    mixin-object "^2.0.1"
-
-shebang-command@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
-  integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
-  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"
-  integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
-
-shell-quote@^1.6.1:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767"
-  integrity sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=
-  dependencies:
-    array-filter "~0.0.0"
-    array-map "~0.0.0"
-    array-reduce "~0.0.0"
-    jsonify "~0.0.0"
-
-sift@7.0.1:
-  version "7.0.1"
-  resolved "https://registry.yarnpkg.com/sift/-/sift-7.0.1.tgz#47d62c50b159d316f1372f8b53f9c10cd21a4b08"
-  integrity sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==
-
-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"
-  integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
-
-slash@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
-  integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=
-
-slash@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
-  integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
-
-slash@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
-  integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
-
-slice-ansi@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
-  integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==
-  dependencies:
-    ansi-styles "^3.2.0"
-    astral-regex "^1.0.0"
-    is-fullwidth-code-point "^2.0.0"
-
-sliced@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41"
-  integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=
-
-slide@^1.1.6:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
-  integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=
-
-smart-buffer@4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.2.tgz#5207858c3815cc69110703c6b94e46c15634395d"
-  integrity sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==
-
-snapdragon-node@^2.0.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
-  integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==
-  dependencies:
-    define-property "^1.0.0"
-    isobject "^3.0.0"
-    snapdragon-util "^3.0.1"
-
-snapdragon-util@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2"
-  integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==
-  dependencies:
-    kind-of "^3.2.0"
-
-snapdragon@^0.8.1:
-  version "0.8.2"
-  resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d"
-  integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==
-  dependencies:
-    base "^0.11.1"
-    debug "^2.2.0"
-    define-property "^0.2.5"
-    extend-shallow "^2.0.1"
-    map-cache "^0.2.2"
-    source-map "^0.5.6"
-    source-map-resolve "^0.5.0"
-    use "^3.1.0"
-
-snekfetch@^3.6.4:
-  version "3.6.4"
-  resolved "https://registry.yarnpkg.com/snekfetch/-/snekfetch-3.6.4.tgz#d13e80a616d892f3d38daae4289f4d258a645120"
-  integrity sha512-NjxjITIj04Ffqid5lqr7XdgwM7X61c/Dns073Ly170bPQHLm6jkmelye/eglS++1nfTWktpP6Y2bFXjdPlQqdw==
-
-snyk-config@^2.2.1:
-  version "2.2.3"
-  resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-2.2.3.tgz#8e09bb98602ad044954d30a9fc1695ab5b6042fa"
-  integrity sha512-9NjxHVMd1U1LFw66Lya4LXgrsFUiuRiL4opxfTFo0LmMNzUoU5Bk/p0zDdg3FE5Wg61r4fP2D8w+QTl6M8CGiw==
-  dependencies:
-    debug "^3.1.0"
-    lodash "^4.17.15"
-    nconf "^0.10.0"
-
-snyk-docker-plugin@1.25.1:
-  version "1.25.1"
-  resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-1.25.1.tgz#3f97dda88adfac2e1938151372d07905767bc8a1"
-  integrity sha512-n/LfA7VXjPEcSz2ZfZonT/DPSC89Zs1/HD0inPFN4RLQT3WiQnjqJUXct+D0nWwEVfhLWNc+Y7PLcTjpnZ9R3Q==
-  dependencies:
-    debug "^4.1.1"
-    dockerfile-ast "0.0.16"
-    semver "^6.1.0"
-    tslib "^1"
-
-snyk-go-parser@1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/snyk-go-parser/-/snyk-go-parser-1.3.1.tgz#427387507578baf008a3e73828e0e53ed8c796f3"
-  integrity sha512-jrFRfIk6yGHFeipGD66WV9ei/A/w/lIiGqI80w1ndMbg6D6M5pVNbK7ngDTmo4GdHrZDYqx/VBGBsUm2bol3Rg==
-  dependencies:
-    toml "^3.0.0"
-    tslib "^1.9.3"
-
-snyk-go-plugin@1.11.0:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/snyk-go-plugin/-/snyk-go-plugin-1.11.0.tgz#7810242391e39588929de47b829d04938bac8f1a"
-  integrity sha512-9hsGgloioGuey5hbZfv+MkFEslxXHyzUlaAazcR0NsY7VLyG/b2g3f88f/ZwCwlWaKL9LMv/ERIiey3oWAB/qg==
-  dependencies:
-    debug "^4.1.1"
-    graphlib "^2.1.1"
-    snyk-go-parser "1.3.1"
-    tmp "0.0.33"
-    tslib "^1.10.0"
-
-snyk-gradle-plugin@2.12.5:
-  version "2.12.5"
-  resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-2.12.5.tgz#6da1c9135b4cee2d6cd32653e569a1f56977d173"
-  integrity sha512-AmiQQUL0nlY3SjWUSMSmmbp273ETJzsqvk1E8jf+G/Q3mRl9xZ6BkPMebweD/y5d/smoQmr6rKL57OG+OXoi3w==
-  dependencies:
-    "@types/debug" "^4.1.4"
-    chalk "^2.4.2"
-    clone-deep "^0.3.0"
-    debug "^4.1.1"
-    tmp "0.0.33"
-    tslib "^1.9.3"
-
-snyk-module@1.9.1, snyk-module@^1.6.0, snyk-module@^1.9.1:
-  version "1.9.1"
-  resolved "https://registry.yarnpkg.com/snyk-module/-/snyk-module-1.9.1.tgz#b2a78f736600b0ab680f1703466ed7309c980804"
-  integrity sha512-A+CCyBSa4IKok5uEhqT+hV/35RO6APFNLqk9DRRHg7xW2/j//nPX8wTSZUPF8QeRNEk/sX+6df7M1y6PBHGSHA==
-  dependencies:
-    debug "^3.1.0"
-    hosted-git-info "^2.7.1"
-
-snyk-mvn-plugin@2.3.3:
-  version "2.3.3"
-  resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.3.3.tgz#0368a73b94446564814808bda48b334a7e9a7cb8"
-  integrity sha512-NYFL+jtHfAJk+Jdyme4I8pTvg/wfoHgkOs1g1nFUEPTcpBb5mfqy7Q9hDJWvnfXY8M6P9aEqvO+bmCVgTQvySg==
-  dependencies:
-    lodash "^4.17.15"
-    tslib "1.9.3"
-
-snyk-nodejs-lockfile-parser@1.16.0:
-  version "1.16.0"
-  resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.16.0.tgz#1c1d0aba4643830901ef999415816e7a92b0974d"
-  integrity sha512-cf3uozRXEG88nsjOQlo+SfOJPpcLs45qpnuk2vhBBZ577IMnV+fTOJQsP2YRiikLUbdgkVlduviwUO6OVn1PhA==
-  dependencies:
-    "@yarnpkg/lockfile" "^1.0.2"
-    graphlib "^2.1.5"
-    lodash "^4.17.14"
-    source-map-support "^0.5.7"
-    tslib "^1.9.3"
-    uuid "^3.3.2"
-
-snyk-nuget-plugin@1.11.3:
-  version "1.11.3"
-  resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.11.3.tgz#38f45b3a2bb97ae7e307726920bcc189b012ea37"
-  integrity sha512-UgLTMr7Vz0qZoL15SkFAUfMb4Vw/qFxf6lBoL2v8xA+Mqdvn2Yu9x/yW659ElFVSUjniqKTFyloKq9/XSv+c+A==
-  dependencies:
-    debug "^3.1.0"
-    jszip "^3.1.5"
-    lodash "^4.17.14"
-    snyk-paket-parser "1.5.0"
-    tslib "^1.9.3"
-    xml2js "^0.4.17"
-
-snyk-paket-parser@1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/snyk-paket-parser/-/snyk-paket-parser-1.5.0.tgz#a0e96888d9d304b1ae6203a0369971575f099548"
-  integrity sha512-1CYMPChJ9D9LBy3NLqHyv8TY7pR/LMISSr08LhfFw/FpfRZ+gTH8W6bbxCmybAYrOFNCqZkRprqOYDqZQFHipA==
-  dependencies:
-    tslib "^1.9.3"
-
-snyk-php-plugin@1.6.4:
-  version "1.6.4"
-  resolved "https://registry.yarnpkg.com/snyk-php-plugin/-/snyk-php-plugin-1.6.4.tgz#c3470aea2f185d2f3417cfc5e966ecf7fd1efa20"
-  integrity sha512-FFQeimtbwq17nDUS0o0zuKgyjXSX7SpoC9iYTeKvxTXrmKf2QlxTtPvmMM4/hQxehEu1i40ow1Ozw0Ahxm8Dpw==
-  dependencies:
-    "@snyk/composer-lockfile-parser" "1.0.3"
-    tslib "1.9.3"
-
-snyk-policy@1.13.5:
-  version "1.13.5"
-  resolved "https://registry.yarnpkg.com/snyk-policy/-/snyk-policy-1.13.5.tgz#c5cf262f759879a65ab0810dd58d59c8ec7e9e47"
-  integrity sha512-KI6GHt+Oj4fYKiCp7duhseUj5YhyL/zJOrrJg0u6r59Ux9w8gmkUYT92FHW27ihwuT6IPzdGNEuy06Yv2C9WaQ==
-  dependencies:
-    debug "^3.1.0"
-    email-validator "^2.0.4"
-    js-yaml "^3.13.1"
-    lodash.clonedeep "^4.5.0"
-    semver "^6.0.0"
-    snyk-module "^1.9.1"
-    snyk-resolve "^1.0.1"
-    snyk-try-require "^1.3.1"
-    then-fs "^2.0.0"
-
-snyk-python-plugin@1.10.2:
-  version "1.10.2"
-  resolved "https://registry.yarnpkg.com/snyk-python-plugin/-/snyk-python-plugin-1.10.2.tgz#e89548a47d4cfe98351604ed8a3372bfd9fbebbd"
-  integrity sha512-dLswHfVI9Ax8+Ia/onhv1p9S5y+Ie/oELOfpfNApbb0BPTJ5k1c2CQ7WcgQ5/nDRMUOgoKn4VTObaAGmD5or9A==
-  dependencies:
-    tmp "0.0.33"
-
-snyk-resolve-deps@4.3.0:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/snyk-resolve-deps/-/snyk-resolve-deps-4.3.0.tgz#0f356ebaed9ca78806024aa0d564c229377f39cb"
-  integrity sha512-HWGiwnz0hH59tyvcpaWho0G8oHlFiiTMgWbx/wZMZmCcgrmmqbjNRp6g+Zg6Cr0Ng2Gy0oc4jqvwspmOoh0c4g==
-  dependencies:
-    "@types/node" "^6.14.4"
-    "@types/package-json" "^5.0.0"
-    "@types/semver" "^5.5.0"
-    ansicolors "^0.3.2"
-    debug "^3.2.5"
-    lodash.assign "^4.2.0"
-    lodash.assignin "^4.2.0"
-    lodash.clone "^4.5.0"
-    lodash.flatten "^4.4.0"
-    lodash.get "^4.4.2"
-    lodash.set "^4.3.2"
-    lru-cache "^4.0.0"
-    semver "^5.5.1"
-    snyk-module "^1.6.0"
-    snyk-resolve "^1.0.0"
-    snyk-tree "^1.0.0"
-    snyk-try-require "^1.1.1"
-    then-fs "^2.0.0"
-
-snyk-resolve@1.0.1, snyk-resolve@^1.0.0, snyk-resolve@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/snyk-resolve/-/snyk-resolve-1.0.1.tgz#eaa4a275cf7e2b579f18da5b188fe601b8eed9ab"
-  integrity sha512-7+i+LLhtBo1Pkth01xv+RYJU8a67zmJ8WFFPvSxyCjdlKIcsps4hPQFebhz+0gC5rMemlaeIV6cqwqUf9PEDpw==
-  dependencies:
-    debug "^3.1.0"
-    then-fs "^2.0.0"
-
-snyk-sbt-plugin@2.6.1:
-  version "2.6.1"
-  resolved "https://registry.yarnpkg.com/snyk-sbt-plugin/-/snyk-sbt-plugin-2.6.1.tgz#bbd01291cb778d5e44689a4a1b0e3de1727942fe"
-  integrity sha512-zWU14cm+cpamJ0CJdekTfgmv6ifdgVcapO6d27KTJThqRuR0arCqGPPyZa/Zl+jzhcK0dtRS4Ihk7g+d36SWIg==
-  dependencies:
-    semver "^6.1.2"
-    tmp "^0.1.0"
-    tree-kill "^1.2.1"
-    tslib "^1.10.0"
-
-snyk-tree@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/snyk-tree/-/snyk-tree-1.0.0.tgz#0fb73176dbf32e782f19100294160448f9111cc8"
-  integrity sha1-D7cxdtvzLngvGRAClBYESPkRHMg=
-  dependencies:
-    archy "^1.0.0"
-
-snyk-try-require@1.3.1, snyk-try-require@^1.1.1, snyk-try-require@^1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/snyk-try-require/-/snyk-try-require-1.3.1.tgz#6e026f92e64af7fcccea1ee53d524841e418a212"
-  integrity sha1-bgJvkuZK9/zM6h7lPVJIQeQYohI=
-  dependencies:
-    debug "^3.1.0"
-    lodash.clonedeep "^4.3.0"
-    lru-cache "^4.0.0"
-    then-fs "^2.0.0"
-
-snyk@^1.208.0:
-  version "1.211.0"
-  resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.211.0.tgz#93871cbf5bb8a95de18f93eb9ac9c2d08a2f784a"
-  integrity sha512-oDS0+Nj9+r+KjW6pjHqKOVIk+fYihaQfiupie0oZpGIaq59G2t4726Oww99iLbn4WLVcI5/LhteQN/xwvKSL1A==
-  dependencies:
-    "@snyk/dep-graph" "1.12.0"
-    "@snyk/gemfile" "1.2.0"
-    "@types/agent-base" "^4.2.0"
-    abbrev "^1.1.1"
-    ansi-escapes "^4.1.0"
-    chalk "^2.4.2"
-    configstore "^3.1.2"
-    debug "^3.1.0"
-    diff "^4.0.1"
-    git-url-parse "11.1.2"
-    glob "^7.1.3"
-    inquirer "^6.2.2"
-    lodash "^4.17.14"
-    needle "^2.2.4"
-    opn "^5.5.0"
-    os-name "^3.0.0"
-    proxy-agent "^3.1.0"
-    proxy-from-env "^1.0.0"
-    semver "^6.0.0"
-    snyk-config "^2.2.1"
-    snyk-docker-plugin "1.25.1"
-    snyk-go-plugin "1.11.0"
-    snyk-gradle-plugin "2.12.5"
-    snyk-module "1.9.1"
-    snyk-mvn-plugin "2.3.3"
-    snyk-nodejs-lockfile-parser "1.16.0"
-    snyk-nuget-plugin "1.11.3"
-    snyk-php-plugin "1.6.4"
-    snyk-policy "1.13.5"
-    snyk-python-plugin "1.10.2"
-    snyk-resolve "1.0.1"
-    snyk-resolve-deps "4.3.0"
-    snyk-sbt-plugin "2.6.1"
-    snyk-tree "^1.0.0"
-    snyk-try-require "1.3.1"
-    source-map-support "^0.5.11"
-    strip-ansi "^5.2.0"
-    tempfile "^2.0.0"
-    then-fs "^2.0.0"
-    update-notifier "^2.5.0"
-    uuid "^3.3.2"
-
-socket.io-adapter@~1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b"
-  integrity sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=
-
-socket.io-client@2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.2.0.tgz#84e73ee3c43d5020ccc1a258faeeb9aec2723af7"
-  integrity sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==
-  dependencies:
-    backo2 "1.0.2"
-    base64-arraybuffer "0.1.5"
-    component-bind "1.0.0"
-    component-emitter "1.2.1"
-    debug "~3.1.0"
-    engine.io-client "~3.3.1"
-    has-binary2 "~1.0.2"
-    has-cors "1.1.0"
-    indexof "0.0.1"
-    object-component "0.0.3"
-    parseqs "0.0.5"
-    parseuri "0.0.5"
-    socket.io-parser "~3.3.0"
-    to-array "0.1.4"
-
-socket.io-parser@~3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f"
-  integrity sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==
-  dependencies:
-    component-emitter "1.2.1"
-    debug "~3.1.0"
-    isarray "2.0.1"
-
-socket.io@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.2.0.tgz#f0f633161ef6712c972b307598ecd08c9b1b4d5b"
-  integrity sha512-wxXrIuZ8AILcn+f1B4ez4hJTPG24iNgxBBDaJfT6MsyOhVYiTXWexGoPkd87ktJG8kQEcL/NBvRi64+9k4Kc0w==
-  dependencies:
-    debug "~4.1.0"
-    engine.io "~3.3.1"
-    has-binary2 "~1.0.2"
-    socket.io-adapter "~1.1.0"
-    socket.io-client "2.2.0"
-    socket.io-parser "~3.3.0"
-
-sockjs-client@1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.3.0.tgz#12fc9d6cb663da5739d3dc5fb6e8687da95cb177"
-  integrity sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==
-  dependencies:
-    debug "^3.2.5"
-    eventsource "^1.0.7"
-    faye-websocket "~0.11.1"
-    inherits "^2.0.3"
-    json3 "^3.3.2"
-    url-parse "^1.4.3"
-
-sockjs@0.3.19:
-  version "0.3.19"
-  resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d"
-  integrity sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==
-  dependencies:
-    faye-websocket "^0.10.0"
-    uuid "^3.0.1"
-
-socks-proxy-agent@^4.0.0, socks-proxy-agent@^4.0.1:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386"
-  integrity sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==
-  dependencies:
-    agent-base "~4.2.1"
-    socks "~2.3.2"
-
-socks@~2.3.2:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.2.tgz#ade388e9e6d87fdb11649c15746c578922a5883e"
-  integrity sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ==
-  dependencies:
-    ip "^1.1.5"
-    smart-buffer "4.0.2"
-
-sort-keys@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
-  integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=
-  dependencies:
-    is-plain-obj "^1.0.0"
-
-source-list-map@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
-  integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
-
-source-map-resolve@^0.5.0:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259"
-  integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==
-  dependencies:
-    atob "^2.1.1"
-    decode-uri-component "^0.2.0"
-    resolve-url "^0.2.1"
-    source-map-url "^0.4.0"
-    urix "^0.1.0"
-
-source-map-support@^0.5.11, source-map-support@^0.5.7, source-map-support@~0.5.12:
-  version "0.5.13"
-  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932"
-  integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==
-  dependencies:
-    buffer-from "^1.0.0"
-    source-map "^0.6.0"
-
-source-map-url@^0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
-  integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
-
-source-map@^0.4.2:
-  version "0.4.4"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
-  integrity sha1-66T12pwNyZneaAMti092FzZSA2s=
-  dependencies:
-    amdefine ">=0.0.4"
-
-source-map@^0.5.0, source-map@^0.5.6:
-  version "0.5.7"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
-  integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
-
-source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
-  integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
-
-sparse-bitfield@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11"
-  integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE=
-  dependencies:
-    memory-pager "^1.0.2"
-
-spdx-correct@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
-  integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==
-  dependencies:
-    spdx-expression-parse "^3.0.0"
-    spdx-license-ids "^3.0.0"
-
-spdx-exceptions@^2.1.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977"
-  integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==
-
-spdx-expression-parse@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
-  integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==
-  dependencies:
-    spdx-exceptions "^2.1.0"
-    spdx-license-ids "^3.0.0"
-
-spdx-license-ids@^3.0.0:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
-  integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==
-
-spdy-transport@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31"
-  integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==
-  dependencies:
-    debug "^4.1.0"
-    detect-node "^2.0.4"
-    hpack.js "^2.1.6"
-    obuf "^1.1.2"
-    readable-stream "^3.0.6"
-    wbuf "^1.7.3"
-
-spdy@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.1.tgz#6f12ed1c5db7ea4f24ebb8b89ba58c87c08257f2"
-  integrity sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==
-  dependencies:
-    debug "^4.1.0"
-    handle-thing "^2.0.0"
-    http-deceiver "^1.2.7"
-    select-hose "^2.0.0"
-    spdy-transport "^3.0.0"
-
-split-string@^3.0.1, split-string@^3.0.2:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
-  integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==
-  dependencies:
-    extend-shallow "^3.0.0"
-
-split2@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493"
-  integrity sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==
-  dependencies:
-    through2 "^2.0.2"
-
-split@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9"
-  integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==
-  dependencies:
-    through "2"
-
-sprintf-js@~1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
-  integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
-
-sshpk@^1.7.0:
-  version "1.16.1"
-  resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
-  integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
-  dependencies:
-    asn1 "~0.2.3"
-    assert-plus "^1.0.0"
-    bcrypt-pbkdf "^1.0.0"
-    dashdash "^1.12.0"
-    ecc-jsbn "~0.1.1"
-    getpass "^0.1.1"
-    jsbn "~0.1.0"
-    safer-buffer "^2.0.2"
-    tweetnacl "~0.14.0"
-
-ssri@^6.0.0, ssri@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
-  integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
-  dependencies:
-    figgy-pudding "^3.5.1"
-
-static-extend@^0.1.1:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
-  integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=
-  dependencies:
-    define-property "^0.2.5"
-    object-copy "^0.1.0"
-
-"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
-  integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
-
-stdout-stream@^1.4.0:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de"
-  integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==
-  dependencies:
-    readable-stream "^2.0.1"
-
-stream-browserify@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
-  integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==
-  dependencies:
-    inherits "~2.0.1"
-    readable-stream "^2.0.2"
-
-stream-each@^1.1.0:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae"
-  integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==
-  dependencies:
-    end-of-stream "^1.1.0"
-    stream-shift "^1.0.0"
-
-stream-http@^2.7.2:
-  version "2.8.3"
-  resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc"
-  integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==
-  dependencies:
-    builtin-status-codes "^3.0.0"
-    inherits "^2.0.1"
-    readable-stream "^2.3.6"
-    to-arraybuffer "^1.0.0"
-    xtend "^4.0.0"
-
-stream-shift@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
-  integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
-
-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"
-  integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
-  dependencies:
-    code-point-at "^1.0.0"
-    is-fullwidth-code-point "^1.0.0"
-    strip-ansi "^3.0.0"
-
-"string-width@^1.0.2 || 2", 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"
-  integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
-  dependencies:
-    is-fullwidth-code-point "^2.0.0"
-    strip-ansi "^4.0.0"
-
-string-width@^3.0.0, string-width@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
-  integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
-  dependencies:
-    emoji-regex "^7.0.1"
-    is-fullwidth-code-point "^2.0.0"
-    strip-ansi "^5.1.0"
-
-string.prototype.padend@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0"
-  integrity sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=
-  dependencies:
-    define-properties "^1.1.2"
-    es-abstract "^1.4.3"
-    function-bind "^1.0.2"
-
-string_decoder@^1.0.0, string_decoder@^1.1.1:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
-  integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
-  dependencies:
-    safe-buffer "~5.2.0"
-
-string_decoder@~0.10.x:
-  version "0.10.31"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
-  integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
-
-string_decoder@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
-  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
-  dependencies:
-    safe-buffer "~5.1.0"
-
-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"
-  integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
-  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"
-  integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
-  dependencies:
-    ansi-regex "^3.0.0"
-
-strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
-  integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
-  dependencies:
-    ansi-regex "^4.1.0"
-
-strip-bom@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
-  integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=
-  dependencies:
-    is-utf8 "^0.2.0"
-
-strip-bom@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
-  integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
-
-strip-eof@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
-  integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
-
-strip-indent@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
-  integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=
-  dependencies:
-    get-stdin "^4.0.1"
-
-strip-indent@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
-  integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=
-
-strip-json-comments@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
-  integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
-
-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"
-  integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
-
-strong-log-transformer@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10"
-  integrity sha512-B3Hgul+z0L9a236FAUC9iZsL+nVHgoCJnqCbN588DjYxvGXaXaaFbfmQ/JhvKjZwsOukuR72XbHv71Qkug0HxA==
-  dependencies:
-    duplexer "^0.1.1"
-    minimist "^1.2.0"
-    through "^2.3.4"
-
-supports-color@6.1.0, supports-color@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
-  integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
-  dependencies:
-    has-flag "^3.0.0"
-
-supports-color@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
-  integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
-
-supports-color@^5.3.0:
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
-  integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
-  dependencies:
-    has-flag "^3.0.0"
-
-table@^5.2.3:
-  version "5.4.5"
-  resolved "https://registry.yarnpkg.com/table/-/table-5.4.5.tgz#c8f4ea2d8fee08c0027fac27b0ec0a4fe01dfa42"
-  integrity sha512-oGa2Hl7CQjfoaogtrOHEJroOcYILTx7BZWLGsJIlzoWmB2zmguhNfPJZsWPKYek/MgCxfco54gEi31d1uN2hFA==
-  dependencies:
-    ajv "^6.10.2"
-    lodash "^4.17.14"
-    slice-ansi "^2.1.0"
-    string-width "^3.0.0"
-
-tapable@^1.0.0, tapable@^1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
-  integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
-
-tar@^2.0.0:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40"
-  integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==
-  dependencies:
-    block-stream "*"
-    fstream "^1.0.12"
-    inherits "2"
-
-tar@^4, tar@^4.4.10, tar@^4.4.8:
-  version "4.4.10"
-  resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1"
-  integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==
-  dependencies:
-    chownr "^1.1.1"
-    fs-minipass "^1.2.5"
-    minipass "^2.3.5"
-    minizlib "^1.2.1"
-    mkdirp "^0.5.0"
-    safe-buffer "^5.1.2"
-    yallist "^3.0.3"
-
-temp-dir@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d"
-  integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=
-
-temp-write@^3.4.0:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-3.4.0.tgz#8cff630fb7e9da05f047c74ce4ce4d685457d492"
-  integrity sha1-jP9jD7fp2gXwR8dM5M5NaFRX1JI=
-  dependencies:
-    graceful-fs "^4.1.2"
-    is-stream "^1.1.0"
-    make-dir "^1.0.0"
-    pify "^3.0.0"
-    temp-dir "^1.0.0"
-    uuid "^3.0.1"
-
-tempfile@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-2.0.0.tgz#6b0446856a9b1114d1856ffcbe509cccb0977265"
-  integrity sha1-awRGhWqbERTRhW/8vlCczLCXcmU=
-  dependencies:
-    temp-dir "^1.0.0"
-    uuid "^3.0.1"
-
-term-size@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
-  integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=
-  dependencies:
-    execa "^0.7.0"
-
-terser-webpack-plugin@^1.4.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4"
-  integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==
-  dependencies:
-    cacache "^12.0.2"
-    find-cache-dir "^2.1.0"
-    is-wsl "^1.1.0"
-    schema-utils "^1.0.0"
-    serialize-javascript "^1.7.0"
-    source-map "^0.6.1"
-    terser "^4.1.2"
-    webpack-sources "^1.4.0"
-    worker-farm "^1.7.0"
-
-terser@^4.1.2:
-  version "4.1.3"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-4.1.3.tgz#6074fbcf3517561c3272ea885f422c7a8c32d689"
-  integrity sha512-on13d+cnpn5bMouZu+J8tPYQecsdRJCJuxFJ+FVoPBoLJgk5bCBkp+Uen2hWyi0KIUm6eDarnlAlH+KgIx/PuQ==
-  dependencies:
-    commander "^2.20.0"
-    source-map "~0.6.1"
-    source-map-support "~0.5.12"
-
-text-extensions@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-2.0.0.tgz#43eabd1b495482fae4a2bf65e5f56c29f69220f6"
-  integrity sha512-F91ZqLgvi1E0PdvmxMgp+gcf6q8fMH7mhdwWfzXnl1k+GbpQDmi8l7DzLC5JTASKbwpY3TfxajAUzAXcv2NmsQ==
-
-text-table@^0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
-  integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
-
-then-fs@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/then-fs/-/then-fs-2.0.0.tgz#72f792dd9d31705a91ae19ebfcf8b3f968c81da2"
-  integrity sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI=
-  dependencies:
-    promise ">=3.2 <8"
-
-thenify-all@^1.0.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
-  integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=
-  dependencies:
-    thenify ">= 3.1.0 < 4"
-
-"thenify@>= 3.1.0 < 4":
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839"
-  integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=
-  dependencies:
-    any-promise "^1.0.0"
-
-through2@^2.0.0, through2@^2.0.2:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
-  integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
-  dependencies:
-    readable-stream "~2.3.6"
-    xtend "~4.0.1"
-
-through2@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.1.tgz#39276e713c3302edf9e388dd9c812dd3b825bd5a"
-  integrity sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==
-  dependencies:
-    readable-stream "2 || 3"
-
-through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6:
-  version "2.3.8"
-  resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
-  integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
-
-thunkify@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d"
-  integrity sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=
-
-thunky@^1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826"
-  integrity sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==
-
-timed-out@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
-  integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
-
-timers-browserify@^2.0.4:
-  version "2.0.10"
-  resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae"
-  integrity sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==
-  dependencies:
-    setimmediate "^1.0.4"
-
-tmp@0.0.33, tmp@^0.0.33:
-  version "0.0.33"
-  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
-  integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
-  dependencies:
-    os-tmpdir "~1.0.2"
-
-tmp@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877"
-  integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==
-  dependencies:
-    rimraf "^2.6.3"
-
-to-array@0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
-  integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA=
-
-to-arraybuffer@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
-  integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
-
-to-fast-properties@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
-  integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
-
-to-object-path@^0.3.0:
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
-  integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=
-  dependencies:
-    kind-of "^3.0.2"
-
-to-readable-stream@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771"
-  integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==
-
-to-regex-range@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38"
-  integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=
-  dependencies:
-    is-number "^3.0.0"
-    repeat-string "^1.6.1"
-
-to-regex@^3.0.1, to-regex@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
-  integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==
-  dependencies:
-    define-property "^2.0.2"
-    extend-shallow "^3.0.2"
-    regex-not "^1.0.2"
-    safe-regex "^1.1.0"
-
-toasters@2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/toasters/-/toasters-2.0.1.tgz#a41c2e1652e51b9b782624027672363ed6547509"
-  integrity sha512-zqZz0itO/iQJhnLKkTGpj1FhWg0YKo2EnAGD0zjDPrR2rQgfSymCaHaQT0Gp9VIuPpu+3UY8m4w4HTI49HpdFw==
-
-toidentifier@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
-  integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
-
-toml@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee"
-  integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==
-
-toposort@^1.0.0:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
-  integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk=
-
-tough-cookie@~2.4.3:
-  version "2.4.3"
-  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
-  integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
-  dependencies:
-    psl "^1.1.24"
-    punycode "^1.4.1"
-
-tr46@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
-  integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=
-  dependencies:
-    punycode "^2.1.0"
-
-tree-kill@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.1.tgz#5398f374e2f292b9dcc7b2e71e30a5c3bb6c743a"
-  integrity sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==
-
-trim-newlines@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
-  integrity sha1-WIeWa7WCpFA6QetST301ARgVphM=
-
-trim-newlines@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20"
-  integrity sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=
-
-trim-off-newlines@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3"
-  integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM=
-
-trim-right@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
-  integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=
-
-"true-case-path@^1.0.2":
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d"
-  integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==
-  dependencies:
-    glob "^7.1.2"
-
-tryer@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
-  integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
-
-tslib@1.9.3:
-  version "1.9.3"
-  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
-  integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
-
-tslib@^1, tslib@^1.10.0, tslib@^1.9.0, tslib@^1.9.3:
-  version "1.10.0"
-  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
-  integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
-
-tsscmp@^1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
-  integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==
-
-tty-browserify@0.0.0:
-  version "0.0.0"
-  resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
-  integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=
-
-tunnel-agent@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
-  integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=
-  dependencies:
-    safe-buffer "^5.0.1"
-
-tweetnacl@^0.14.3, tweetnacl@~0.14.0:
-  version "0.14.5"
-  resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
-  integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
-
-tweetnacl@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.1.tgz#2594d42da73cd036bd0d2a54683dd35a6b55ca17"
-  integrity sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A==
-
-type-check@~0.3.2:
-  version "0.3.2"
-  resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
-  integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=
-  dependencies:
-    prelude-ls "~1.1.2"
-
-type-fest@^0.3.0:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1"
-  integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==
-
-type-fest@^0.5.2:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2"
-  integrity sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==
-
-type-fest@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
-  integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==
-
-type-is@~1.6.17, type-is@~1.6.18:
-  version "1.6.18"
-  resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
-  integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
-  dependencies:
-    media-typer "0.3.0"
-    mime-types "~2.1.24"
-
-typedarray@^0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
-  integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-
-uglify-js@3.4.x:
-  version "3.4.10"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
-  integrity sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==
-  dependencies:
-    commander "~2.19.0"
-    source-map "~0.6.1"
-
-uglify-js@^3.1.4:
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"
-  integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==
-  dependencies:
-    commander "~2.20.0"
-    source-map "~0.6.1"
-
-uid-number@0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
-  integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=
-
-umask@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d"
-  integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=
-
-underscore@^1.9.1:
-  version "1.9.1"
-  resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
-  integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==
-
-unicode-canonical-property-names-ecmascript@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
-  integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==
-
-unicode-match-property-ecmascript@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c"
-  integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==
-  dependencies:
-    unicode-canonical-property-names-ecmascript "^1.0.4"
-    unicode-property-aliases-ecmascript "^1.0.4"
-
-unicode-match-property-value-ecmascript@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277"
-  integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==
-
-unicode-property-aliases-ecmascript@^1.0.4:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57"
-  integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==
-
-union-value@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
-  integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==
-  dependencies:
-    arr-union "^3.1.0"
-    get-value "^2.0.6"
-    is-extendable "^0.1.1"
-    set-value "^2.0.1"
-
-uniq@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
-  integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
-
-unique-filename@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
-  integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==
-  dependencies:
-    unique-slug "^2.0.0"
-
-unique-slug@^2.0.0:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c"
-  integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==
-  dependencies:
-    imurmurhash "^0.1.4"
-
-unique-string@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a"
-  integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=
-  dependencies:
-    crypto-random-string "^1.0.0"
-
-universal-user-agent@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-3.0.0.tgz#4cc88d68097bffd7ac42e3b7c903e7481424b4b9"
-  integrity sha512-T3siHThqoj5X0benA5H0qcDnrKGXzU8TKoX15x/tQHw1hQBvIEBHjxQ2klizYsqBOO/Q+WuxoQUihadeeqDnoA==
-  dependencies:
-    os-name "^3.0.0"
-
-universalify@^0.1.0:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
-  integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
-
-unpipe@1.0.0, unpipe@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
-  integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
-
-unset-value@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559"
-  integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=
-  dependencies:
-    has-value "^0.3.1"
-    isobject "^3.0.0"
-
-unzip-response@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
-  integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=
-
-upath@^1.1.1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068"
-  integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==
-
-update-notifier@^2.5.0:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6"
-  integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==
-  dependencies:
-    boxen "^1.2.1"
-    chalk "^2.0.1"
-    configstore "^3.0.0"
-    import-lazy "^2.1.0"
-    is-ci "^1.0.10"
-    is-installed-globally "^0.1.0"
-    is-npm "^1.0.0"
-    latest-version "^3.0.0"
-    semver-diff "^2.0.0"
-    xdg-basedir "^3.0.0"
-
-upper-case@^1.1.1:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
-  integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=
-
-uri-js@^4.2.2:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
-  integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==
-  dependencies:
-    punycode "^2.1.0"
-
-urix@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
-  integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
-
-url-parse-lax@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
-  integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=
-  dependencies:
-    prepend-http "^1.0.1"
-
-url-parse-lax@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
-  integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=
-  dependencies:
-    prepend-http "^2.0.0"
-
-url-parse@^1.4.3:
-  version "1.4.7"
-  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
-  integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
-  dependencies:
-    querystringify "^2.1.1"
-    requires-port "^1.0.0"
-
-url-template@^2.0.8:
-  version "2.0.8"
-  resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21"
-  integrity sha1-/FZaPMy/93MMd19WQflVV5FDnyE=
-
-url@^0.11.0:
-  version "0.11.0"
-  resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
-  integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=
-  dependencies:
-    punycode "1.3.2"
-    querystring "0.2.0"
-
-use@^3.1.0:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
-  integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
-
-util-deprecate@^1.0.1, util-deprecate@~1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
-  integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
-
-util-promisify@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/util-promisify/-/util-promisify-2.1.0.tgz#3c2236476c4d32c5ff3c47002add7c13b9a82a53"
-  integrity sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=
-  dependencies:
-    object.getownpropertydescriptors "^2.0.3"
-
-util.promisify@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030"
-  integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==
-  dependencies:
-    define-properties "^1.1.2"
-    object.getownpropertydescriptors "^2.0.3"
-
-util@0.10.3:
-  version "0.10.3"
-  resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
-  integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk=
-  dependencies:
-    inherits "2.0.1"
-
-util@^0.11.0:
-  version "0.11.1"
-  resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61"
-  integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==
-  dependencies:
-    inherits "2.0.3"
-
-utila@^0.4.0, utila@~0.4:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
-  integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=
-
-utils-merge@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
-  integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
-
-uuid@^3.0.1, uuid@^3.3.2:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
-  integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
-
-v8-compile-cache@2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
-  integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
-
-v8-compile-cache@^2.0.3:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
-  integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==
-
-validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
-  integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
-  dependencies:
-    spdx-correct "^3.0.0"
-    spdx-expression-parse "^3.0.0"
-
-validate-npm-package-name@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e"
-  integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34=
-  dependencies:
-    builtins "^1.0.3"
-
-vary@^1, vary@~1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
-  integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
-
-verror@1.10.0:
-  version "1.10.0"
-  resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
-  integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=
-  dependencies:
-    assert-plus "^1.0.0"
-    core-util-is "1.0.2"
-    extsprintf "^1.2.0"
-
-vm-browserify@^1.0.1:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019"
-  integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==
-
-vscode-languageserver-types@^3.5.0:
-  version "3.14.0"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz#d3b5952246d30e5241592b6dde8280e03942e743"
-  integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==
-
-vue-eslint-parser@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz#00f4e4da94ec974b821a26ff0ed0f7a78402b8a1"
-  integrity sha512-JlHVZwBBTNVvzmifwjpZYn0oPWH2SgWv5dojlZBsrhablDu95VFD+hriB1rQGwbD+bms6g+rAFhQHk6+NyiS6g==
-  dependencies:
-    debug "^4.1.0"
-    eslint-scope "^4.0.0"
-    eslint-visitor-keys "^1.0.0"
-    espree "^4.1.0"
-    esquery "^1.0.1"
-    lodash "^4.17.11"
-
-vue-hot-reload-api@^2.3.0, vue-hot-reload-api@^2.3.3:
-  version "2.3.3"
-  resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.3.tgz#2756f46cb3258054c5f4723de8ae7e87302a1ccf"
-  integrity sha512-KmvZVtmM26BQOMK1rwUZsrqxEGeKiYSZGA7SNWE6uExx8UX/cj9hq2MRV/wWC3Cq6AoeDGk57rL9YMFRel/q+g==
-
-vue-loader@^15.7.0:
-  version "15.7.1"
-  resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.1.tgz#6ccacd4122aa80f69baaac08ff295a62e3aefcfd"
-  integrity sha512-fwIKtA23Pl/rqfYP5TSGK7gkEuLhoTvRYW+TU7ER3q9GpNLt/PjG5NLv3XHRDiTg7OPM1JcckBgds+VnAc+HbA==
-  dependencies:
-    "@vue/component-compiler-utils" "^3.0.0"
-    hash-sum "^1.0.2"
-    loader-utils "^1.1.0"
-    vue-hot-reload-api "^2.3.0"
-    vue-style-loader "^4.1.0"
-
-vue-router@^3.0.7:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.1.1.tgz#0893c29548ba2dbe35ed104dcd1aa06743aa0ead"
-  integrity sha512-RAFCIQjs4gbDV7YNH44c1GT0jXSTFvNhKh3o0xiA/UWI1EM7Eriv45n8kM+R8NZ58wXEGtKVaDVBlrXJBuM/Bg==
-
-vue-style-loader@^4.1.0, vue-style-loader@^4.1.2:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
-  integrity sha512-0ip8ge6Gzz/Bk0iHovU9XAUQaFt/G2B61bnWa2tCcqqdgfHs1lF9xXorFbE55Gmy92okFT+8bfmySuUOu13vxQ==
-  dependencies:
-    hash-sum "^1.0.2"
-    loader-utils "^1.0.2"
-
-vue-template-compiler@^2.6.10:
-  version "2.6.10"
-  resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc"
-  integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==
-  dependencies:
-    de-indent "^1.0.2"
-    he "^1.1.0"
-
-vue-template-es2015-compiler@^1.9.0:
-  version "1.9.1"
-  resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
-  integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
-
-vue@^2.6.10:
-  version "2.6.10"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
-  integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
-
-vuex@^3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.1.1.tgz#0c264bfe30cdbccf96ab9db3177d211828a5910e"
-  integrity sha512-ER5moSbLZuNSMBFnEBVGhQ1uCBNJslH9W/Dw2W7GZN23UQA69uapP5GTT9Vm8Trc0PzBSVt6LzF3hGjmv41xcg==
-
-watchpack@^1.6.0:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
-  integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==
-  dependencies:
-    chokidar "^2.0.2"
-    graceful-fs "^4.1.2"
-    neo-async "^2.5.0"
-
-wbuf@^1.1.0, wbuf@^1.7.3:
-  version "1.7.3"
-  resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df"
-  integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==
-  dependencies:
-    minimalistic-assert "^1.0.0"
-
-wcwidth@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
-  integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=
-  dependencies:
-    defaults "^1.0.3"
-
-webidl-conversions@^4.0.2:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
-  integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
-
-webpack-bundle-analyzer@^3.4.1:
-  version "3.4.1"
-  resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.4.1.tgz#430544c7ba1631baccf673475ca8300cb74a3c47"
-  integrity sha512-Bs8D/1zF+17lhqj2OYmzi7HEVYqEVxu7lCO9Ff8BwajenOU0vAwEoV8e4ICCPNZAcqR1PCR/7o2SkW+cnCmF0A==
-  dependencies:
-    acorn "^6.0.7"
-    acorn-walk "^6.1.1"
-    bfj "^6.1.1"
-    chalk "^2.4.1"
-    commander "^2.18.0"
-    ejs "^2.6.1"
-    express "^4.16.3"
-    filesize "^3.6.1"
-    gzip-size "^5.0.0"
-    lodash "^4.17.15"
-    mkdirp "^0.5.1"
-    opener "^1.5.1"
-    ws "^6.0.0"
-
-webpack-cli@^3.3.5:
-  version "3.3.6"
-  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.6.tgz#2c8c399a2642133f8d736a359007a052e060032c"
-  integrity sha512-0vEa83M7kJtxK/jUhlpZ27WHIOndz5mghWL2O53kiDoA9DIxSKnfqB92LoqEn77cT4f3H2cZm1BMEat/6AZz3A==
-  dependencies:
-    chalk "2.4.2"
-    cross-spawn "6.0.5"
-    enhanced-resolve "4.1.0"
-    findup-sync "3.0.0"
-    global-modules "2.0.0"
-    import-local "2.0.0"
-    interpret "1.2.0"
-    loader-utils "1.2.3"
-    supports-color "6.1.0"
-    v8-compile-cache "2.0.3"
-    yargs "13.2.4"
-
-webpack-dev-middleware@^3.7.0:
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz#ef751d25f4e9a5c8a35da600c5fda3582b5c6cff"
-  integrity sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==
-  dependencies:
-    memory-fs "^0.4.1"
-    mime "^2.4.2"
-    range-parser "^1.2.1"
-    webpack-log "^2.0.0"
-
-webpack-dev-server@^3.7.2:
-  version "3.7.2"
-  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.7.2.tgz#f79caa5974b7f8b63268ef5421222a8486d792f5"
-  integrity sha512-mjWtrKJW2T9SsjJ4/dxDC2fkFVUw8jlpemDERqV0ZJIkjjjamR2AbQlr3oz+j4JLhYCHImHnXZK5H06P2wvUew==
-  dependencies:
-    ansi-html "0.0.7"
-    bonjour "^3.5.0"
-    chokidar "^2.1.6"
-    compression "^1.7.4"
-    connect-history-api-fallback "^1.6.0"
-    debug "^4.1.1"
-    del "^4.1.1"
-    express "^4.17.1"
-    html-entities "^1.2.1"
-    http-proxy-middleware "^0.19.1"
-    import-local "^2.0.0"
-    internal-ip "^4.3.0"
-    ip "^1.1.5"
-    killable "^1.0.1"
-    loglevel "^1.6.3"
-    opn "^5.5.0"
-    p-retry "^3.0.1"
-    portfinder "^1.0.20"
-    schema-utils "^1.0.0"
-    selfsigned "^1.10.4"
-    semver "^6.1.1"
-    serve-index "^1.9.1"
-    sockjs "0.3.19"
-    sockjs-client "1.3.0"
-    spdy "^4.0.0"
-    strip-ansi "^3.0.1"
-    supports-color "^6.1.0"
-    url "^0.11.0"
-    webpack-dev-middleware "^3.7.0"
-    webpack-log "^2.0.0"
-    yargs "12.0.5"
-
-webpack-log@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f"
-  integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==
-  dependencies:
-    ansi-colors "^3.0.0"
-    uuid "^3.3.2"
-
-webpack-md5-hash@0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/webpack-md5-hash/-/webpack-md5-hash-0.0.6.tgz#317b766a0a724eb5501610b3c8f4cddc4edf70f5"
-  integrity sha512-HrQ0AJpeXHRa3IjsgyyEfTx8EqYs5y/4x/WklSYsNDcqBixHzCkrmJV5U+4ks+sx7ycKoIdqWLdyuk913FCS+Q==
-  dependencies:
-    md5 "^2.0.0"
-
-webpack-merge@^4.2.1:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.1.tgz#5e923cf802ea2ace4fd5af1d3247368a633489b4"
-  integrity sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==
-  dependencies:
-    lodash "^4.17.5"
-
-webpack-sources@^1.4.0, webpack-sources@^1.4.1:
-  version "1.4.3"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
-  integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
-  dependencies:
-    source-list-map "^2.0.0"
-    source-map "~0.6.1"
-
-webpack@^4.35.3:
-  version "4.39.1"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.39.1.tgz#60ed9fb2b72cd60f26ea526c404d2a4cc97a1bd8"
-  integrity sha512-/LAb2TJ2z+eVwisldp3dqTEoNhzp/TLCZlmZm3GGGAlnfIWDgOEE758j/9atklNLfRyhKbZTCOIoPqLJXeBLbQ==
-  dependencies:
-    "@webassemblyjs/ast" "1.8.5"
-    "@webassemblyjs/helper-module-context" "1.8.5"
-    "@webassemblyjs/wasm-edit" "1.8.5"
-    "@webassemblyjs/wasm-parser" "1.8.5"
-    acorn "^6.2.1"
-    ajv "^6.10.2"
-    ajv-keywords "^3.4.1"
-    chrome-trace-event "^1.0.2"
-    enhanced-resolve "^4.1.0"
-    eslint-scope "^4.0.3"
-    json-parse-better-errors "^1.0.2"
-    loader-runner "^2.4.0"
-    loader-utils "^1.2.3"
-    memory-fs "^0.4.1"
-    micromatch "^3.1.10"
-    mkdirp "^0.5.1"
-    neo-async "^2.6.1"
-    node-libs-browser "^2.2.1"
-    schema-utils "^1.0.0"
-    tapable "^1.1.3"
-    terser-webpack-plugin "^1.4.1"
-    watchpack "^1.6.0"
-    webpack-sources "^1.4.1"
-
-websocket-driver@>=0.5.1:
-  version "0.7.3"
-  resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9"
-  integrity sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==
-  dependencies:
-    http-parser-js ">=0.4.0 <0.4.11"
-    safe-buffer ">=5.1.0"
-    websocket-extensions ">=0.1.1"
-
-websocket-extensions@>=0.1.1:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29"
-  integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==
-
-whatwg-url@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.0.0.tgz#fde926fa54a599f3adf82dff25a9f7be02dc6edd"
-  integrity sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==
-  dependencies:
-    lodash.sortby "^4.7.0"
-    tr46 "^1.0.1"
-    webidl-conversions "^4.0.2"
-
-which-module@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
-  integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=
-
-which-module@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
-  integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
-
-which@1, which@^1.2.14, which@^1.2.9, which@^1.3.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
-  integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
-  dependencies:
-    isexe "^2.0.0"
-
-wide-align@^1.1.0:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
-  integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
-  dependencies:
-    string-width "^1.0.2 || 2"
-
-widest-line@^2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc"
-  integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==
-  dependencies:
-    string-width "^2.1.1"
-
-window-size@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876"
-  integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=
-
-windows-release@^3.1.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f"
-  integrity sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==
-  dependencies:
-    execa "^1.0.0"
-
-with-callback@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/with-callback/-/with-callback-1.0.2.tgz#a09629b9a920028d721404fb435bdcff5c91bc21"
-  integrity sha1-oJYpuakgAo1yFAT7Q1vc/1yRvCE=
-
-wordwrap@~0.0.2:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
-  integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
-
-wordwrap@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
-  integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
-
-worker-farm@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
-  integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==
-  dependencies:
-    errno "~0.1.7"
-
-wrap-ansi@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
-  integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=
-  dependencies:
-    string-width "^1.0.1"
-    strip-ansi "^3.0.1"
-
-wrap-ansi@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
-  integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
-  dependencies:
-    ansi-styles "^3.2.0"
-    string-width "^3.0.0"
-    strip-ansi "^5.0.0"
-
-wrappy@1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
-  integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-
-write-file-atomic@^2.0.0, write-file-atomic@^2.3.0, write-file-atomic@^2.4.2:
-  version "2.4.3"
-  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481"
-  integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==
-  dependencies:
-    graceful-fs "^4.1.11"
-    imurmurhash "^0.1.4"
-    signal-exit "^3.0.2"
-
-write-json-file@^2.2.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.3.0.tgz#2b64c8a33004d54b8698c76d585a77ceb61da32f"
-  integrity sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=
-  dependencies:
-    detect-indent "^5.0.0"
-    graceful-fs "^4.1.2"
-    make-dir "^1.0.0"
-    pify "^3.0.0"
-    sort-keys "^2.0.0"
-    write-file-atomic "^2.0.0"
-
-write-json-file@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-3.2.0.tgz#65bbdc9ecd8a1458e15952770ccbadfcff5fe62a"
-  integrity sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==
-  dependencies:
-    detect-indent "^5.0.0"
-    graceful-fs "^4.1.15"
-    make-dir "^2.1.0"
-    pify "^4.0.1"
-    sort-keys "^2.0.0"
-    write-file-atomic "^2.4.2"
-
-write-pkg@^3.1.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-3.2.0.tgz#0e178fe97820d389a8928bc79535dbe68c2cff21"
-  integrity sha512-tX2ifZ0YqEFOF1wjRW2Pk93NLsj02+n1UP5RvO6rCs0K6R2g1padvf006cY74PQJKMGS2r42NK7FD0dG6Y6paw==
-  dependencies:
-    sort-keys "^2.0.0"
-    write-json-file "^2.2.0"
-
-write@1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
-  integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==
-  dependencies:
-    mkdirp "^0.5.1"
-
-ws@^6.0.0:
-  version "6.2.1"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
-  integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
-  dependencies:
-    async-limiter "~1.0.0"
-
-ws@~6.1.0:
-  version "6.1.4"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
-  integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==
-  dependencies:
-    async-limiter "~1.0.0"
-
-xdg-basedir@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
-  integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
-
-xml2js@^0.4.17:
-  version "0.4.19"
-  resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
-  integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
-  dependencies:
-    sax ">=0.6.0"
-    xmlbuilder "~9.0.1"
-
-xmlbuilder@~9.0.1:
-  version "9.0.7"
-  resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
-  integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
-
-xmlhttprequest-ssl@~1.5.4:
-  version "1.5.5"
-  resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
-  integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=
-
-xregexp@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
-  integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=
-
-xtend@^4.0.0, xtend@~4.0.1:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
-  integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
-
-y18n@^3.2.0, y18n@^3.2.1:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
-  integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
-
-"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
-  integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
-
-yallist@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
-  integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
-
-yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
-  integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
-
-yargs-parser@^11.1.1:
-  version "11.1.1"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
-  integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==
-  dependencies:
-    camelcase "^5.0.0"
-    decamelize "^1.2.0"
-
-yargs-parser@^13.1.0:
-  version "13.1.1"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
-  integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==
-  dependencies:
-    camelcase "^5.0.0"
-    decamelize "^1.2.0"
-
-yargs-parser@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
-  integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=
-  dependencies:
-    camelcase "^3.0.0"
-
-yargs@12.0.5, yargs@^12.0.1:
-  version "12.0.5"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
-  integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
-  dependencies:
-    cliui "^4.0.0"
-    decamelize "^1.2.0"
-    find-up "^3.0.0"
-    get-caller-file "^1.0.1"
-    os-locale "^3.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 || ^4.0.0"
-    yargs-parser "^11.1.1"
-
-yargs@13.2.4:
-  version "13.2.4"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83"
-  integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==
-  dependencies:
-    cliui "^5.0.0"
-    find-up "^3.0.0"
-    get-caller-file "^2.0.1"
-    os-locale "^3.1.0"
-    require-directory "^2.1.1"
-    require-main-filename "^2.0.0"
-    set-blocking "^2.0.0"
-    string-width "^3.0.0"
-    which-module "^2.0.0"
-    y18n "^4.0.0"
-    yargs-parser "^13.1.0"
-
-yargs@^3.19.0:
-  version "3.32.0"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995"
-  integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=
-  dependencies:
-    camelcase "^2.0.1"
-    cliui "^3.0.3"
-    decamelize "^1.1.1"
-    os-locale "^1.4.0"
-    string-width "^1.0.1"
-    window-size "^0.1.4"
-    y18n "^3.2.0"
-
-yargs@^7.0.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
-  integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=
-  dependencies:
-    camelcase "^3.0.0"
-    cliui "^3.2.0"
-    decamelize "^1.1.1"
-    get-caller-file "^1.0.1"
-    os-locale "^1.4.0"
-    read-pkg-up "^1.0.1"
-    require-directory "^2.1.1"
-    require-main-filename "^1.0.1"
-    set-blocking "^2.0.0"
-    string-width "^1.0.2"
-    which-module "^1.0.0"
-    y18n "^3.2.1"
-    yargs-parser "^5.0.0"
-
-yeast@0.1.2:
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
-  integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=