diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000000000000000000000000000000000000..256c63e4c59502f937824c1379ec7c32dc9ab63e
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,57 @@
+{
+    "parser": "@typescript-eslint/parser",
+    "plugins": ["@typescript-eslint"],
+    "parserOptions": {
+        "ecmaVersion": 9,
+        "ecmaFeatures": {
+            "jsx": false
+        },
+        "project": "tsconfig.json"
+    },
+    "env": {
+        "node": true,
+        "jasmine": true
+    },
+    "extends": ["plugin:@typescript-eslint/recommended"],
+    "rules": {
+        "ordered-imports": "off",
+        "no-trailing-spaces": "error",
+        "max-classes-per-file": ["warn", 1],
+        "object-literal-sort-keys": "off",
+        "@typescript-eslint/no-explicit-any": "error",
+        "@typescript-eslint/prefer-for-of": "error",
+        "@typescript-eslint/typedef": "warn",
+        "@typescript-eslint/no-floating-promises": "error",
+        "curly": "error",
+        "no-empty": "off",
+        "no-invalid-this": "error",
+        "@typescript-eslint/no-throw-literal": "warn",
+        "prefer-const": "error",
+        "indent": ["error", 4],
+        "max-lines": ["warn", 500],
+        "no-duplicate-imports": "error",
+        "@typescript-eslint/array-type": "error",
+        "@typescript-eslint/promise-function-async": "error",
+        "no-bitwise": "error",
+        "no-console": "error",
+        "no-debugger": "error",
+        "prefer-template": "error",
+        // Disable these as they were introduced by @typescript-eslint/recommended
+        "@typescript-eslint/no-use-before-define": "off",
+        "@typescript-eslint/no-inferrable-types": "off",
+        "@typescript-eslint/member-delimiter-style": "off",
+        "@typescript-eslint/no-unused-expressions": "off",
+        "@typescript-eslint/interface-name-prefix": "off"
+    },
+    "overrides": [
+        {
+            "files": [
+                "test/**/*"
+            ],
+            "rules": {
+                "@typescript-eslint/no-empty-function": "off",
+                "@typescript-eslint/no-explicit-any": "off"
+            }
+        }
+    ]
+}
diff --git a/README.md b/README.md
index a894a82bd5cabec05d02a70ab8a7c21e597c4b9a..13721918bcd467bbe6b9179a8535e25804d4ee9e 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ that implements the [AS API](https://matrix.org/docs/spec/application_service/r0
 
 The bridge supports any version of Node.js >= v12.X, including all [current releases](https://nodejs.org/en/about/releases/).
 
-### Setup the bridge
+### Set up the bridge
 
 * Run ``npm install`` to grab the dependencies. `npm` may complain about peer dependencies, but you can safely ignore these.
 * Run ``npm run build`` to build the typescript into javascript.
@@ -113,6 +113,23 @@ should show up in the network list on Element and other clients.
 
 [Howto](./docs/howto.md)
 
+## End User Documentation
+
+### Bridging a Room
+
+You must get an authorization link from bridge owner. You must be a server admin or get
+help from server admin on Discord side.
+
+* Invite the Matrix side bot to your room and wait for it to join.
+* On Discord side use the authorization link to invite bot to Discord server.
+* Find out a serverid and channelid for your server/channel you want to bridge. Search the web for instructions.
+* In Matrix room give command ``!discord bridge ServerID ChannelID``
+* The bridge will ask for confirmation from the Discord server admins to complete the bridge. Once approved, you're all set.
+
+### Unbridging a Room
+
+* In Matrix room give command ``!discord unbridge``
+
 ## Features and Roadmap
 In a vague order of what is coming up next
 
diff --git a/package.json b/package.json
index 8f958309ba4ee74946c8e73d4eff743aad693370..27172919f2707b05e8f250a832fc416c8e26c7ae 100644
--- a/package.json
+++ b/package.json
@@ -5,8 +5,8 @@
   "main": "discordas.js",
   "scripts": {
     "test": "mocha -r ts-node/register test/config.ts test/test_*.ts test/**/test_*.ts",
-    "lint": "tslint --project ./tsconfig.json -t stylish",
-    "coverage": "nyc mocha -r ts-node/register test/config.ts test/test_*.ts test/**/test_*.ts",
+    "lint": "eslint -c .eslintrc --max-warnings 200 src/**/*.ts test/**/*.ts",
+    "coverage": "tsc && nyc mocha build/test/config.js build/test",
     "build": "tsc",
     "postinstall": "npm run build",
     "start": "npm run-script build && node ./build/src/discordas.js -c config.yaml",
@@ -57,6 +57,8 @@
   },
   "devDependencies": {
     "@istanbuljs/nyc-config-typescript": "^1.0.1",
+    "@typescript-eslint/eslint-plugin": "^2.14.0",
+    "@typescript-eslint/parser": "^2.14.0",
     "@types/better-sqlite3": "^5.4.1",
     "@types/chai": "^4.2.11",
     "@types/command-line-args": "^5.0.0",
@@ -72,8 +74,8 @@
     "proxyquire": "^1.7.11",
     "source-map-support": "^0.5.19",
     "ts-node": "^8.10.2",
-    "tslint": "^5.20.1",
     "typescript": "^3.9.5",
-    "why-is-node-running": "^2.2.0"
+    "why-is-node-running": "^2.2.0",
+    "eslint": "^7.4.0"
   }
 }
diff --git a/src/clientfactory.ts b/src/clientfactory.ts
index 5a150e2678a584f3e05db05d277d13fc11591b79..b8f949fc07d786322ab94e6aa71d6dfe91e4babe 100644
--- a/src/clientfactory.ts
+++ b/src/clientfactory.ts
@@ -123,17 +123,17 @@ export class DiscordClientFactory {
     }
 
     public bindMetricsToChannel(channel: TextChannel) {
-        // tslint:disable-next-line:no-any
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         const flexChan = channel as any;
         if (flexChan._xmet_send !== undefined) {
             return;
         }
         // Prefix the real functions with _xmet_
+        // eslint-disable-next-line @typescript-eslint/camelcase
         flexChan._xmet_send = channel.send;
-        // tslint:disable-next-line:only-arrow-functions
-        channel.send = function() {
+        channel.send = (...rest) => {
             MetricPeg.get.remoteCall("channel.send");
-            return flexChan._xmet_send.apply(channel, arguments);
+            return flexChan._xmet_send.apply(channel, rest);
         };
     }
 }
diff --git a/src/config.ts b/src/config.ts
index 0d49c57983988b29aa97cb33a72be12132777d6d..5b21b766d886d660a115eab7769343acbabf98f9 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -35,7 +35,7 @@ export class DiscordBridgeConfig {
      * @param newConfig Config keys
      * @param configLayer Private parameter
      */
-    // tslint:disable-next-line no-any
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
     public applyConfig(newConfig: {[key: string]: any}, configLayer: {[key: string]: any} = this) {
           Object.keys(newConfig).forEach((key) => {
             if (configLayer[key] instanceof Object && !(configLayer[key] instanceof Array)) {
@@ -53,10 +53,10 @@ export class DiscordBridgeConfig {
      * @param configLayer private parameter: current layer of configuration to alter recursively
      */
     public applyEnvironmentOverrides(
-        // tslint:disable-next-line no-any
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         environment: {[key: string]: any},
         path: string[] = [ENV_PREFIX],
-        // tslint:disable-next-line no-any
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         configLayer: {[key: string]: any} = this,
     ) {
         Object.keys(configLayer).forEach((key) => {
diff --git a/src/db/dbdataemoji.ts b/src/db/dbdataemoji.ts
index 9b076b6c407c9fb41291a6a42d80111203623c7c..6e74bd56aaf37da404b0c231c366712ccea418be 100644
--- a/src/db/dbdataemoji.ts
+++ b/src/db/dbdataemoji.ts
@@ -39,9 +39,9 @@ export class DbEmoji implements IDbData {
                 WHERE mxc_url = $mxc`;
         }
         const row = await store.db.Get(query, {
-                id: params.emoji_id,
-                mxc: params.mxc_url,
-            });
+            id: params.emoji_id,
+            mxc: params.mxc_url,
+        });
         this.Result = Boolean(row); // check if row exists
         if (this.Result && row) {
             this.EmojiId = row.emoji_id as string;
@@ -60,12 +60,14 @@ export class DbEmoji implements IDbData {
             INSERT INTO emoji
             (emoji_id,name,animated,mxc_url,created_at,updated_at)
             VALUES ($emoji_id,$name,$animated,$mxc_url,$created_at,$updated_at);`, {
-                animated: Number(this.Animated),
-                created_at: this.CreatedAt,
-                emoji_id: this.EmojiId,
-                mxc_url: this.MxcUrl,
-                name: this.Name,
-                updated_at: this.UpdatedAt,
+            /* eslint-disable @typescript-eslint/camelcase */
+            animated: Number(this.Animated),
+            created_at: this.CreatedAt,
+            emoji_id: this.EmojiId,
+            mxc_url: this.MxcUrl,
+            name: this.Name,
+            updated_at: this.UpdatedAt,
+            /* eslint-enable @typescript-eslint/camelcase */
         });
     }
 
@@ -80,11 +82,13 @@ export class DbEmoji implements IDbData {
             updated_at = $updated_at
             WHERE
             emoji_id = $emoji_id`, {
-                animated: Number(this.Animated),
-                emoji_id: this.EmojiId,
-                mxc_url: this.MxcUrl,
-                name: this.Name,
-                updated_at: this.UpdatedAt,
+            /* eslint-disable @typescript-eslint/camelcase */
+            animated: Number(this.Animated),
+            emoji_id: this.EmojiId,
+            mxc_url: this.MxcUrl,
+            name: this.Name,
+            updated_at: this.UpdatedAt,
+            /* eslint-enable @typescript-eslint/camelcase */
         });
     }
 
diff --git a/src/db/dbdataevent.ts b/src/db/dbdataevent.ts
index 46c8a9387548a5fde822980ba157307c0985d035..cfe5c3f0fd1e779a727619bcfeef28b98f550ddb 100644
--- a/src/db/dbdataevent.ts
+++ b/src/db/dbdataevent.ts
@@ -24,7 +24,7 @@ export class DbEvent implements IDbDataMany {
     public GuildId: string;
     public ChannelId: string;
     public Result: boolean;
-    // tslint:disable-next-line no-any
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
     private rows: any[];
 
     get ResultCount(): number {
@@ -33,21 +33,21 @@ export class DbEvent implements IDbDataMany {
 
     public async RunQuery(store: DiscordStore, params: ISqlCommandParameters): Promise<void> {
         this.rows = [];
-        // tslint:disable-next-line no-any
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         let rowsM: any[] | null = null;
         if (params.matrix_id) {
             rowsM = await store.db.All(`
                 SELECT *
                 FROM event_store
                 WHERE matrix_id = $id`, {
-                    id: params.matrix_id,
+                id: params.matrix_id,
             });
         } else if (params.discord_id) {
             rowsM = await store.db.All(`
                 SELECT *
                 FROM event_store
                 WHERE discord_id = $id`, {
-                    id: params.discord_id,
+                id: params.discord_id,
             });
         } else {
             throw new Error("Unknown/incorrect id given as a param");
@@ -55,20 +55,24 @@ export class DbEvent implements IDbDataMany {
 
         for (const rowM of rowsM) {
             const row = {
+                /* eslint-disable @typescript-eslint/camelcase */
                 discord_id: rowM.discord_id,
                 matrix_id: rowM.matrix_id,
+                /* eslint-enable @typescript-eslint/camelcase */
             };
             for (const rowD of await store.db.All(`
                     SELECT *
                     FROM discord_msg_store
                     WHERE msg_id = $id`, {
-                        id: rowM.discord_id,
+                id: rowM.discord_id,
             })) {
-                // tslint:disable-next-line no-any
-                const insertRow: any = Object.assign({}, row);
-                insertRow.guild_id = rowD.guild_id;
-                insertRow.channel_id = rowD.channel_id;
-                this.rows.push(insertRow);
+                this.rows.push({
+                    /* eslint-disable @typescript-eslint/camelcase */
+                    ...row,
+                    guild_id: rowD.guild_id,
+                    channel_id: rowD.channel_id,
+                    /* eslint-enable @typescript-eslint/camelcase */
+                });
             }
         }
         this.Result = this.rows.length !== 0;
@@ -91,15 +95,17 @@ export class DbEvent implements IDbDataMany {
             INSERT INTO event_store
             (matrix_id,discord_id)
             VALUES ($matrix_id,$discord_id);`, {
-                discord_id: this.DiscordId,
-                matrix_id: this.MatrixId,
+            /* eslint-disable @typescript-eslint/camelcase */
+            discord_id: this.DiscordId,
+            matrix_id: this.MatrixId,
+            /* eslint-enable @typescript-eslint/camelcase */
         });
         // Check if the discord item exists?
         const msgExists = await store.db.Get(`
                 SELECT *
                 FROM discord_msg_store
                 WHERE msg_id = $id`, {
-                    id: this.DiscordId,
+            id: this.DiscordId,
         }) != null;
         if (msgExists) {
             return;
@@ -108,9 +114,11 @@ export class DbEvent implements IDbDataMany {
             INSERT INTO discord_msg_store
             (msg_id, guild_id, channel_id)
             VALUES ($msg_id, $guild_id, $channel_id);`, {
-                channel_id: this.ChannelId,
-                guild_id: this.GuildId,
-                msg_id: this.DiscordId,
+            /* eslint-disable @typescript-eslint/camelcase */
+            channel_id: this.ChannelId,
+            guild_id: this.GuildId,
+            msg_id: this.DiscordId,
+            /* eslint-enable @typescript-eslint/camelcase */
         });
     }
 
@@ -123,13 +131,17 @@ export class DbEvent implements IDbDataMany {
             DELETE FROM event_store
             WHERE matrix_id = $matrix_id
             AND discord_id = $discord_id;`, {
-                discord_id: this.DiscordId,
-                matrix_id: this.MatrixId,
+            /* eslint-disable @typescript-eslint/camelcase */
+            discord_id: this.DiscordId,
+            matrix_id: this.MatrixId,
+            /* eslint-enable @typescript-eslint/camelcase */
         });
         return store.db.Run(`
             DELETE FROM discord_msg_store
             WHERE msg_id = $discord_id;`, {
-                discord_id: this.DiscordId,
+            /* eslint-disable @typescript-eslint/camelcase */
+            discord_id: this.DiscordId,
+            /* eslint-enable @typescript-eslint/camelcase */
         });
     }
 }
diff --git a/src/db/dbdatainterface.ts b/src/db/dbdatainterface.ts
index 043bec08ba887e8b927a3b35eea6a7be7cc110a7..2a66a6b3894faf0ad2a6abbae4b18118fddced01 100644
--- a/src/db/dbdatainterface.ts
+++ b/src/db/dbdatainterface.ts
@@ -18,7 +18,7 @@ import { DiscordStore } from "../store";
 
 export interface IDbData {
     Result: boolean;
-    // tslint:disable-next-line no-any
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
     RunQuery(store: DiscordStore, params: any): Promise<void|Error>;
     Insert(store: DiscordStore): Promise<void|Error>;
     Update(store: DiscordStore): Promise<void|Error>;
diff --git a/src/db/postgres.ts b/src/db/postgres.ts
index a3fc00cee282dfbf2d843df8e13da584456064e7..e4503bfb35f0276102344bda1e3c424680b7a1d2 100644
--- a/src/db/postgres.ts
+++ b/src/db/postgres.ts
@@ -25,17 +25,18 @@ const pgp: pgPromise.IMain = pgPromise({
 
 export class Postgres implements IDatabaseConnector {
     public static ParameterizeSql(sql: string): string {
-        return sql.replace(/\$((\w|\d|_)+)+/g, (k) => {
+        return sql.replace(/\$((\w|\d|_)+)+/g, (k: string) => {
             return `\${${k.substring("$".length)}}`;
         });
     }
 
-    // tslint:disable-next-line no-any
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
     private db: pgPromise.IDatabase<any>;
     constructor(private connectionString: string) {
 
     }
-    public Open() {
+
+    public Open(): void {
         // Hide username:password
         const logConnString = this.connectionString.substring(
             this.connectionString.indexOf("@") || 0,
@@ -63,7 +64,7 @@ export class Postgres implements IDatabaseConnector {
 
     public async Run(sql: string, parameters?: ISqlCommandParameters): Promise<void> {
         log.silly("Run:", sql);
-        return this.db.oneOrNone(Postgres.ParameterizeSql(sql), parameters).then(() => {});
+        await this.db.oneOrNone(Postgres.ParameterizeSql(sql), parameters);
     }
 
     public async Close(): Promise<void> {
@@ -73,6 +74,5 @@ export class Postgres implements IDatabaseConnector {
     public async Exec(sql: string): Promise<void> {
         log.silly("Exec:", sql);
         await this.db.none(sql);
-        return;
     }
 }
diff --git a/src/db/roomstore.ts b/src/db/roomstore.ts
index a3068c8ee4d11134d2780880dfb7960054e7c7cc..e8e66f4df880b1b09241a7c2f5213c20a8e0c18a 100644
--- a/src/db/roomstore.ts
+++ b/src/db/roomstore.ts
@@ -52,7 +52,7 @@ export class RemoteStoreRoom {
     public data: IRemoteRoomDataLazy;
     constructor(public readonly roomId: string, data: IRemoteRoomDataLazy) {
         for (const k of ["discord_guild", "discord_channel", "discord_name",
-        "discord_topic", "discord_iconurl", "discord_iconurl_mxc", "discord_type"]) {
+            "discord_topic", "discord_iconurl", "discord_iconurl_mxc", "discord_type"]) {
             data[k] = typeof(data[k]) === "number" ? String(data[k]) : data[k] || null;
         }
         for (const k of ["update_name", "update_topic", "update_icon", "plumbed"]) {
@@ -128,7 +128,7 @@ export class DbRoomStore {
             };
             try {
                 await this.db.Run(`INSERT INTO room_entries VALUES ($id, $matrix, $remote)`, values);
-                log.verbose("Created new entry " + entry.id);
+                log.verbose(`Created new entry ${entry.id}`);
             } catch (ex) {
                 log.error("Failed to insert room entry", ex);
                 throw Error("Failed to insert room entry");
@@ -189,7 +189,7 @@ export class DbRoomStore {
                     {remoteId},
                 );
                 if (row) {
-                    // tslint:disable-next-line no-any
+                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                     remote = new RemoteStoreRoom(remoteId, row as any);
                 }
             }
@@ -225,7 +225,7 @@ export class DbRoomStore {
                     {rid: remoteId},
                 );
                 if (row) {
-                    // tslint:disable-next-line no-any
+                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                     remote = new RemoteStoreRoom(remoteId, row as any);
                 }
             }
@@ -253,7 +253,7 @@ export class DbRoomStore {
 
         try {
             await this.db.Run(`INSERT INTO room_entries VALUES ($id, $matrix, $remote)`, values);
-            log.verbose("Created new entry " + values.id);
+            log.verbose(`Created new entry ${values.id}`);
         } catch (ex) {
             log.error("Failed to insert room entry", ex);
             throw Error("Failed to insert room entry");
@@ -277,7 +277,7 @@ export class DbRoomStore {
         SELECT * FROM remote_room_data
         INNER JOIN room_entries ON remote_room_data.room_id = room_entries.remote_id
         WHERE ${whereClaues}`;
-        // tslint:disable-next-line no-any
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         return (await this.db.All(sql, data as any)).map((row) => {
             const id = row.id as string;
             const matrixId = row.matrix_id;
@@ -285,7 +285,7 @@ export class DbRoomStore {
             return {
                 id,
                 matrix: matrixId ? new MatrixStoreRoom(matrixId as string) : null,
-                // tslint:disable-next-line no-any
+                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                 remote: matrixId ? new RemoteStoreRoom(remoteId as string, row as any) : null,
             };
         });
@@ -321,6 +321,7 @@ export class DbRoomStore {
         );
 
         const data = {
+            /* eslint-disable @typescript-eslint/camelcase */
             discord_channel:     room.data.discord_channel,
             discord_guild:       room.data.discord_guild,
             discord_iconurl:     room.data.discord_iconurl,
@@ -332,12 +333,13 @@ export class DbRoomStore {
             update_icon:         Number(room.data.update_icon || 0),
             update_name:         Number(room.data.update_name || 0),
             update_topic:        Number(room.data.update_topic || 0),
+            /* eslint-enable @typescript-eslint/camelcase */
         } as IRemoteRoomData;
 
         if (!existingRow) {
             // Insert new data.
             await this.db.Run(
-            `INSERT INTO remote_room_data VALUES (
+                `INSERT INTO remote_room_data VALUES (
                 $id,
                 $discord_guild,
                 $discord_channel,
@@ -352,11 +354,11 @@ export class DbRoomStore {
                 $plumbed
             )
             `,
-            {
-                id: room.roomId,
-                // tslint:disable-next-line no-any
-                ...data as any,
-            });
+                {
+                    id: room.roomId,
+                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+                    ...data as any,
+                });
             return;
         }
 
@@ -365,8 +367,8 @@ export class DbRoomStore {
         // New keys
         Object.keys(room.data).filter(
             (k: string) => existingRow[k] === null).forEach((key) => {
-                const val = room.data[key];
-                keysToUpdate[key] = typeof val === "boolean" ? Number(val) : val;
+            const val = room.data[key];
+            keysToUpdate[key] = typeof val === "boolean" ? Number(val) : val;
         });
 
         // Updated keys
@@ -386,12 +388,12 @@ export class DbRoomStore {
 
         try {
             await this.db.Run(`UPDATE remote_room_data SET ${setStatement} WHERE room_id = $id`,
-            {
-                id: room.roomId,
-                // tslint:disable-next-line no-any
-                ...keysToUpdate as any,
-            });
-            log.verbose("Upserted room " + room.roomId);
+                {
+                    id: room.roomId,
+                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+                    ...keysToUpdate as any,
+                });
+            log.verbose(`Upserted room ${  room.roomId}`);
         } catch (ex) {
             log.error("Failed to upsert room", ex);
             throw Error("Failed to upsert room");
diff --git a/src/db/userstore.ts b/src/db/userstore.ts
index 7e84dc0a84faf4767a188349e11236ef34586d9c..f922b68a183326c327159452467a42b1b585df02 100644
--- a/src/db/userstore.ts
+++ b/src/db/userstore.ts
@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
+/* eslint-disable max-classes-per-file */
 
 import { IDatabaseConnector } from "./connector";
 import { Log } from "../log";
@@ -81,9 +82,11 @@ export class DbUserStore {
             {remoteId},
         );
         if (nicks) {
+            /* eslint-disable @typescript-eslint/camelcase */
             nicks.forEach(({nick, guild_id}) => {
                 remoteUser.guildNicks.set(guild_id as string, nick as string);
             });
+            /* eslint-enable @typescript-eslint/camelcase */
         }
         this.remoteUserCache.set(remoteId, remoteUser);
         return remoteUser;
@@ -98,35 +101,39 @@ export class DbUserStore {
         );
         if (!existingData) {
             await this.db.Run(
-            `INSERT INTO remote_user_data VALUES (
-                $remote_id,
-                $displayname,
-                $avatarurl,
-                $avatarurl_mxc
-            )`,
-            {
-                avatarurl: user.avatarurl,
-                avatarurl_mxc: user.avatarurlMxc,
-                displayname: user.displayname,
-                remote_id: user.id,
-            });
+                `INSERT INTO remote_user_data VALUES (
+                    $remote_id,
+                    $displayname,
+                    $avatarurl,
+                    $avatarurl_mxc
+                )`,
+                {
+                    /* eslint-disable @typescript-eslint/camelcase */
+                    avatarurl: user.avatarurl,
+                    avatarurl_mxc: user.avatarurlMxc,
+                    displayname: user.displayname,
+                    remote_id: user.id,
+                    /* eslint-enable @typescript-eslint/camelcase */
+                });
         } else {
             await this.db.Run(
-`UPDATE remote_user_data SET displayname = $displayname,
+                `UPDATE remote_user_data SET displayname = $displayname,
 avatarurl = $avatarurl,
 avatarurl_mxc = $avatarurl_mxc WHERE remote_id = $remote_id`,
-            {
-                avatarurl: user.avatarurl,
-                avatarurl_mxc: user.avatarurlMxc,
-                displayname: user.displayname,
-                remote_id: user.id,
-            });
+                {
+                    /* eslint-disable @typescript-eslint/camelcase */
+                    avatarurl: user.avatarurl,
+                    avatarurl_mxc: user.avatarurlMxc,
+                    displayname: user.displayname,
+                    remote_id: user.id,
+                    /* eslint-enable @typescript-eslint/camelcase */
+                });
         }
         const existingNicks = {};
         (await this.db.All(
             "SELECT guild_id, nick FROM remote_user_guild_nicks WHERE remote_id = $remoteId",
             {remoteId: user.id},
-        )).forEach(({guild_id, nick}) => existingNicks[guild_id as string] = nick);
+        )).forEach(({guild_id, nick}) => existingNicks[guild_id as string] = nick); // eslint-disable-line @typescript-eslint/camelcase
         for (const guildId of user.guildNicks.keys()) {
             const nick = user.guildNicks.get(guildId) || null;
             if (existingData) {
@@ -134,28 +141,32 @@ avatarurl_mxc = $avatarurl_mxc WHERE remote_id = $remote_id`,
                     return;
                 } else if (existingNicks[guildId]) {
                     await this.db.Run(
-`UPDATE remote_user_guild_nicks SET nick = $nick
+                        `UPDATE remote_user_guild_nicks SET nick = $nick
 WHERE remote_id = $remote_id
 AND guild_id = $guild_id`,
-                    {
-                        guild_id: guildId,
-                        nick,
-                        remote_id: user.id,
-                    });
+                        {
+                            /* eslint-disable @typescript-eslint/camelcase */
+                            guild_id: guildId,
+                            nick,
+                            remote_id: user.id,
+                            /* eslint-enable @typescript-eslint/camelcase */
+                        });
                     return;
                 }
             }
             await this.db.Run(
-            `INSERT INTO remote_user_guild_nicks VALUES (
+                `INSERT INTO remote_user_guild_nicks VALUES (
                 $remote_id,
                 $guild_id,
                 $nick
             )`,
-            {
-                guild_id: guildId,
-                nick,
-                remote_id: user.id,
-            });
+                {
+                    /* eslint-disable @typescript-eslint/camelcase */
+                    guild_id: guildId,
+                    nick,
+                    remote_id: user.id,
+                    /* eslint-enable @typescript-eslint/camelcase */
+                });
         }
 
     }
diff --git a/src/discordas.ts b/src/discordas.ts
index 44200705294b7517467e332ff9f812684ad0c41f..c8267cb4844dcbd9fc4f2d6e81e3ce75a2fbb035 100644
--- a/src/discordas.ts
+++ b/src/discordas.ts
@@ -39,11 +39,12 @@ const commandOptions = [
     { name: "help", alias: "h", type: Boolean },
 ];
 
-function generateRegistration(opts, registrationPath)  {
+function generateRegistration(opts, registrationPath: string): void {
     if (!opts.url) {
         throw Error("'url' not given in command line opts, cannot generate registration file");
     }
     const reg = {
+        /* eslint-disable @typescript-eslint/camelcase */
         as_token: uuid(),
         hs_token: uuid(),
         id: "discord-bridge",
@@ -66,14 +67,15 @@ function generateRegistration(opts, registrationPath)  {
         rate_limited: false,
         sender_localpart: "_discord_bot",
         url: opts.url,
+        /* eslint-enable @typescript-eslint/camelcase */
     } as IAppserviceRegistration;
     fs.writeFileSync(registrationPath, yaml.safeDump(reg));
 }
 
-function setupLogging() {
+function setupLogging(): void {
     const logMap = new Map<string, Log>();
-    // tslint:disable-next-line:no-any
-    const logFunc = (level: string, module: string, args: any[]) => {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    const logFunc = (level: string, module: string, args: any[]): void => {
         if (!Array.isArray(args)) {
             args = [args];
         }
@@ -81,7 +83,7 @@ function setupLogging() {
             // Spammy logs begon
             return;
         }
-        const mod = "bot-sdk" + module;
+        const mod = `bot-sdk${module}`;
         let logger = logMap.get(mod);
         if (!logger) {
             logger = new Log(mod);
@@ -91,21 +93,21 @@ function setupLogging() {
     };
 
     LogService.setLogger({
-        // tslint:disable-next-line:no-any
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         debug: (mod: string, args: any[]) => logFunc("silly", mod, args),
-        // tslint:disable-next-line:no-any
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         error: (mod: string, args: any[]) => logFunc("error", mod, args),
-        // tslint:disable-next-line:no-any
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         info: (mod: string, args: any[]) => logFunc("info", mod, args),
-        // tslint:disable-next-line:no-any
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         warn: (mod: string, args: any[]) => logFunc("warn", mod, args),
     });
 }
 
-async function run() {
+async function run(): Promise<void> {
     const opts = cliArgs(commandOptions);
     if (opts.help) {
-        /* tslint:disable:no-console */
+        // eslint-disable-next-line no-console
         console.log(usage([
             {
                 content: "The matrix appservice for discord",
@@ -187,7 +189,7 @@ async function run() {
     });
 
     if (config.bridge.disablePortalBridging !== true) {
-        // tslint:disable-next-line:no-any
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         appservice.on("query.room", async (roomAlias: string, createRoom: (opts: any) => Promise<void>) => {
             try {
                 const createRoomOpts = await roomhandler.OnAliasQuery(roomAlias);
diff --git a/src/log.ts b/src/log.ts
index 9eb99981753227b80a915a4d90aedb6c3abc63fc..085b7b223634139d70d020bfb9dee8ce71a4d67a 100644
--- a/src/log.ts
+++ b/src/log.ts
@@ -24,21 +24,21 @@ const FORMAT_FUNC = format.printf((info) => {
 });
 
 export class Log {
-    public static get level() {
+    public static get level(): string {
         return this.logger.level;
     }
 
-    public static set level(level) {
+    public static set level(level: string) {
         this.logger.level = level;
     }
 
-    public static Configure(config: DiscordBridgeConfigLogging) {
+    public static Configure(config: DiscordBridgeConfigLogging): void {
         // Merge defaults.
         Log.config = Object.assign(new DiscordBridgeConfigLogging(), config);
         Log.setupLogger();
     }
 
-    public static ForceSilent() {
+    public static ForceSilent(): void {
         new Log("Log").warn("Log set to silent");
         Log.logger.silent = true;
     }
@@ -50,7 +50,7 @@ export class Log {
         return ["silly", "verbose", "info", "http", "warn", "error", "silent"].includes(level);
     }
 
-    private static setupLogger() {
+    private static setupLogger(): void {
         if (Log.logger) {
             Log.logger.close();
         }
@@ -103,41 +103,40 @@ export class Log {
             maxSize: config.maxSize,
         };
 
-        // tslint:disable-next-line no-any
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         return new (transports as any).DailyRotateFile(opts);
     }
 
-    public warning = this.warn;
-
     constructor(private module: string) { }
 
-    // tslint:disable-next-line no-any
-    public error(...msg: any[]) {
+    public error(...msg: unknown[]): void {
         this.log("error", msg);
     }
 
-    // tslint:disable-next-line no-any
-    public warn(...msg: any[]) {
+    public warn(...msg: unknown[]): void {
         this.log("warn", msg);
     }
 
-    // tslint:disable-next-line no-any
-    public info(...msg: any[]) {
+    public warning(...msg: unknown[]): void {
+        this.warn(...msg);
+    }
+
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    public info(...msg: unknown[]): void {
         this.log("info", msg);
     }
 
-    // tslint:disable-next-line no-any
-    public verbose(...msg: any[]) {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    public verbose(...msg: unknown[]): void {
         this.log("verbose", msg);
     }
 
-    // tslint:disable-next-line no-any
-    public silly(...msg: any[]) {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    public silly(...msg: unknown[]): void {
         this.log("silly", msg);
     }
 
-    // tslint:disable-next-line no-any
-    private log(level: string, msg: any[]) {
+    private log(level: string, msg: unknown[]): void {
         if (!Log.logger) {
             // We've not configured the logger yet, so create a basic one.
             Log.config = new DiscordBridgeConfigLogging();
diff --git a/src/matrixcommandhandler.ts b/src/matrixcommandhandler.ts
index da9d97fad47dd1b7e4f066e0cb037d1e54a9e21c..536b2b7002374ad6ebbf93d9c3d72773df1f8baa 100644
--- a/src/matrixcommandhandler.ts
+++ b/src/matrixcommandhandler.ts
@@ -26,11 +26,9 @@ import { IRoomStoreEntry } from "./db/roomstore";
 import * as markdown from "marked";
 const log = new Log("MatrixCommandHandler");
 
-/* tslint:disable:no-magic-numbers */
 const PROVISIONING_DEFAULT_POWER_LEVEL = 50;
 const PROVISIONING_DEFAULT_USER_POWER_LEVEL = 0;
 const ROOM_CACHE_MAXAGE_MS = 15 * 60 * 1000;
-/* tslint:enable:no-magic-numbers */
 
 export class MatrixCommandHandler {
     private botJoinedRooms: Set<string> = new Set(); // roomids
@@ -59,7 +57,7 @@ export class MatrixCommandHandler {
         const actions: ICommandActions = {
             bridge: {
                 description: "Bridges this room to a Discord channel",
-                // tslint:disable prefer-template
+                /* eslint-disable prefer-template */
                 help: "How to bridge a Discord guild:\n" +
                     "1. Invite the bot to your Discord guild using this link: " + Util.GetBotLink(this.config) + "\n" +
                     "2. Invite me to the matrix room you'd like to bridge\n" +
@@ -69,7 +67,7 @@ export class MatrixCommandHandler {
                     "   Note: The Guild ID and Channel ID can be retrieved from the URL in your web browser.\n" +
                     "   The URL is formatted as https://discordapp.com/channels/GUILD_ID/CHANNEL_ID\n" +
                     "5. Enjoy your new bridge!",
-                // tslint:enable prefer-template
+                /* eslint-enable prefer-template */
                 params: ["guildId", "channelId"],
                 permission: {
                     cat: "events",
@@ -142,7 +140,7 @@ export class MatrixCommandHandler {
                         await this.provisioner.UnbridgeChannel(res.channel, event.room_id);
                         return "This room has been unbridged";
                     } catch (err) {
-                        log.error("Error while unbridging room " + event.room_id);
+                        log.error(`Error while unbridging room ${event.room_id}`);
                         log.error(err);
                         return "There was an error unbridging this room. " +
                             "Please try again later or contact the bridge operator.";
@@ -197,10 +195,12 @@ export class MatrixCommandHandler {
         const reply = await Util.ParseCommand("!discord", event.content!.body!, actions, parameters, permissionCheck);
         const formattedReply = markdown(reply);
         await this.bridge.botClient.sendMessage(event.room_id, {
+            /* eslint-disable @typescript-eslint/camelcase */
             body: reply,
             format: "org.matrix.custom.html",
             formatted_body: formattedReply,
             msgtype: "m.notice",
+            /* eslint-enable @typescript-eslint/camelcase */
         });
     }
 
diff --git a/src/matrixroomhandler.ts b/src/matrixroomhandler.ts
index dfadeec7292866091b2f58cc95ebd6434730d67c..6689fa7372c0f6818143d206f458ec229a42197b 100644
--- a/src/matrixroomhandler.ts
+++ b/src/matrixroomhandler.ts
@@ -25,7 +25,6 @@ import { DbRoomStore, MatrixStoreRoom, RemoteStoreRoom } from "./db/roomstore";
 import { Appservice, Intent, IApplicationServiceProtocol } from "matrix-bot-sdk";
 
 const ICON_URL = "https://matrix.org/_matrix/media/r0/download/matrix.org/mlxoESwIsTbJrfXyAAogrNxA";
-/* tslint:disable:no-magic-numbers */
 const HTTP_UNSUPPORTED = 501;
 const ROOM_NAME_PARTS = 2;
 const PROVISIONING_DEFAULT_POWER_LEVEL = 50;
@@ -42,7 +41,6 @@ const JOIN_ROOM_SCHEDULE = [
     300000,         // 5 minutes
     900000,         // 15 minutes
 ];
-/* tslint:enable:no-magic-numbers */
 
 export class MatrixRoomHandler {
     private botUserId: string;
@@ -60,13 +58,14 @@ export class MatrixRoomHandler {
                 this.tpGetProtocol(protocol)
                     .then(cb)
                     .catch((err) => log.warn("Failed to get protocol", err));
-        });
+            }
+        );
 
-        // tslint:disable-next-line:no-any
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         this.bridge.on("thirdparty.location.remote", (protocol: string, fields: any, cb: (response: any) => void) => {
             this.tpGetLocation(protocol, fields)
-            .then(cb)
-            .catch((err) => log.warn("Failed to get remote locations", err));
+                .then(cb)
+                .catch((err) => log.warn("Failed to get remote locations", err));
         });
 
         // These are not supported.
@@ -81,7 +80,7 @@ export class MatrixRoomHandler {
         });
     }
 
-    public async OnAliasQueried(alias: string, roomId: string) {
+    public async OnAliasQueried(alias: string, roomId: string): Promise<void> {
         log.verbose(`Got OnAliasQueried for ${alias} ${roomId}`);
         let channel: Discord.GuildChannel;
         try {
@@ -119,9 +118,9 @@ export class MatrixRoomHandler {
         let delay = this.config.limits.roomGhostJoinDelay;
         for (const member of (channel as Discord.TextChannel).members.array()) {
             if (member.id === this.discord.GetBotId()) {
-              continue;
+                continue;
             }
-            promiseList.push((async () => {
+            promiseList.push((async (): Promise<void> => {
                 await Util.DelayedPromise(delay);
                 log.info(`UserSyncing ${member.id}`);
                 try {
@@ -139,7 +138,7 @@ export class MatrixRoomHandler {
         await Promise.all(promiseList);
     }
 
-    // tslint:disable-next-line no-any
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
     public async OnAliasQuery(alias: string): Promise<any> {
         const aliasLocalpart = alias.substring("#".length, alias.indexOf(":"));
         log.info("Got request for #", aliasLocalpart);
@@ -161,6 +160,7 @@ export class MatrixRoomHandler {
         const instances = {};
         for (const guild of this.discord.GetGuilds()) {
             instances[guild.name] = {
+                /* eslint-disable @typescript-eslint/camelcase */
                 bot_user_id: this.botUserId,
                 desc: guild.name,
                 fields: {
@@ -168,9 +168,11 @@ export class MatrixRoomHandler {
                 },
                 icon: guild.iconURL || ICON_URL,
                 network_id: guild.id,
+                /* eslint-enable @typescript-eslint/camelcase */
             };
         }
         return {
+            /* eslint-disable @typescript-eslint/camelcase */
             field_types: {
                 // guild_name: {
                 //   regexp: "\S.{0,98}\S",
@@ -201,10 +203,11 @@ export class MatrixRoomHandler {
             instances,
             location_fields: ["guild_id", "channel_name"],
             user_fields: ["username", "discriminator"],
+            /* eslint-enable @typescript-eslint/camelcase */
         };
     }
 
-    // tslint:disable-next-line no-any
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
     public async tpGetLocation(protocol: string, fields: any): Promise<IThirdPartyLookup[]> {
         log.info("Got location request ", protocol, fields);
         const chans = this.discord.ThirdpartySearchForChannels(fields.guild_id, fields.channel_name);
@@ -213,7 +216,7 @@ export class MatrixRoomHandler {
 
     private async joinRoom(intent: Intent, roomIdOrAlias: string, member?: Discord.GuildMember): Promise<void> {
         let currentSchedule = JOIN_ROOM_SCHEDULE[0];
-        const doJoin = async () => {
+        const doJoin = async (): Promise<void> => {
             await Util.DelayedPromise(currentSchedule);
             if (member) {
                 await this.discord.UserSyncroniser.JoinRoom(member, roomIdOrAlias);
@@ -221,7 +224,7 @@ export class MatrixRoomHandler {
                 await intent.joinRoom(roomIdOrAlias);
             }
         };
-        const errorHandler = async (err) => {
+        const errorHandler = async (err): Promise<void> => {
             log.error(`Error joining room ${roomIdOrAlias} as ${intent.userId}`);
             log.error(err);
             const idx = JOIN_ROOM_SCHEDULE.indexOf(currentSchedule);
@@ -245,17 +248,23 @@ export class MatrixRoomHandler {
         }
     }
 
-    private async createMatrixRoom(channel: Discord.TextChannel,
-                                   alias: string, aliasLocalpart: string) {
+    private async createMatrixRoom(
+        channel: Discord.TextChannel,
+        alias: string,
+        aliasLocalpart: string
+    ) {
         const remote = new RemoteStoreRoom(`discord_${channel.guild.id}_${channel.id}`, {
+            /* eslint-disable @typescript-eslint/camelcase */
             discord_channel: channel.id,
             discord_guild: channel.guild.id,
             discord_type: "text",
             update_icon: 1,
             update_name: 1,
             update_topic: 1,
+            /* eslint-enable @typescript-eslint/camelcase */
         });
         const creationOpts = {
+            /* eslint-disable @typescript-eslint/camelcase */
             initial_state: [
                 {
                     content: {
@@ -267,6 +276,7 @@ export class MatrixRoomHandler {
             ],
             room_alias_name: aliasLocalpart,
             visibility: this.config.room.defaultVisibility,
+            /* eslint-enable @typescript-eslint/camelcase */
         };
         // We need to tempoarily store this until we know the room_id.
         await this.roomStore.linkRooms(
diff --git a/src/metrics.ts b/src/metrics.ts
index 52f4028c0fc8983add64d80b427e9d96ab2320f2..f11513b89d1c9678aafe4c717536e4c8013ada49 100644
--- a/src/metrics.ts
+++ b/src/metrics.ts
@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
+/* eslint-disable max-classes-per-file, @typescript-eslint/no-empty-function */
 
 import { Gauge, Counter, Histogram, default as promClient } from "prom-client";
 import { Log } from "./log";
@@ -72,11 +73,9 @@ export class PrometheusBridgeMetrics implements IBridgeMetrics {
         // TODO: Bind this for every user.
         this.httpServer = http.createServer((req, res) => {
             if (req.method !== "GET" || req.url !== "/metrics") {
-                // tslint:disable-next-line:no-magic-numbers
                 res.writeHead(404, "Not found");
                 res.end();
             }
-            // tslint:disable-next-line:no-magic-numbers
             res.writeHead(200, "OK", {"Content-Type": promClient.register.contentType});
             res.write(promClient.register.metrics());
             res.end();
diff --git a/src/store.ts b/src/store.ts
index c7574cb81dabb3db147dd77596144cf5d9d841cb..88cdc87a358c0d39fcc6110beaa5645561bda6bb 100644
--- a/src/store.ts
+++ b/src/store.ts
@@ -249,7 +249,7 @@ export class DiscordStore implements IAppserviceStorageProvider {
         }
     }
 
-    // tslint:disable-next-line no-any callable-types
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any callable-types
     public async Get<T extends IDbData>(dbType: {new(): T; }, params: any): Promise<T|null> {
         const dType = new dbType();
         log.silly(`get <${dType.constructor.name} with params ${params}>`);
diff --git a/src/structures/timedcache.ts b/src/structures/timedcache.ts
index 93424af9d488d493439bd4294dc969297f79eeee..06b2c1288ee4174bbfa7a4c07c443f8e72f3e92b 100644
--- a/src/structures/timedcache.ts
+++ b/src/structures/timedcache.ts
@@ -1,3 +1,6 @@
+import { Log } from "../log";
+const log = new Log("Timedcache");
+
 interface ITimedValue<V> {
     value: V;
     ts: number;
@@ -20,7 +23,10 @@ export class TimedCache<K, V> implements Map<K, V> {
 
     public forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void|Promise<void>): void {
         for (const item of this) {
-            callbackfn(item[1], item[0], this);
+            const potentialPromise = callbackfn(item[1], item[0], this);
+            if (potentialPromise) {
+                potentialPromise.catch(log.warn);
+            }
         }
     }
 
@@ -73,7 +79,7 @@ export class TimedCache<K, V> implements Map<K, V> {
                 }
                 if (item.done) {
                     // Typscript doesn't like us returning undefined for value, which is dumb.
-                    // tslint:disable-next-line: no-any
+                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                     return {done: true, value: undefined} as any as IteratorResult<[K, V]>;
                 }
                 return {done: false, value: [item.value[0], filteredValue]} as IteratorResult<[K, V]>;
diff --git a/test/db/test_roomstore.ts b/test/db/test_roomstore.ts
index 273ff7057dc260ed26c3f6fa0b352f9b364019a7..f9b0b0fe08c4820578070eb616ebfb64cd28416f 100644
--- a/test/db/test_roomstore.ts
+++ b/test/db/test_roomstore.ts
@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */
+/* eslint-disable @typescript-eslint/camelcase */
 
 import { expect } from "chai";
 import { DiscordStore, CURRENT_SCHEMA } from "../../src/store";
diff --git a/test/mocks/appservicemock.ts b/test/mocks/appservicemock.ts
index 5aaf9ca2e873416efaaea1aea49e688d36a91dde..4de03ee482d1eb6f56606cb805693af62dc7a4e7 100644
--- a/test/mocks/appservicemock.ts
+++ b/test/mocks/appservicemock.ts
@@ -240,7 +240,7 @@ class MatrixClientMock extends AppserviceMockBase {
 
     public async uploadContent(data: Buffer, contentType: string, filename: string = "noname") {
         this.funcCalled("uploadContent", data, contentType, filename);
-        return "mxc://" + filename;
+        return `mxc://${filename}`;
     }
 
     public mxcToHttp(mxcUrl: string) {
diff --git a/test/test_channelsyncroniser.ts b/test/test_channelsyncroniser.ts
index f75d6d2040b164e9dbdc87b7b256990e01d15f8b..a98045bf1747a187f0cb367f403bd64a93ae69f5 100644
--- a/test/test_channelsyncroniser.ts
+++ b/test/test_channelsyncroniser.ts
@@ -214,9 +214,7 @@ describe("ChannelSyncroniser", () => {
 
             const {channelSync} = CreateChannelSync(testStore);
             const chans = await channelSync.GetRoomIdsFromChannel(chan as any);
-            /* tslint:disable:no-magic-numbers */
             expect(chans.length).equals(2);
-            /* tslint:enable:no-magic-numbers */
             expect(chans[0]).equals("!1:localhost");
             expect(chans[1]).equals("!2:localhost");
         });
diff --git a/test/test_discordcommandhandler.ts b/test/test_discordcommandhandler.ts
index f2e0c7b3800318bb9cd2c79f8576e9c8ae566c3a..d29ee9b09a9f157e953c283451786626d2b48e9e 100644
--- a/test/test_discordcommandhandler.ts
+++ b/test/test_discordcommandhandler.ts
@@ -39,12 +39,12 @@ function createCH(opts: any = {}) {
     const discord = {
         ChannelSyncroniser: cs,
         Provisioner: {
-            HasPendingRequest: (chan) => true,
+            HasPendingRequest: (chan): boolean => true,
             MarkApproved: async (chan, member, approved) => {
                 MARKED = approved ? 1 : 0;
                 return approved;
             },
-            UnbridgeChannel: () => {
+            UnbridgeChannel: (): void => {
                 ROOMSUNBRIDGED++;
             },
         },
@@ -52,7 +52,7 @@ function createCH(opts: any = {}) {
     const discordCommandHndlr = (Proxyquire("../src/discordcommandhandler", {
         "./util": {
             Util: {
-                GetMxidFromName: () => {
+                GetMxidFromName: (): string => {
                     return "@123456:localhost";
                 },
                 ParseCommand: Util.ParseCommand,
@@ -69,7 +69,7 @@ describe("DiscordCommandHandler", () => {
         const guild = new MockGuild("456", [channel]);
         channel.guild = guild;
         const member: any = new MockMember("123456", "blah");
-        member.hasPermission = () => {
+        member.hasPermission = (): boolean => {
             return true;
         };
         const message = {
@@ -86,7 +86,7 @@ describe("DiscordCommandHandler", () => {
         const guild = new MockGuild("456", [channel, (new MockChannel("456"))]);
         channel.guild = guild;
         const member: any = new MockMember("123456", "blah");
-        member.hasPermission = () => {
+        member.hasPermission = (): boolean => {
             return true;
         };
         const message = {
@@ -95,7 +95,6 @@ describe("DiscordCommandHandler", () => {
             member,
         };
         await handler.Process(message);
-        // tslint:disable-next-line:no-magic-numbers
         expect(bridge.botIntent.underlyingClient.wasCalled("kickUser")).to.equal(2);
     });
     it("will deny permission", async () => {
@@ -104,7 +103,7 @@ describe("DiscordCommandHandler", () => {
         const guild = new MockGuild("456", [channel]);
         channel.guild = guild;
         const member: any = new MockMember("123456", "blah");
-        member.hasPermission = () => {
+        member.hasPermission = (): boolean => {
             return false;
         };
         const message = {
@@ -121,7 +120,7 @@ describe("DiscordCommandHandler", () => {
         const guild = new MockGuild("456", [channel]);
         channel.guild = guild;
         const member: any = new MockMember("123456", "blah");
-        member.hasPermission = () => {
+        member.hasPermission = (): boolean => {
             return true;
         };
         const message = {
@@ -138,7 +137,7 @@ describe("DiscordCommandHandler", () => {
         const guild = new MockGuild("456", [channel]);
         channel.guild = guild;
         const member: any = new MockMember("123456", "blah");
-        member.hasPermission = () => {
+        member.hasPermission = (): boolean => {
             return true;
         };
         const message = {
@@ -150,12 +149,12 @@ describe("DiscordCommandHandler", () => {
         expect(bridge.botIntent.underlyingClient.wasCalled("unbanUser")).to.equal(1);
     });
     it("handles !matrix approve", async () => {
-        const {handler, bridge} = createCH();
+        const {handler} = createCH();
         const channel = new MockChannel("123");
         const guild = new MockGuild("456", [channel]);
         channel.guild = guild;
         const member: any = new MockMember("123456", "blah");
-        member.hasPermission = () => {
+        member.hasPermission = (): boolean => {
             return true;
         };
         const message = {
@@ -172,7 +171,7 @@ describe("DiscordCommandHandler", () => {
         const guild = new MockGuild("456", [channel]);
         channel.guild = guild;
         const member: any = new MockMember("123456", "blah");
-        member.hasPermission = () => {
+        member.hasPermission = (): boolean => {
             return true;
         };
         const message = {
@@ -189,7 +188,7 @@ describe("DiscordCommandHandler", () => {
         const guild = new MockGuild("456", [channel]);
         channel.guild = guild;
         const member: any = new MockMember("123456", "blah");
-        member.hasPermission = () => {
+        member.hasPermission = (): boolean => {
             return true;
         };
         const message = {
diff --git a/test/test_log.ts b/test/test_log.ts
index ca9b044a81cedd23ca4e91042c3e4be8c7a9aeeb..b3ad9d11dc3aa0470a56cce55ef9bb1cbf3a4911 100644
--- a/test/test_log.ts
+++ b/test/test_log.ts
@@ -26,7 +26,7 @@ let loggedMessages: any[] = [];
 const WinstonMock = {
     createLogger: (format, transports) => {
         return createdLogger = {
-            close: () => { },
+            close: (): void => { },
             format,
             log: (type, ...msg) => {
                 loggedMessages = loggedMessages.concat(msg);
diff --git a/tools/addRoomsToDirectory.ts b/tools/addRoomsToDirectory.ts
index 520a24774ddccb814c9c8451923e365cb6ca1828..6b06ac7b9324bd9d3fe3a2fde04edcc98b250db7 100644
--- a/tools/addRoomsToDirectory.ts
+++ b/tools/addRoomsToDirectory.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-/* tslint:disable:no-console */
+/* eslint-disable no-console */
 /**
  * Allows you to become an admin for a room the bot is in control of.
  */
@@ -53,30 +53,32 @@ const optionDefinitions = [
 const options = args(optionDefinitions);
 
 if (options.help) {
-    /* tslint:disable:no-console */
+    /* eslint-disable no-console */
     console.log(usage([
-    {
-        content: "A tool to set all the bridged rooms to visible in the directory.",
-        header: "Add rooms to directory",
-    },
-    {
-        header: "Options",
-        optionList: optionDefinitions,
-    },
+        {
+            content: "A tool to set all the bridged rooms to visible in the directory.",
+            header: "Add rooms to directory",
+        },
+        {
+            header: "Options",
+            optionList: optionDefinitions,
+        },
     ]));
     process.exit(0);
 }
 
 const {store, appservice} = ToolsHelper.getToolDependencies(options.config, options.registration);
 
-async function run() {
+async function run(): Promise<void> {
     try {
         await store!.init();
     } catch (e) {
         log.error(`Failed to load database`, e);
     }
     let rooms = await store!.roomStore.getEntriesByRemoteRoomData({
+        /* eslint-disable @typescript-eslint/camelcase */
         discord_type: "text",
+        /* eslint-disable @typescript-eslint/camelcase */
     });
     rooms = rooms.filter((r) => r.remote && r.remote.get("plumbed") !== true );
     log.info(`Got ${rooms.length} rooms to set`);
diff --git a/tools/addbot.ts b/tools/addbot.ts
index d505fddd9045036e4822aa37bf62889509803ec8..6fd4517f351edf4585b274d0a1ed8784b3a3b84f 100644
--- a/tools/addbot.ts
+++ b/tools/addbot.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-/* tslint:disable:no-bitwise no-console no-var-requires */
+/* eslint-disable no-bitwise, no-console */
 /**
  * Generates a URL you can use to authorize a bot with a guild.
  */
@@ -26,42 +26,42 @@ import { Util } from "../src/util";
 import { DiscordBridgeConfig } from "../src/config";
 
 const optionDefinitions = [
-  {
-      alias: "h",
-      description: "Display this usage guide.",
-      name: "help",
-      type: Boolean,
-  },
-  {
-      alias: "c",
-      defaultValue: "config.yaml",
-      description: "The AS config file.",
-      name: "config",
-      type: String,
-      typeLabel: "<config.yaml>",
-  },
+    {
+        alias: "h",
+        description: "Display this usage guide.",
+        name: "help",
+        type: Boolean,
+    },
+    {
+        alias: "c",
+        defaultValue: "config.yaml",
+        description: "The AS config file.",
+        name: "config",
+        type: String,
+        typeLabel: "<config.yaml>",
+    },
 ];
 
 const options = args(optionDefinitions);
 
 if (options.help) {
-  /* tslint:disable:no-console */
-  console.log(usage([
-  {
-      content: "A tool to obtain the Discord bot invitation URL.",
-      header: "Add bot",
-  },
-  {
-      header: "Options",
-      optionList: optionDefinitions,
-  },
-  ]));
-  process.exit(0);
+    // eslint-disable-next-line no-console
+    console.log(usage([
+        {
+            content: "A tool to obtain the Discord bot invitation URL.",
+            header: "Add bot",
+        },
+        {
+            header: "Options",
+            optionList: optionDefinitions,
+        },
+    ]));
+    process.exit(0);
 }
 
 const yamlConfig = yaml.safeLoad(fs.readFileSync(options.config, "utf8"));
 if (yamlConfig === null || typeof yamlConfig !== "object") {
-  throw Error("You have an error in your discord config.");
+    throw Error("You have an error in your discord config.");
 }
 const url = Util.GetBotLink(yamlConfig as DiscordBridgeConfig);
 console.log(`Go to ${url} to invite the bot into a guild.`);
diff --git a/tools/adminme.ts b/tools/adminme.ts
index 1504d0ec5788c686a6e052f5c0bbaa99fbe14416..06e756da26241b9f5a589d5c66b76a44178041c8 100644
--- a/tools/adminme.ts
+++ b/tools/adminme.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-/* tslint:disable:no-console */
+/* eslint-disable no-console */
 /**
  * Allows you to become an admin for a room that the bot is in control of.
  */
@@ -71,7 +71,7 @@ const optionDefinitions = [
 const options = args(optionDefinitions);
 
 if (options.help) {
-    /* tslint:disable:no-console */
+    /* eslint-disable no-console */
     console.log(usage([
         {
             content: "A tool to give a user a power level in a bot user controlled room.",
diff --git a/tools/chanfix.ts b/tools/chanfix.ts
index 9b293e891b6457d3a08a1adc179abc7530963cf3..5c186c61419a99a0e5889b90925969f147fc8c99 100644
--- a/tools/chanfix.ts
+++ b/tools/chanfix.ts
@@ -51,7 +51,7 @@ const optionDefinitions = [
 const options = args(optionDefinitions);
 
 if (options.help) {
-    /* tslint:disable:no-console */
+    /* eslint-disable no-console */
     console.log(usage([
     {
         content: "A tool to fix channels of rooms already bridged " +
diff --git a/tools/ghostfix.ts b/tools/ghostfix.ts
index 88dee4380a129063472c55036eafeaa0d2042251..d5f6b40683e27d6910b6f24ffa68a6670dbfc12a 100644
--- a/tools/ghostfix.ts
+++ b/tools/ghostfix.ts
@@ -24,7 +24,6 @@ import { ToolsHelper } from "./toolshelper";
 const log = new Log("GhostFix");
 
 // Note: The schedule must not have duplicate values to avoid problems in positioning.
-/* tslint:disable:no-magic-numbers */ // Disabled because it complains about the values in the array
 const JOIN_ROOM_SCHEDULE = [
     0,              // Right away
     1000,           // 1 second
@@ -32,7 +31,6 @@ const JOIN_ROOM_SCHEDULE = [
     300000,         // 5 minutes
     900000,         // 15 minutes
 ];
-/* tslint:enable:no-magic-numbers */
 
 const optionDefinitions = [
     {
@@ -62,17 +60,17 @@ const optionDefinitions = [
 const options = args(optionDefinitions);
 
 if (options.help) {
-    /* tslint:disable:no-console */
+    /* eslint-disable no-console */
     console.log(usage([
-    {
-        content: "A tool to fix usernames of ghosts already in " +
-        "matrix rooms, to make sure they represent the correct discord usernames.",
-        header: "Fix usernames of joined ghosts",
-    },
-    {
-        header: "Options",
-        optionList: optionDefinitions,
-    },
+        {
+            content: "A tool to fix usernames of ghosts already in " +
+            "matrix rooms, to make sure they represent the correct discord usernames.",
+            header: "Fix usernames of joined ghosts",
+        },
+        {
+            header: "Options",
+            optionList: optionDefinitions,
+        },
     ]));
     process.exit(0);
 }
@@ -132,4 +130,4 @@ async function run() {
     process.exit(0);
 }
 
-run(); // tslint:disable-line no-floating-promises
+run(); // eslint-disable no-floating-promises
diff --git a/tools/userClientTools.ts b/tools/userClientTools.ts
index a6d923f7899075f7ebb8eba544735d94f2ac87fa..a9591018ac5d4904ecf2d8ff4ad7142633df91ad 100644
--- a/tools/userClientTools.ts
+++ b/tools/userClientTools.ts
@@ -57,7 +57,7 @@ const optionDefinitions = [
 
 const options = args(optionDefinitions);
 if (options.help || (options.add && options.remove) || !(options.add || options.remove)) {
-    /* tslint:disable:no-console */
+    /* eslint-disable no-console */
     console.log(usage([
         {
             content: "A tool to give a user a power level in a bot user controlled room.",
diff --git a/tsconfig.json b/tsconfig.json
index e57e4513c483a95d650c220c37987a8829a33ef0..19323e419200383ab52c0675579a455fbe48050e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -13,6 +13,7 @@
     "compileOnSave": true,
     "include": [
         "src/**/*",
+        "test/**/*",
         "tools/**/*",
     ]
 }
diff --git a/tslint.json b/tslint.json
deleted file mode 100644
index 9b0ff6f8d641ed7cb9ae37cc0c24343e655e4306..0000000000000000000000000000000000000000
--- a/tslint.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
-  "extends": "tslint:recommended",
-  "rules": {
-    "ordered-imports": false,
-    "no-trailing-whitespace": "error",
-    "max-classes-per-file": {
-      "severity": "warning"
-    },
-    "object-literal-sort-keys": "off",
-    "no-any": true,
-    "arrow-return-shorthand": true,
-    "prefer-for-of": true,
-    "typedef": {
-      "severity": "warning"
-    },
-    "await-promise": [true],
-    "curly": true,
-    "no-empty": false,
-    "no-invalid-this": true,
-    "no-string-throw": {
-      "severity": "warning"
-    },
-    "no-unused-expression": true,
-    "prefer-const": true,
-    "indent": [true, "spaces", 4],
-    "max-file-line-count": {
-      "severity": "warning",
-      "options": [500]
-    },
-    "no-duplicate-imports": true,
-    "array-type": [true, "array"],
-    "promise-function-async": true,
-    "no-bitwise": true,
-    "no-debugger": true,
-    "no-floating-promises": true,
-    "prefer-template": [true, "allow-single-concat"]
-  }
-}
diff --git a/yarn.lock b/yarn.lock
index 138ce0e20135a001e4a73d009768e1f2ccf1f31e..d2cb11abd4fdb0e9a775917685a885e5ef7e9c79 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -200,6 +200,22 @@
     combined-stream "^1.0.8"
     mime-types "^2.1.12"
 
+"@eslint/eslintrc@^0.2.1":
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.1.tgz#f72069c330461a06684d119384435e12a5d76e3c"
+  integrity sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA==
+  dependencies:
+    ajv "^6.12.4"
+    debug "^4.1.1"
+    espree "^7.3.0"
+    globals "^12.1.0"
+    ignore "^4.0.6"
+    import-fresh "^3.2.1"
+    js-yaml "^3.13.1"
+    lodash "^4.17.19"
+    minimatch "^3.0.4"
+    strip-json-comments "^3.1.1"
+
 "@istanbuljs/load-nyc-config@^1.0.0":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -277,6 +293,11 @@
   dependencies:
     "@types/node" "*"
 
+"@types/eslint-visitor-keys@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
+  integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
+
 "@types/express-serve-static-core@*":
   version "4.17.14"
   resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.14.tgz#cabf91debeeb3cb04b798e2cff908864e89b6106"
@@ -321,6 +342,11 @@
   resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.5.tgz#136d5e6a57a931e1cce6f9d8126aa98a9c92a6bb"
   integrity sha512-JCcp6J0GV66Y4ZMDAQCXot4xprYB+Zfd3meK9+INSJeVZwJmHAW30BBEEkPzXswMXuiyReUGOP3GxrADc9wPww==
 
+"@types/json-schema@^7.0.3":
+  version "7.0.6"
+  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
+  integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
+
 "@types/keyv@*":
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7"
@@ -391,6 +417,49 @@
     "@types/mime" "*"
     "@types/node" "*"
 
+"@typescript-eslint/eslint-plugin@^2.14.0":
+  version "2.34.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9"
+  integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ==
+  dependencies:
+    "@typescript-eslint/experimental-utils" "2.34.0"
+    functional-red-black-tree "^1.0.1"
+    regexpp "^3.0.0"
+    tsutils "^3.17.1"
+
+"@typescript-eslint/experimental-utils@2.34.0":
+  version "2.34.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f"
+  integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==
+  dependencies:
+    "@types/json-schema" "^7.0.3"
+    "@typescript-eslint/typescript-estree" "2.34.0"
+    eslint-scope "^5.0.0"
+    eslint-utils "^2.0.0"
+
+"@typescript-eslint/parser@^2.14.0":
+  version "2.34.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8"
+  integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA==
+  dependencies:
+    "@types/eslint-visitor-keys" "^1.0.0"
+    "@typescript-eslint/experimental-utils" "2.34.0"
+    "@typescript-eslint/typescript-estree" "2.34.0"
+    eslint-visitor-keys "^1.1.0"
+
+"@typescript-eslint/typescript-estree@2.34.0":
+  version "2.34.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5"
+  integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==
+  dependencies:
+    debug "^4.1.1"
+    eslint-visitor-keys "^1.1.0"
+    glob "^7.1.6"
+    is-glob "^4.0.1"
+    lodash "^4.17.15"
+    semver "^7.3.2"
+    tsutils "^3.17.1"
+
 "@ungap/promise-all-settled@1.1.2":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
@@ -411,6 +480,16 @@ accepts@~1.3.7:
     mime-types "~2.1.24"
     negotiator "0.6.2"
 
+acorn-jsx@^5.2.0:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
+  integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
+
+acorn@^7.4.0:
+  version "7.4.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
+  integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
+
 aggregate-error@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@@ -419,7 +498,7 @@ aggregate-error@^3.0.0:
     clean-stack "^2.0.0"
     indent-string "^4.0.0"
 
-ajv@^6.12.3:
+ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4:
   version "6.12.6"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
   integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -429,7 +508,7 @@ ajv@^6.12.3:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
-ansi-colors@4.1.1:
+ansi-colors@4.1.1, ansi-colors@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
   integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
@@ -550,6 +629,11 @@ assertion-error@^1.1.0:
   resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
   integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
 
+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@^3.1.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
@@ -702,11 +786,6 @@ buffer@^5.5.0:
     base64-js "^1.3.1"
     ieee754 "^1.1.13"
 
-builtin-modules@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
-  integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
-
 bytes@3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
@@ -740,6 +819,11 @@ caching-transform@^4.0.0:
     package-hash "^4.0.0"
     write-file-atomic "^3.0.0"
 
+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==
+
 camelcase@^5.0.0, camelcase@^5.3.1:
   version "5.3.1"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
@@ -767,7 +851,7 @@ chai@^4.2.0:
     pathval "^1.1.0"
     type-detect "^4.0.5"
 
-chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.2:
+chalk@^2.0.0, 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==
@@ -932,11 +1016,6 @@ command-line-usage@^6.1.0:
     table-layout "^1.0.0"
     typical "^5.2.0"
 
-commander@^2.12.1:
-  version "2.20.3"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
-  integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
-
 commondir@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -986,7 +1065,7 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
   integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
 
-cross-spawn@^7.0.0:
+cross-spawn@^7.0.0, cross-spawn@^7.0.2:
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
   integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@@ -1021,6 +1100,13 @@ debug@4.2.0, debug@^4.1.0, debug@^4.1.1:
   dependencies:
     ms "2.1.2"
 
+debug@^4.0.1:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
+  integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
+  dependencies:
+    ms "2.1.2"
+
 decamelize@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -1057,6 +1143,11 @@ deep-extend@^0.6.0, deep-extend@~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=
+
 default-require-extensions@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.0.tgz#e03f93aac9b2b6443fc52e5e4a37b3ad9ad8df96"
@@ -1112,6 +1203,13 @@ diff@4.0.2, diff@^4.0.1:
     node-emoji "^1.10.0"
     simple-markdown "^0.7.2"
 
+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-serializer@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.1.0.tgz#5f7c828f1bfc44887dc2a315ab5c45691d544b58"
@@ -1182,6 +1280,13 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1:
   dependencies:
     once "^1.4.0"
 
+enquirer@^2.3.5:
+  version "2.3.6"
+  resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
+  integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
+  dependencies:
+    ansi-colors "^4.1.1"
+
 entities@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
@@ -1207,11 +1312,117 @@ escape-string-regexp@^1.0.5:
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
 
+eslint-scope@^5.0.0, eslint-scope@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+  integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+  dependencies:
+    esrecurse "^4.3.0"
+    estraverse "^4.1.1"
+
+eslint-utils@^2.0.0, eslint-utils@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
+  integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
+  dependencies:
+    eslint-visitor-keys "^1.1.0"
+
+eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
+  integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
+
+eslint-visitor-keys@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
+  integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
+
+eslint@^7.4.0:
+  version "7.14.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.14.0.tgz#2d2cac1d28174c510a97b377f122a5507958e344"
+  integrity sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA==
+  dependencies:
+    "@babel/code-frame" "^7.0.0"
+    "@eslint/eslintrc" "^0.2.1"
+    ajv "^6.10.0"
+    chalk "^4.0.0"
+    cross-spawn "^7.0.2"
+    debug "^4.0.1"
+    doctrine "^3.0.0"
+    enquirer "^2.3.5"
+    eslint-scope "^5.1.1"
+    eslint-utils "^2.1.0"
+    eslint-visitor-keys "^2.0.0"
+    espree "^7.3.0"
+    esquery "^1.2.0"
+    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"
+    is-glob "^4.0.0"
+    js-yaml "^3.13.1"
+    json-stable-stringify-without-jsonify "^1.0.1"
+    levn "^0.4.1"
+    lodash "^4.17.19"
+    minimatch "^3.0.4"
+    natural-compare "^1.4.0"
+    optionator "^0.9.1"
+    progress "^2.0.0"
+    regexpp "^3.1.0"
+    semver "^7.2.1"
+    strip-ansi "^6.0.0"
+    strip-json-comments "^3.1.0"
+    table "^5.2.3"
+    text-table "^0.2.0"
+    v8-compile-cache "^2.0.3"
+
+espree@^7.3.0:
+  version "7.3.0"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348"
+  integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==
+  dependencies:
+    acorn "^7.4.0"
+    acorn-jsx "^5.2.0"
+    eslint-visitor-keys "^1.3.0"
+
 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.2.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57"
+  integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==
+  dependencies:
+    estraverse "^5.1.0"
+
+esrecurse@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+  integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+  dependencies:
+    estraverse "^5.2.0"
+
+estraverse@^4.1.1:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
+  integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
+
+estraverse@^5.1.0, estraverse@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
+  integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
+
+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"
@@ -1293,6 +1504,11 @@ fast-json-stable-stringify@^2.0.0:
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
   integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
 
+fast-levenshtein@^2.0.6:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+  integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
+
 fast-safe-stringify@^2.0.4:
   version "2.0.7"
   resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
@@ -1303,6 +1519,13 @@ fecha@^4.2.0:
   resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.0.tgz#3ffb6395453e3f3efff850404f0a59b6747f5f41"
   integrity sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==
 
+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-stream-rotator@^0.5.7:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/file-stream-rotator/-/file-stream-rotator-0.5.7.tgz#868a2e5966f7640a17dd86eda0e4467c089f6286"
@@ -1382,11 +1605,25 @@ find-up@^4.0.0, find-up@^4.1.0:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
+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"
+
 flat@^5.0.2:
   version "5.0.2"
   resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
   integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
 
+flatted@^2.0.0:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
+  integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
+
 fn.name@1.x.x:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc"
@@ -1456,6 +1693,11 @@ function-bind@^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"
@@ -1509,7 +1751,7 @@ github-from-package@0.0.0:
   resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
   integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=
 
-glob-parent@~5.1.0:
+glob-parent@^5.0.0, glob-parent@~5.1.0:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
   integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
@@ -1521,7 +1763,7 @@ glob-to-regexp@^0.4.1:
   resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
   integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
 
-glob@7.1.6, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
+glob@7.1.6, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
   version "7.1.6"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
   integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@@ -1538,6 +1780,13 @@ globals@^11.1.0:
   resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
   integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
 
+globals@^12.1.0:
+  version "12.4.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8"
+  integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==
+  dependencies:
+    type-fest "^0.8.1"
+
 got@^11.6.0:
   version "11.8.0"
   resolved "https://registry.yarnpkg.com/got/-/got-11.8.0.tgz#be0920c3586b07fd94add3b5b27cb28f49e6545f"
@@ -1702,6 +1951,19 @@ ieee754@^1.1.13:
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
 
+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==
+
+import-fresh@^3.0.0, import-fresh@^3.2.1:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.2.tgz#fc129c160c5d68235507f4331a6baad186bdbc3e"
+  integrity sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==
+  dependencies:
+    parent-module "^1.0.0"
+    resolve-from "^4.0.0"
+
 imurmurhash@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@@ -1781,7 +2043,7 @@ is-fullwidth-code-point@^3.0.0:
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
   integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
 
-is-glob@^4.0.1, is-glob@~4.0.1:
+is-glob@^4.0.0, is-glob@^4.0.1, 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==
@@ -1937,6 +2199,11 @@ json-schema@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:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -1971,6 +2238,14 @@ kuler@^2.0.0:
   resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3"
   integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==
 
+levn@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
+  integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+  dependencies:
+    prelude-ls "^1.2.1"
+    type-check "~0.4.0"
+
 locate-path@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -2008,7 +2283,7 @@ lodash.toarray@^4.4.0:
   resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561"
   integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE=
 
-lodash@4, lodash@^4.17.15, lodash@^4.17.19:
+lodash@4, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
   version "4.17.20"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
   integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
@@ -2275,6 +2550,11 @@ napi-build-utils@^1.0.1:
   resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
   integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
 
+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=
+
 negotiator@0.6.2:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -2429,6 +2709,18 @@ one-time@^1.0.0:
   dependencies:
     fn.name "1.x.x"
 
+optionator@^0.9.1:
+  version "0.9.1"
+  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
+  integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
+  dependencies:
+    deep-is "^0.1.3"
+    fast-levenshtein "^2.0.6"
+    levn "^0.4.1"
+    prelude-ls "^1.2.1"
+    type-check "^0.4.0"
+    word-wrap "^1.2.3"
+
 p-cancelable@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e"
@@ -2516,6 +2808,13 @@ packet-reader@1.0.0:
   resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74"
   integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==
 
+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-srcset@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1"
@@ -2701,6 +3000,11 @@ prebuild-install@^5.3.3:
     tunnel-agent "^0.6.0"
     which-pm-runs "^1.0.0"
 
+prelude-ls@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
+  integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
 prism-media@^1.2.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-1.2.2.tgz#4f1c841f248b67d325a24b4e6b1a491b8f50a24f"
@@ -2718,6 +3022,11 @@ process-on-spawn@^1.0.0:
   dependencies:
     fromentries "^1.2.0"
 
+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==
+
 prom-client@^12.0.0:
   version "12.0.0"
   resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-12.0.0.tgz#9689379b19bd3f6ab88a9866124db9da3d76c6ed"
@@ -2841,6 +3150,11 @@ reduce-flatten@^2.0.0:
   resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27"
   integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==
 
+regexpp@^3.0.0, regexpp@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
+  integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
+
 release-zalgo@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730"
@@ -2906,6 +3220,11 @@ resolve-alpn@^1.0.0:
   resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c"
   integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==
 
+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-from@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
@@ -2931,6 +3250,13 @@ responselike@^2.0.0:
   dependencies:
     lowercase-keys "^2.0.0"
 
+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"
+
 rimraf@^3.0.0:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@@ -2970,7 +3296,7 @@ semver-closest@^0.1.0:
   dependencies:
     semver "^5.4.1"
 
-semver@^5.3.0, semver@^5.4.1:
+semver@^5.4.1:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -2980,6 +3306,11 @@ semver@^6.0.0, semver@^6.3.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
+semver@^7.2.1, semver@^7.3.2:
+  version "7.3.2"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
+  integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
+
 send@0.17.1:
   version "0.17.1"
   resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@@ -3081,6 +3412,15 @@ simple-swizzle@^0.2.2:
   dependencies:
     is-arrayish "^0.3.1"
 
+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"
+
 source-map-support@^0.5.17, source-map-support@^0.5.19:
   version "0.5.19"
   resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
@@ -3252,7 +3592,7 @@ strip-bom@^4.0.0:
   resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
   integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==
 
-strip-json-comments@3.1.1:
+strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
   integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
@@ -3293,6 +3633,16 @@ table-layout@^1.0.0:
     typical "^5.2.0"
     wordwrapjs "^4.0.0"
 
+table@^5.2.3:
+  version "5.4.6"
+  resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
+  integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==
+  dependencies:
+    ajv "^6.10.2"
+    lodash "^4.17.14"
+    slice-ansi "^2.1.0"
+    string-width "^3.0.0"
+
 tar-fs@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.0.tgz#d1cdd121ab465ee0eb9ccde2d35049d3f3daf0d5"
@@ -3348,6 +3698,11 @@ text-hex@1.0.x:
   resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
   integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==
 
+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=
+
 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"
@@ -3389,34 +3744,15 @@ ts-node@^8.10.2:
     source-map-support "^0.5.17"
     yn "3.1.1"
 
-tslib@^1.8.0, tslib@^1.8.1:
+tslib@^1.8.1:
   version "1.14.1"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
 
-tslint@^5.20.1:
-  version "5.20.1"
-  resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d"
-  integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==
-  dependencies:
-    "@babel/code-frame" "^7.0.0"
-    builtin-modules "^1.1.1"
-    chalk "^2.3.0"
-    commander "^2.12.1"
-    diff "^4.0.1"
-    glob "^7.1.1"
-    js-yaml "^3.13.1"
-    minimatch "^3.0.4"
-    mkdirp "^0.5.1"
-    resolve "^1.3.2"
-    semver "^5.3.0"
-    tslib "^1.8.0"
-    tsutils "^2.29.0"
-
-tsutils@^2.29.0:
-  version "2.29.0"
-  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
-  integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==
+tsutils@^3.17.1:
+  version "3.17.1"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
+  integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==
   dependencies:
     tslib "^1.8.1"
 
@@ -3437,12 +3773,19 @@ tweetnacl@^1.0.3:
   resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
   integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
 
+type-check@^0.4.0, type-check@~0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
+  integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+  dependencies:
+    prelude-ls "^1.2.1"
+
 type-detect@^4.0.0, type-detect@^4.0.5:
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
   integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
 
-type-fest@^0.8.0:
+type-fest@^0.8.0, type-fest@^0.8.1:
   version "0.8.1"
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
   integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
@@ -3518,6 +3861,11 @@ uuid@^3.3.2, uuid@^3.3.3:
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
   integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
 
+v8-compile-cache@^2.0.3:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132"
+  integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==
+
 uuid@^8.3.1:
   version "8.3.1"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
@@ -3602,6 +3950,11 @@ winston@^3.2.1:
     triple-beam "^1.3.0"
     winston-transport "^4.4.0"
 
+word-wrap@^1.2.3:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
+  integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
+
 wordwrapjs@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/wordwrapjs/-/wordwrapjs-4.0.0.tgz#9aa9394155993476e831ba8e59fb5795ebde6800"
@@ -3648,6 +4001,13 @@ write-file-atomic@^3.0.0:
     signal-exit "^3.0.2"
     typedarray-to-buffer "^3.1.5"
 
+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@^7.3.1:
   version "7.3.1"
   resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"