Repository: intuit/auto
Pull request #605 (2019-10-16 18:47:12)

Update Tooling
- Use `tsc -b` + references for faster, more reliable builds
- Switch from `tslint` to `eslint`
<!-- GITHUB_RELEASE PR BODY: canary-version -->
Published PR with canary version: `7.8.1-canary.605.7759.0`
<!-- GITHUB_RELEASE PR BODY: canary-version -->



Patch:
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 00000000..569ce539
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,2 @@
+**/node_modules
+**/dist
\ No newline at end of file
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 00000000..b833a2c4
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,67 @@
+{
+  "parser": "@typescript-eslint/parser",
+
+  "parserOptions": {
+    "project": "./tsconfig.json",
+    "sourceType": "module"
+  },
+
+  "extends": [
+    "xo",
+    "plugin:jest/recommended",
+    "plugin:@typescript-eslint/recommended",
+    "prettier",
+    "prettier/@typescript-eslint"
+  ],
+
+  "plugins": ["prettier", "jest", "@typescript-eslint"],
+
+  "rules": {
+    /* xo config */
+
+    // makes commenting out lines quickly a hassle
+    "capitalized-comments": 0,
+    "default-param-last": 0,
+    "complexity": ["error", { "max": 25 }],
+
+    /* jest plugin */
+
+    "jest/no-empty-title": 2,
+    "jest/prefer-strict-equal": 2,
+    "jest/prefer-spy-on": 2,
+    "jest/no-standalone-expect": 2,
+    "jest/no-try-expect": 2,
+    "jest/no-export": 2,
+    "jest/no-truthy-falsy": 1,
+    "jest/no-duplicate-hooks": 1,
+    "jest/no-if": 1,
+    "jest/prefer-to-have-length": 1,
+
+    /* typescript */
+
+    "@typescript-eslint/no-this-alias": 2,
+    "@typescript-eslint/no-unnecessary-type-assertion": 2,
+    "@typescript-eslint/no-useless-constructor": 2,
+    // if we turn this on babel adds regeneratorRuntime which makes builds harder and larger
+    "@typescript-eslint/promise-function-async": 0,
+    // just rely on typescript inference
+    "@typescript-eslint/explicit-function-return-type": 0,
+    "@typescript-eslint/camelcase": 0,
+    "@typescript-eslint/interface-name-prefix": 0,
+    "@typescript-eslint/no-non-null-assertion": 0,
+    "@typescript-eslint/explicit-member-accessibility": 0
+  },
+
+  "overrides": [
+    {
+      "files": ["*.test.*"],
+      "rules": {
+        "@typescript-eslint/no-non-null-assertion": 0,
+        "@typescript-eslint/no-explicit-any": 0,
+        "require-atomic-updates": 0,
+        "max-nested-callbacks": 0,
+        "@typescript-eslint/ban-ts-ignore": 0
+      }
+    }
+  ]
+}
diff --git a/.gitignore b/.gitignore
index 194c2977..3f525424 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,7 @@ package-lock.json
 _ignite
 .vscode/
 binary
-
+**/tsconfig.tsbuildinfo
 
 # Created by https://www.gitignore.io/api/node,intellij,sublimetext,visualstudiocode
 # Edit at https://www.gitignore.io/?templates=node,intellij,sublimetext,visualstudiocode
diff --git a/package.json b/package.json
index d0a7fe54..40837c7f 100644
--- a/package.json
+++ b/package.json
@@ -22,11 +22,11 @@
     "plugins/*"
   ],
   "scripts": {
-    "clean": "lerna clean --yes && rm -rf node_modules && rm -rf packages/*/dist && rm -rf plugins/*/dist",
+    "clean": "lerna clean --yes && rimraf node_modules '+(packages|plugins)/**/+(dist|tsconfig.tsbuildinfo)'",
     "semver:check": "./scripts/post-install.sh",
-    "build": "lerna run build --stream",
-    "build:watch": "lerna run build --parallel -- --watch --preserveWatchOutput",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b tsconfig.dev.json",
+    "start": "npm run build -- --watch",
+    "lint": "eslint packages plugins --ext .ts",
     "precommit": "lint-staged",
     "test": "jest --runInBand",
     "release": "chmod +x ./packages/cli/dist/bin/auto.js && ./packages/cli/dist/bin/auto.js shipit",
@@ -38,9 +38,17 @@
     "create:plugin": "./scripts/create-plugin.js"
   },
   "devDependencies": {
+    "@typescript-eslint/eslint-plugin": "^2.4.0",
+    "@typescript-eslint/parser": "^2.4.0",
     "all-contributors-cli": "^6.4.0",
     "change-case": "^3.1.0",
     "copy-template-dir": "^1.4.0",
+    "eslint": "^6.5.1",
+    "eslint-config-prettier": "^6.4.0",
+    "eslint-config-xo": "^0.27.1",
+    "eslint-plugin-import": "^2.18.2",
+    "eslint-plugin-jest": "^22.19.0",
+    "eslint-plugin-prettier": "^3.1.1",
     "graphql": "^14.2.1",
     "husky": "^3.0.3",
     "ignite": "^1.10.2",
@@ -48,12 +56,9 @@
     "lerna": "^3.13.4",
     "lint-staged": "^9.4.0",
     "prettier": "^1.16.4",
+    "rimraf": "^3.0.0",
     "ts-jest": "^24.0.2",
-    "tslint": "~5.20.0",
-    "tslint-config-prettier": "~1.18.0",
-    "tslint-xo": "~0.16.0",
-    "typescript": "~3.6.2",
-    "typescript-tslint-plugin": "^0.5.4"
+    "typescript": "~3.6.2"
   },
   "prettier": {
     "singleQuote": true
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 5af8d7e3..295aa4f3 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -33,9 +33,9 @@
     "cli"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2",
     "bundle": "rimraf binary && pkg . --out-path binary && yarn gzip",
     "gzip": "ls binary/auto* | xargs gzip"
@@ -55,7 +55,6 @@
     "@types/command-line-usage": "^5.0.1",
     "@types/dedent": "^0.7.0",
     "@types/signale": "^1.2.1",
-    "pkg": "^4.4.0",
-    "rimraf": "^3.0.0"
+    "pkg": "^4.4.0"
   }
 }
diff --git a/packages/cli/src/parse-args.ts b/packages/cli/src/parse-args.ts
index 09830b21..1bb15e02 100644
--- a/packages/cli/src/parse-args.ts
+++ b/packages/cli/src/parse-args.ts
@@ -515,6 +515,30 @@ function filterCommands(allCommands: ICommand[], include: string[]) {
     }));
 }
 
+function styleTypes(
+  command: ICommand,
+  option: commandLineUsage.OptionDefinition
+) {
+  const isRequired =
+    command.require && command.require.includes(option.name as Flags);
+
+  if (isRequired && option.type === Number) {
+    option.typeLabel =
+      '{rgb(173, 216, 230) {underline number}} [{rgb(254,91,92) required}]';
+  } else if (option.type === Number) {
+    option.typeLabel = '{rgb(173, 216, 230) {underline number}}';
+  }
+
+  if (isRequired && option.type === String) {
+    option.typeLabel =
+      '{rgb(173, 216, 230) {underline string}} [{rgb(254,91,92) required}]';
+  } else if (option.multiple && option.type === String) {
+    option.typeLabel = '{rgb(173, 216, 230) {underline string[]}}';
+  } else if (option.type === String) {
+    option.typeLabel = '{rgb(173, 216, 230) {underline string}}';
+  }
+}
+
 function printRootHelp() {
   const options = [
     { ...version, group: 'misc' },
@@ -621,30 +645,6 @@ function printVersion() {
   console.log(`v${packageJson.version}`);
 }
 
-function styleTypes(
-  command: ICommand,
-  option: commandLineUsage.OptionDefinition
-) {
-  const isRequired =
-    command.require && command.require.includes(option.name as Flags);
-
-  if (isRequired && option.type === Number) {
-    option.typeLabel =
-      '{rgb(173, 216, 230) {underline number}} [{rgb(254,91,92) required}]';
-  } else if (option.type === Number) {
-    option.typeLabel = '{rgb(173, 216, 230) {underline number}}';
-  }
-
-  if (isRequired && option.type === String) {
-    option.typeLabel =
-      '{rgb(173, 216, 230) {underline string}} [{rgb(254,91,92) required}]';
-  } else if (option.multiple && option.type === String) {
-    option.typeLabel = '{rgb(173, 216, 230) {underline string[]}}';
-  } else if (option.type === String) {
-    option.typeLabel = '{rgb(173, 216, 230) {underline string}}';
-  }
-}
-
 export default function parseArgs(testArgs?: string[]) {
   const mainOptions = commandLineArgs(mainDefinitions, {
     stopAtFirstUnknown: true,
diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json
index a9bb8ce0..0f4f9be9 100644
--- a/packages/cli/tsconfig.json
+++ b/packages/cli/tsconfig.json
@@ -1,7 +1,22 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../core"
+    },
+    {
+      "path": "../../plugins/npm"
+    },
+    {
+      "path": "../../plugins/released"
+    }
+  ]
 }
diff --git a/packages/core/package.json b/packages/core/package.json
index 2c51226d..4ef22d81 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -30,9 +30,9 @@
     "node"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/packages/core/src/auto.ts b/packages/core/src/auto.ts
index 8db696d6..b6162223 100644
--- a/packages/core/src/auto.ts
+++ b/packages/core/src/auto.ts
@@ -34,8 +34,7 @@ import LogParse, { IExtendedCommit } from './log-parse';
 import Release, {
   getVersionMap,
   IAutoConfig,
-  ILabelDefinitionMap,
-  VersionLabel
+  ILabelDefinitionMap
 } from './release';
 import SEMVER, { calculateSemVerBump, IVersionLabels } from './semver';
 import execPromise from './utils/exec-promise';
@@ -228,7 +227,9 @@ export default class Auto {
     this.logger.verbose.info("Using command: 'label'");
     let labels: string[] = [];
 
-    if (!pr) {
+    if (pr) {
+      labels = await this.git.getLabels(pr);
+    } else {
       const pulls = await this.git.getPullRequests({
         state: 'closed'
       });
@@ -237,13 +238,11 @@ export default class Auto {
           (a, b) =>
             new Date(b.merged_at).getTime() - new Date(a.merged_at).getTime()
         )
-        .find(pull => !!pull.merged_at);
+        .find(pull => pull.merged_at);
 
       if (lastMerged) {
         labels = lastMerged.labels.map(label => label.name);
       }
-    } else {
-      labels = await this.git.getLabels(pr);
     }
 
     if (labels.length) {
@@ -286,7 +285,9 @@ export default class Auto {
     // tslint:disable-next-line variable-name
     const target_url = url;
 
-    if (!dryRun) {
+    if (dryRun) {
+      this.logger.verbose.info('`pr` dry run complete.');
+    } else {
       try {
         await this.git.createStatus({
           ...options,
@@ -300,8 +301,6 @@ export default class Auto {
       }
 
       this.logger.log.success('Posted status to Pull Request.');
-    } else {
-      this.logger.verbose.info('`pr` dry run complete.');
     }
 
     this.logger.verbose.success('Finished `pr` command');
@@ -334,13 +333,12 @@ export default class Auto {
       const releaseTag = labels.find(l => l === 'release');
 
       const skipReleaseTag = labels.find(
-        l =>
-          !!this.release && this.release.options.skipReleaseLabels.includes(l)
+        l => this.release && this.release.options.skipReleaseLabels.includes(l)
       );
       const semverTag = labels.find(
         l =>
           labelValues.some(labelValue => labelValue.includes(l)) &&
-          !!this.release &&
+          this.release &&
           !this.release.options.skipReleaseLabels.includes(l) &&
           l !== 'release'
       );
@@ -376,7 +374,9 @@ export default class Auto {
 
     this.logger.verbose.info('Posting status to GitHub\n', msg);
 
-    if (!dryRun) {
+    if (dryRun) {
+      this.logger.verbose.info('`pr-check` dry run complete.');
+    } else {
       try {
         await this.git.createStatus({
           ...options,
@@ -391,8 +391,6 @@ export default class Auto {
           `Failed to post status to Pull Request with error code ${error.status}`
         );
       }
-    } else {
-      this.logger.verbose.info('`pr-check` dry run complete.');
     }
 
     this.logger.verbose.success('Finished `pr-check` command');
@@ -764,6 +762,7 @@ export default class Auto {
     if (!this.git || !this.release) {
       throw this.createErrorMessage();
     }
+
     const lastRelease = from || (await this.git.getLatestRelease());
     const bump = await this.release.getSemverBump(lastRelease);
     this.versionBump = bump;
@@ -866,13 +865,13 @@ export default class Auto {
 
     let release: Response<ReposCreateReleaseResponse> | undefined;
 
-    if (!dryRun) {
-      this.logger.log.info(`Releasing ${newVersion} to GitHub.`);
-      release = await this.git.publish(releaseNotes, newVersion);
-    } else {
+    if (dryRun) {
       this.logger.log.info(
         `Would have released (unless ran with "shipit"): ${newVersion}`
       );
+    } else {
+      this.logger.log.info(`Releasing ${newVersion} to GitHub.`);
+      release = await this.git.publish(releaseNotes, newVersion);
     }
 
     await this.hooks.afterRelease.promise({
@@ -974,10 +973,11 @@ If a command fails manually run:
 
     pluginsPaths
       .map(plugin =>
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         typeof plugin === 'string' ? ([plugin, {}] as [string, any]) : plugin
       )
       .map(plugin => loadPlugin(plugin, this.logger))
-      .filter((plugin): plugin is IPlugin => !!plugin)
+      .filter((plugin): plugin is IPlugin => Boolean(plugin))
       .forEach(plugin => {
         this.logger.verbose.info(`Using ${plugin.name} Plugin...`);
         plugin.apply(this);
diff --git a/packages/core/src/changelog.ts b/packages/core/src/changelog.ts
index 49447e5b..02c32af2 100644
--- a/packages/core/src/changelog.ts
+++ b/packages/core/src/changelog.ts
@@ -287,7 +287,7 @@ export default class Changelog {
   private async createLabelSection(split: ICommitSplit, sections: string[]) {
     const changelogTitles = Object.entries(this.options.labels).reduce(
       (titles, [, labels]) => {
-        labels.map(labelDef => {
+        labels.forEach(labelDef => {
           if (labelDef.title) {
             titles[labelDef.name] = labelDef.title;
           }
@@ -358,7 +358,7 @@ export default class Changelog {
       })
     );
 
-    included.map(commit => {
+    included.forEach(commit => {
       if (!commit) {
         return;
       }
diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts
index 644acf28..6aaf96a9 100644
--- a/packages/core/src/config.ts
+++ b/packages/core/src/config.ts
@@ -142,6 +142,7 @@ export default class Config {
       if (extend.endsWith('package.json')) {
         config = config && config.auto;
       }
+
       this.logger.verbose.note(`${extend} found: ${config}`);
     } else {
       config = tryRequire(`${extend}/package.json`);
diff --git a/packages/core/src/git.ts b/packages/core/src/git.ts
index 6264d63d..1dda39d8 100644
--- a/packages/core/src/git.ts
+++ b/packages/core/src/git.ts
@@ -8,7 +8,7 @@ import HttpsProxyAgent from 'https-proxy-agent';
 import tinyColor from 'tinycolor2';
 import { promisify } from 'util';
 
-import { Memoize } from 'typescript-memoize';
+import { Memoize as memoize } from 'typescript-memoize';
 
 import { ILabelDefinition } from './release';
 import execPromise from './utils/exec-promise';
@@ -65,16 +65,19 @@ export default class Git {
     this.baseUrl = this.options.baseUrl || 'https://api.github.com';
     this.graphqlBaseUrl = this.options.graphqlBaseUrl || this.baseUrl;
     this.logger.veryVerbose.info(`Initializing GitHub with: ${this.baseUrl}`);
-    const gitHub = Octokit.plugin(enterpriseCompat)
+    const GitHub = Octokit.plugin(enterpriseCompat)
       .plugin(retry)
       .plugin(throttling);
-    this.github = new gitHub({
+    this.github = new GitHub({
       baseUrl: this.baseUrl,
       agent: this.options.agent,
       auth: this.options.token,
       previews: ['symmetra-preview'],
       throttle: {
-        onRateLimit: (retryAfter: number, opts: any) => {
+        onRateLimit: (
+          retryAfter: number,
+          opts: { request: { retryCount: number }; method: string; url: string }
+        ) => {
           this.logger.log.warn(
             `Request quota exhausted for request ${opts.method} ${opts.url}`
           );
@@ -84,7 +87,10 @@ export default class Git {
             return true;
           }
         },
-        onAbuseLimit: (retryAfter: number, opts: any) => {
+        onAbuseLimit: (
+          retryAfter: number,
+          opts: { request: { retryCount: number }; method: string; url: string }
+        ) => {
           // does not retry, only logs an error
           this.logger.log.error(
             `Went over abuse rate limit ${opts.method} ${opts.url}`
@@ -96,11 +102,12 @@ export default class Git {
       if (error && error.headers && error.headers.authorization) {
         delete error.headers.authorization;
       }
+
       throw error;
     });
   }
 
-  @Memoize()
+  @memoize()
   async getLatestReleaseInfo() {
     const latestRelease = await this.github.repos.getLatestRelease({
       owner: this.options.owner,
@@ -110,7 +117,7 @@ export default class Git {
     return latestRelease.data;
   }
 
-  @Memoize()
+  @memoize()
   async getLatestRelease(): Promise<string> {
     try {
       const latestRelease = await this.getLatestReleaseInfo();
@@ -161,7 +168,7 @@ export default class Git {
     return result;
   }
 
-  @Memoize()
+  @memoize()
   async getLabels(prNumber: number) {
     this.logger.verbose.info(`Getting labels for PR: ${prNumber}`);
 
@@ -187,7 +194,7 @@ export default class Git {
     }
   }
 
-  @Memoize()
+  @memoize()
   async getPr(prNumber: number) {
     this.logger.verbose.info(`Getting info for PR: ${prNumber}`);
 
@@ -232,7 +239,7 @@ export default class Git {
     }
   }
 
-  @Memoize()
+  @memoize()
   async getGitLog(start: string, end = 'HEAD'): Promise<ICommit[]> {
     const log = await gitlog({
       repo: process.cwd(),
@@ -250,7 +257,7 @@ export default class Git {
     }));
   }
 
-  @Memoize()
+  @memoize()
   async getUserByEmail(email: string) {
     try {
       const search = (await this.github.search.users({
@@ -263,7 +270,7 @@ export default class Git {
     }
   }
 
-  @Memoize()
+  @memoize()
   async getUserByUsername(username: string) {
     try {
       const user = await this.github.users.getByUsername({
@@ -276,7 +283,7 @@ export default class Git {
     }
   }
 
-  @Memoize()
+  @memoize()
   async getPullRequest(pr: number) {
     this.logger.verbose.info(`Getting Pull Request: ${pr}`);
 
@@ -413,7 +420,7 @@ export default class Git {
     return result;
   }
 
-  @Memoize()
+  @memoize()
   async getProject() {
     this.logger.verbose.info('Getting project from GitHub');
 
@@ -443,7 +450,7 @@ export default class Git {
     return result;
   }
 
-  @Memoize()
+  @memoize()
   async getCommitsForPR(
     pr: number
   ): Promise<Octokit.PullsListCommitsResponseItem[]> {
@@ -494,6 +501,7 @@ export default class Git {
     if (commentId === -1) {
       return;
     }
+
     this.logger.verbose.info(`Deleting comment: ${commentId}`);
     await this.github.issues.deleteComment({
       owner: this.options.owner,
@@ -533,6 +541,7 @@ export default class Git {
     if (commentId === -1) {
       return this.createComment(message, pr, context);
     }
+
     this.logger.verbose.info('Editing comment');
     const result = await this.github.issues.updateComment({
       owner: this.options.owner,
diff --git a/packages/core/src/init.ts b/packages/core/src/init.ts
index 1d0bf5ec..21bb8fa5 100644
--- a/packages/core/src/init.ts
+++ b/packages/core/src/init.ts
@@ -1,4 +1,4 @@
-// tslint:disable no-unnecessary-type-annotation
+/* eslint-disable no-await-in-loop, @typescript-eslint/ban-ts-ignore */
 
 import dedent from 'dedent';
 import { prompt } from 'enquirer';
@@ -11,7 +11,7 @@ import { defaultLabelDefinition, ILabelDefinition } from './release';
 import { ILogger } from './utils/logger';
 
 const writeFile = promisify(fs.writeFile);
-const isObject = (value: any) => typeof value === 'object' && value !== null;
+const isObject = <T>(value: T) => typeof value === 'object' && value !== null;
 
 async function getFlags() {
   return prompt([
@@ -154,7 +154,9 @@ async function getCustomLabels(onlyLabels = false) {
         title:  #{title}
         desc:   #{description}
       `,
-      validate: (state: any) => {
+      validate: (state: {
+        values: { name?: string; title?: string; description?: string };
+      }) => {
         if (!state.values.name) {
           return 'Label is required for new label';
         }
@@ -198,7 +200,7 @@ export default async function init(
   const autoRc = Object.entries({
     ...flags,
     labels
-  } as { [key: string]: any }).reduce(
+  }).reduce(
     (all, [key, value]) => {
       if (
         value === '' ||
@@ -213,7 +215,7 @@ export default async function init(
         [key]: value
       };
     },
-    {} as { [key: string]: any }
+    {} as { [key: string]: number | {} | boolean }
   );
 
   if (Object.keys(autoRc).length === 0) {
diff --git a/packages/core/src/release.ts b/packages/core/src/release.ts
index 0070f338..29fdaead 100644
--- a/packages/core/src/release.ts
+++ b/packages/core/src/release.ts
@@ -8,7 +8,7 @@ import { inc, ReleaseType } from 'semver';
 import { promisify } from 'util';
 
 import { AsyncSeriesBailHook, SyncHook } from 'tapable';
-import { Memoize } from 'typescript-memoize';
+import { Memoize as memoize } from 'typescript-memoize';
 import { ICreateLabelsOptions } from './auto-args';
 import Changelog from './changelog';
 import Git from './git';
@@ -49,7 +49,7 @@ export interface IAutoConfig {
   skipReleaseLabels: string[];
   onlyPublishWithReleaseLabel?: boolean;
   noVersionPrefix?: boolean;
-  plugins?: (string | [string, any])[];
+  plugins?: (string | [string, number | boolean | string | object])[];
   labels: ILabelDefinitionMap;
 }
 
@@ -468,21 +468,21 @@ export default class Release {
     const labelsToCreate = Object.entries(labels).filter(
       ([versionLabel, labelDef]) => {
         if (!labelDef) {
-          return;
+          return false;
         }
 
         if (
           versionLabel === 'release' &&
           !this.options.onlyPublishWithReleaseLabel
         ) {
-          return;
+          return false;
         }
 
         if (
           versionLabel === 'skip-release' &&
           this.options.onlyPublishWithReleaseLabel
         ) {
-          return;
+          return false;
         }
 
         return true;
@@ -564,7 +564,7 @@ export default class Release {
     return inc(lastTag, bump as ReleaseType);
   }
 
-  @Memoize()
+  @memoize()
   private async createLogParse() {
     const logParse = new LogParse();
 
@@ -584,7 +584,7 @@ export default class Release {
     return logParse;
   }
 
-  @Memoize()
+  @memoize()
   private async getPRsSinceLastRelease() {
     let lastRelease: { published_at: string };
 
@@ -625,31 +625,35 @@ export default class Release {
    * @param commits Commits to modify
    */
   private async addPrInfoToCommit(commit: IExtendedCommit) {
-    if (!commit.labels) {
-      commit.labels = [];
+    const modifiedCommit = { ...commit };
+
+    if (!modifiedCommit.labels) {
+      modifiedCommit.labels = [];
     }
 
-    if (commit.pullRequest) {
-      const info = await this.git.getPr(commit.pullRequest.number);
+    if (modifiedCommit.pullRequest) {
+      const info = await this.git.getPr(modifiedCommit.pullRequest.number);
 
       if (!info || !info.data) {
-        return commit;
+        return modifiedCommit;
       }
 
       const labels = info ? info.data.labels.map(l => l.name) : [];
-      commit.labels = [...new Set([...labels, ...commit.labels])];
-      commit.pullRequest.body = info.data.body;
+      modifiedCommit.labels = [
+        ...new Set([...labels, ...modifiedCommit.labels])
+      ];
+      modifiedCommit.pullRequest.body = info.data.body;
 
-      if (!commit.authors.find(author => Boolean(author.username))) {
+      if (!modifiedCommit.authors.find(author => Boolean(author.username))) {
         const user = await this.git.getUserByUsername(info.data.user.login);
 
         if (user) {
-          commit.authors.push({ ...user, username: user.login });
+          modifiedCommit.authors.push({ ...user, username: user.login });
         }
       }
     }
 
-    return commit;
+    return modifiedCommit;
   }
 
   /**
@@ -679,15 +683,16 @@ export default class Release {
   }
 
   private async attachAuthor(commit: IExtendedCommit) {
+    const modifiedCommit = { ...commit };
     let resolvedAuthors: (
       | (ICommitAuthor & { login?: string })
       | Partial<GHub.UsersGetByUsernameResponse>)[] = [];
 
     // If there is a pull request we will attempt to get the authors
     // from any commit in the PR
-    if (commit.pullRequest) {
+    if (modifiedCommit.pullRequest) {
       const [prCommitsErr, prCommits] = await on(
-        this.git.getCommitsForPR(Number(commit.pullRequest.number))
+        this.git.getCommitsForPR(Number(modifiedCommit.pullRequest.number))
       );
 
       if (prCommitsErr || !prCommits) {
@@ -720,17 +725,17 @@ export default class Release {
       });
     }
 
-    commit.authors = resolvedAuthors.map(author => ({
+    modifiedCommit.authors = resolvedAuthors.map(author => ({
       ...author,
       ...(author && 'login' in author ? { username: author.login } : {})
     }));
 
-    commit.authors.map(author => {
+    modifiedCommit.authors.forEach(author => {
       this.logger.veryVerbose.info(
         `Found author: ${author.username} ${author.email} ${author.name}`
       );
     });
 
-    return commit;
+    return modifiedCommit;
   }
 }
diff --git a/packages/core/src/semver.ts b/packages/core/src/semver.ts
index 993693da..34dfdf3b 100644
--- a/packages/core/src/semver.ts
+++ b/packages/core/src/semver.ts
@@ -45,7 +45,7 @@ export function calculateSemVerBump(
     }
   });
 
-  labels.map(pr => {
+  labels.forEach(pr => {
     pr.forEach(label => {
       const userLabel = [...labelMap.entries()].find(pair =>
         pair[1].includes(label)
diff --git a/packages/core/src/utils/exec-promise.ts b/packages/core/src/utils/exec-promise.ts
index b164cc27..ae547735 100644
--- a/packages/core/src/utils/exec-promise.ts
+++ b/packages/core/src/utils/exec-promise.ts
@@ -48,14 +48,7 @@ export default async function execPromise(
 
     child.on('exit', code => {
       // No code, no errors
-      if (!code) {
-        // Tools can occasionally print to stderr but not fail, so print that just in case.
-        if (allStderr.length) {
-          console.log(allStderr);
-        }
-        // Resolve the string of the whole stdout
-        completed(allStdout.trim());
-      } else {
+      if (code) {
         // The command bailed for whatever reason, print a verbose error message
         // with the stdout underneath
         let appendedStdErr = '';
@@ -63,6 +56,14 @@ export default async function execPromise(
         appendedStdErr += allStderr.length ? `\n\n${allStderr}` : '';
 
         reject(new Error(`Running command '${cmd}' failed${appendedStdErr}`));
+      } else {
+        // Tools can occasionally print to stderr but not fail, so print that just in case.
+        if (allStderr.length) {
+          console.log(allStderr);
+        }
+
+        // Resolve the string of the whole stdout
+        completed(allStdout.trim());
       }
     });
   });
diff --git a/packages/core/src/utils/load-plugins.ts b/packages/core/src/utils/load-plugins.ts
index 42310e25..5f95ee49 100644
--- a/packages/core/src/utils/load-plugins.ts
+++ b/packages/core/src/utils/load-plugins.ts
@@ -1,3 +1,5 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
 import * as path from 'path';
 import Auto from '../auto';
 import { ILogger } from './logger';
@@ -14,11 +16,7 @@ export default function loadPlugin(
   [pluginPath, options]: [string, any],
   logger: ILogger
 ): IPlugin | undefined {
-  // tslint:disable-next-line:no-unnecessary-initializer
-  let plugin:
-    | IPluginConstructor
-    | { default: IPluginConstructor }
-    | undefined = undefined;
+  let plugin: IPluginConstructor | { default: IPluginConstructor } | undefined;
 
   // Try requiring a path
   if (pluginPath.startsWith('.') || pluginPath.startsWith('/')) {
@@ -28,8 +26,11 @@ export default function loadPlugin(
   // Try requiring a path from cwd
   if (!plugin && (pluginPath.startsWith('.') || pluginPath.startsWith('/'))) {
     plugin = tryRequire(path.join(process.cwd(), pluginPath));
-    logger.log.warn(`Could not find plugin from path: ${pluginPath}`);
-    return;
+
+    if (!plugin) {
+      logger.log.warn(`Could not find plugin from path: ${pluginPath}`);
+      return;
+    }
   }
 
   // For pkg bundle
@@ -63,6 +64,7 @@ export default function loadPlugin(
 
   try {
     if ('default' in plugin && plugin.default) {
+      // eslint-disable-next-line new-cap
       return new plugin.default(options);
     }
 
diff --git a/packages/core/src/utils/try-require.ts b/packages/core/src/utils/try-require.ts
index 00c36985..0ab5e4e4 100644
--- a/packages/core/src/utils/try-require.ts
+++ b/packages/core/src/utils/try-require.ts
@@ -14,8 +14,6 @@ export default function tryRequire(tryPath: string) {
     try {
       // Require from __dirname. Needed for npx and global installs
       return require(tryPath);
-    } catch (error) {
-      return;
-    }
+    } catch (error) {}
   }
 }
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
index a9bb8ce0..8f54e6ac 100644
--- a/packages/core/tsconfig.json
+++ b/packages/core/tsconfig.json
@@ -1,7 +1,10 @@
 {
-  "compilerOptions": {
-    "outDir": "./dist"
-  },
   "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+  "include": ["src/**/*", "../../typings/**/*"],
+
+  "compilerOptions": {
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
+  }
 }
diff --git a/plugins/chrome/package.json b/plugins/chrome/package.json
index bd104023..3ea9b40f 100644
--- a/plugins/chrome/package.json
+++ b/plugins/chrome/package.json
@@ -31,9 +31,9 @@
     "web store"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/plugins/chrome/tsconfig.json b/plugins/chrome/tsconfig.json
index a9bb8ce0..bfbef6fc 100644
--- a/plugins/chrome/tsconfig.json
+++ b/plugins/chrome/tsconfig.json
@@ -1,7 +1,16 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../../packages/core"
+    }
+  ]
 }
diff --git a/plugins/conventional-commits/package.json b/plugins/conventional-commits/package.json
index e749a741..b5f85806 100644
--- a/plugins/conventional-commits/package.json
+++ b/plugins/conventional-commits/package.json
@@ -30,9 +30,9 @@
     "conventional commits"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/plugins/conventional-commits/tsconfig.json b/plugins/conventional-commits/tsconfig.json
index a9bb8ce0..bfbef6fc 100644
--- a/plugins/conventional-commits/tsconfig.json
+++ b/plugins/conventional-commits/tsconfig.json
@@ -1,7 +1,16 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../../packages/core"
+    }
+  ]
 }
diff --git a/plugins/git-tag/package.json b/plugins/git-tag/package.json
index b90208a5..5e94f4e1 100644
--- a/plugins/git-tag/package.json
+++ b/plugins/git-tag/package.json
@@ -29,9 +29,9 @@
     "changelog"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/plugins/git-tag/tsconfig.json b/plugins/git-tag/tsconfig.json
index a9bb8ce0..bfbef6fc 100644
--- a/plugins/git-tag/tsconfig.json
+++ b/plugins/git-tag/tsconfig.json
@@ -1,7 +1,16 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../../packages/core"
+    }
+  ]
 }
diff --git a/plugins/jira/package.json b/plugins/jira/package.json
index 737d1689..51021855 100644
--- a/plugins/jira/package.json
+++ b/plugins/jira/package.json
@@ -30,9 +30,9 @@
     "changelog"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/plugins/jira/tsconfig.json b/plugins/jira/tsconfig.json
index a9bb8ce0..bfbef6fc 100644
--- a/plugins/jira/tsconfig.json
+++ b/plugins/jira/tsconfig.json
@@ -1,7 +1,16 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../../packages/core"
+    }
+  ]
 }
diff --git a/plugins/maven/package.json b/plugins/maven/package.json
index 4897f756..b412b420 100644
--- a/plugins/maven/package.json
+++ b/plugins/maven/package.json
@@ -31,9 +31,9 @@
     "comment"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/plugins/maven/tsconfig.json b/plugins/maven/tsconfig.json
index a9bb8ce0..bfbef6fc 100644
--- a/plugins/maven/tsconfig.json
+++ b/plugins/maven/tsconfig.json
@@ -1,7 +1,16 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../../packages/core"
+    }
+  ]
 }
diff --git a/plugins/npm/package.json b/plugins/npm/package.json
index 1db63370..825719a2 100644
--- a/plugins/npm/package.json
+++ b/plugins/npm/package.json
@@ -31,9 +31,9 @@
     "lerna"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/plugins/npm/src/index.ts b/plugins/npm/src/index.ts
index f06f3e4b..23ce85f1 100644
--- a/plugins/npm/src/index.ts
+++ b/plugins/npm/src/index.ts
@@ -2,7 +2,7 @@ import envCi from 'env-ci';
 import * as fs from 'fs';
 import parseAuthor from 'parse-author';
 import path from 'path';
-import { Memoize } from 'typescript-memoize';
+import { Memoize as memoize } from 'typescript-memoize';
 
 import { Auto, execPromise, ILogger, IPlugin, SEMVER } from '@auto-it/core';
 import getPackages from 'get-monorepo-packages';
@@ -21,9 +21,7 @@ function isMonorepo() {
 async function getPublishedVersion(name: string) {
   try {
     return await execPromise('npm', ['view', name, 'version']);
-  } catch (error) {
-    return;
-  }
+  } catch (error) {}
 }
 
 export async function greaterRelease(
@@ -58,13 +56,23 @@ const inFolder = (parent: string, child: string) => {
   );
 };
 
-export async function changedPackages(
-  sha: string,
-  packages: IMonorepoPackage[],
-  lernaJson: { version?: string },
-  logger: ILogger,
-  version?: SEMVER
-) {
+interface ChangedPackagesArgs {
+  sha: string;
+  packages: IMonorepoPackage[];
+  lernaJson: {
+    version?: string;
+  };
+  logger: ILogger;
+  version?: SEMVER;
+}
+
+export async function changedPackages({
+  sha,
+  packages,
+  lernaJson,
+  logger,
+  version
+}: ChangedPackagesArgs) {
   const changed = new Set<string>();
   const changedFiles = await execPromise('git', [
     'show',
@@ -167,7 +175,7 @@ export default class NPMPlugin implements IPlugin {
       typeof config.forcePublish === 'boolean' ? config.forcePublish : true;
   }
 
-  @Memoize()
+  @memoize()
   async getLernaPackages() {
     return execPromise('npx', ['lerna', 'ls', '-pl']).then(res =>
       res.split('\n').map(packageInfo => {
@@ -177,7 +185,7 @@ export default class NPMPlugin implements IPlugin {
     );
   }
 
-  @Memoize()
+  @memoize()
   async getIndependentPackageList() {
     return this.getLernaPackages().then(packages =>
       packages
@@ -298,17 +306,17 @@ export default class NPMPlugin implements IPlugin {
           const lernaPackages = await this.getLernaPackages();
           const lernaJson = getLernaJson();
 
-          commit.packages = await changedPackages(
-            commit.hash,
-            lernaPackages,
+          const packages = await changedPackages({
+            sha: commit.hash,
+            packages: lernaPackages,
             lernaJson,
-            auto.logger,
+            logger: auto.logger,
             version
-          );
+          });
 
           const section =
-            commit.packages && commit.packages.length
-              ? commit.packages.map(p => `\`${p}\``).join(', ')
+            packages && packages.length
+              ? packages.map(p => `\`${p}\``).join(', ')
               : 'monorepo';
 
           if (section === 'monorepo') {
@@ -326,9 +334,9 @@ export default class NPMPlugin implements IPlugin {
       if (isMonorepo()) {
         auto.logger.verbose.info('Detected monorepo, using lerna');
         const isIndependent = getLernaJson().version === 'independent';
-        const monorepoBump = !isIndependent
-          ? await bumpLatest(getMonorepoPackage(), version)
-          : undefined;
+        const monorepoBump = isIndependent
+          ? undefined
+          : await bumpLatest(getMonorepoPackage(), version);
 
         await execPromise('npx', [
           'lerna',
diff --git a/plugins/npm/src/package-config.ts b/plugins/npm/src/package-config.ts
index d2403333..43c3b738 100644
--- a/plugins/npm/src/package-config.ts
+++ b/plugins/npm/src/package-config.ts
@@ -24,7 +24,7 @@ export default async function getConfigFromPackageJson(): Promise<IRepoConfig> {
   const { owner, name } =
     parseGitHubUrl(
       typeof repository === 'string' ? repository : repository.url
-    ) || ({} as any);
+    ) || {};
 
   if (!owner || !name) {
     throw new Error(
diff --git a/plugins/npm/src/set-npm-token.ts b/plugins/npm/src/set-npm-token.ts
index 6c60499a..39d67703 100644
--- a/plugins/npm/src/set-npm-token.ts
+++ b/plugins/npm/src/set-npm-token.ts
@@ -38,7 +38,7 @@ export default async function setTokenOnCI(logger: ILogger) {
   logger.verbose.note(`Using ${registry} registry for package`);
 
   const url = registry.replace(/^https?:/, ``);
-  // tslint:disable-next-line no-invalid-template-strings
+  // eslint-disable-next-line no-template-curly-in-string
   const authTokenString = urlJoin(url, ':_authToken=${NPM_TOKEN}');
 
   logger.verbose.info(`Will set authentication token string in ${rc}`);
diff --git a/plugins/npm/tsconfig.json b/plugins/npm/tsconfig.json
index a9bb8ce0..bfbef6fc 100644
--- a/plugins/npm/tsconfig.json
+++ b/plugins/npm/tsconfig.json
@@ -1,7 +1,16 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../../packages/core"
+    }
+  ]
 }
diff --git a/plugins/omit-commits/package.json b/plugins/omit-commits/package.json
index 1c27772b..9459f4a7 100644
--- a/plugins/omit-commits/package.json
+++ b/plugins/omit-commits/package.json
@@ -31,9 +31,9 @@
     "omit"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/plugins/omit-commits/tsconfig.json b/plugins/omit-commits/tsconfig.json
index a9bb8ce0..bfbef6fc 100644
--- a/plugins/omit-commits/tsconfig.json
+++ b/plugins/omit-commits/tsconfig.json
@@ -1,7 +1,16 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../../packages/core"
+    }
+  ]
 }
diff --git a/plugins/omit-release-notes/package.json b/plugins/omit-release-notes/package.json
index 6256d6cf..fb81a653 100644
--- a/plugins/omit-release-notes/package.json
+++ b/plugins/omit-release-notes/package.json
@@ -31,9 +31,9 @@
     "omit"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/plugins/omit-release-notes/tsconfig.json b/plugins/omit-release-notes/tsconfig.json
index a9bb8ce0..bfbef6fc 100644
--- a/plugins/omit-release-notes/tsconfig.json
+++ b/plugins/omit-release-notes/tsconfig.json
@@ -1,7 +1,16 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../../packages/core"
+    }
+  ]
 }
diff --git a/plugins/released/package.json b/plugins/released/package.json
index 686bb0e3..c3939bd6 100644
--- a/plugins/released/package.json
+++ b/plugins/released/package.json
@@ -31,9 +31,9 @@
     "comment"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/plugins/released/src/index.ts b/plugins/released/src/index.ts
index 4a98199b..9f396868 100644
--- a/plugins/released/src/index.ts
+++ b/plugins/released/src/index.ts
@@ -105,10 +105,10 @@ export default class ReleasedLabelPlugin implements IPlugin {
 
     const issues = messages
       .map(message => message.match(closeIssue))
-      .filter((r): r is string[] => !!r)
+      .filter((r): r is string[] => Boolean(r))
       .reduce((all, arr) => [...all, ...arr], [])
       .map(issue => issue.match(/#(\d+)/i))
-      .filter((r: RegExpMatchArray | null): r is RegExpMatchArray => !!r)
+      .filter((r: RegExpMatchArray | null): r is RegExpMatchArray => Boolean(r))
       .map(match => Number(match[1]));
 
     await Promise.all(
diff --git a/plugins/released/tsconfig.json b/plugins/released/tsconfig.json
index a9bb8ce0..bfbef6fc 100644
--- a/plugins/released/tsconfig.json
+++ b/plugins/released/tsconfig.json
@@ -1,7 +1,16 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../../packages/core"
+    }
+  ]
 }
diff --git a/plugins/slack/package.json b/plugins/slack/package.json
index 2f62145e..ac1dae46 100644
--- a/plugins/slack/package.json
+++ b/plugins/slack/package.json
@@ -30,9 +30,9 @@
     "slack"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/plugins/slack/src/index.ts b/plugins/slack/src/index.ts
index 2278e000..df92ec3c 100644
--- a/plugins/slack/src/index.ts
+++ b/plugins/slack/src/index.ts
@@ -96,7 +96,6 @@ export default class SlackPlugin implements IPlugin {
           `@${atTarget}: New release *<${releaseUrl}|${newVersion}>*`,
           body
         ].join('\n'),
-        // eslint-disable-next-line camelcase
         link_names: 1
       }),
       headers: { 'Content-Type': 'application/json' }
diff --git a/plugins/slack/tsconfig.json b/plugins/slack/tsconfig.json
index a9bb8ce0..bfbef6fc 100644
--- a/plugins/slack/tsconfig.json
+++ b/plugins/slack/tsconfig.json
@@ -1,7 +1,16 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../../packages/core"
+    }
+  ]
 }
diff --git a/plugins/twitter/package.json b/plugins/twitter/package.json
index d77057eb..a85997b0 100644
--- a/plugins/twitter/package.json
+++ b/plugins/twitter/package.json
@@ -31,9 +31,9 @@
     "twitter"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/plugins/twitter/src/index.ts b/plugins/twitter/src/index.ts
index 848c7b55..79109e19 100644
--- a/plugins/twitter/src/index.ts
+++ b/plugins/twitter/src/index.ts
@@ -33,14 +33,23 @@ const removeLastLine = (text: string) =>
     .join('\n')
     .trim();
 
-const makeTweet = (
-  releaseNotes: string,
-  message: string,
-  versionBump: ReleaseType,
-  newVersion: string,
-  repo: string,
-  url: string
-) => {
+interface MakeTweetArgs {
+  releaseNotes: string;
+  message: string;
+  versionBump: ReleaseType;
+  newVersion: string;
+  repo: string;
+  url: string;
+}
+
+const makeTweet = ({
+  releaseNotes,
+  message,
+  versionBump,
+  newVersion,
+  repo,
+  url
+}: MakeTweetArgs) => {
   const build = (notes: string) =>
     message
       .replace('%release', versionBump)
@@ -71,7 +80,7 @@ export default class TwitterPlugin implements IPlugin {
   name = 'Twitter';
 
   readonly options: ITwitterPluginOptions;
-  private readonly tweet: (message: string) => Promise<any>;
+  private readonly tweet: (message: string) => Promise<void>;
 
   constructor(options: Partial<ITwitterPluginOptions> = {}) {
     this.options = { ...defaults, ...options };
@@ -112,14 +121,14 @@ export default class TwitterPlugin implements IPlugin {
         }
 
         await this.tweet(
-          makeTweet(
+          makeTweet({
             releaseNotes,
-            this.options.message,
+            message: this.options.message,
             versionBump,
             newVersion,
-            auto.git.options.repo,
-            response.data.html_url
-          )
+            repo: auto.git.options.repo,
+            url: response.data.html_url
+          })
         );
       }
     );
diff --git a/plugins/twitter/tsconfig.json b/plugins/twitter/tsconfig.json
index a9bb8ce0..bfbef6fc 100644
--- a/plugins/twitter/tsconfig.json
+++ b/plugins/twitter/tsconfig.json
@@ -1,7 +1,16 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../../packages/core"
+    }
+  ]
 }
diff --git a/plugins/upload-assets/package.json b/plugins/upload-assets/package.json
index 8351ffa4..9f41d957 100644
--- a/plugins/upload-assets/package.json
+++ b/plugins/upload-assets/package.json
@@ -31,9 +31,9 @@
     "assets"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/plugins/upload-assets/tsconfig.json b/plugins/upload-assets/tsconfig.json
index a9bb8ce0..bfbef6fc 100644
--- a/plugins/upload-assets/tsconfig.json
+++ b/plugins/upload-assets/tsconfig.json
@@ -1,7 +1,16 @@
 {
+  "extends": "../../tsconfig.json",
+  "include": ["src/**/*", "../../typings/**/*"],
+
   "compilerOptions": {
-    "outDir": "./dist"
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "composite": true
   },
-  "extends": "../../tsconfig.json",
-  "include": ["src/**/*", "../../typings/**/*"]
+
+  "references": [
+    {
+      "path": "../../packages/core"
+    }
+  ]
 }
diff --git a/scripts/template-plugin/package.json b/scripts/template-plugin/package.json
index a61ffc63..30a1ef07 100644
--- a/scripts/template-plugin/package.json
+++ b/scripts/template-plugin/package.json
@@ -29,9 +29,9 @@
     "changelog"
   ],
   "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "build:watch": "npm run build -- -w",
-    "lint": "tslint -p . --format stylish",
+    "build": "tsc -b",
+    "start": "npm run build -- -w",
+    "lint": "eslint src --ext .ts",
     "test": "jest --maxWorkers=2 --config ../../package.json"
   },
   "dependencies": {
diff --git a/tsconfig.dev.json b/tsconfig.dev.json
new file mode 100644
index 00000000..44e5328a
--- /dev/null
+++ b/tsconfig.dev.json
@@ -0,0 +1,46 @@
+{
+  "files": [],
+  "include": [],
+
+  "references": [
+    {
+      "path": "packages/cli"
+    },
+    {
+      "path": "plugins/chrome"
+    },
+    {
+      "path": "plugins/conventional-commits"
+    },
+    {
+      "path": "plugins/git-tag"
+    },
+    {
+      "path": "plugins/jira"
+    },
+    {
+      "path": "plugins/maven"
+    },
+    {
+      "path": "plugins/npm"
+    },
+    {
+      "path": "plugins/omit-commits"
+    },
+    {
+      "path": "plugins/omit-release-notes"
+    },
+    {
+      "path": "plugins/released"
+    },
+    {
+      "path": "plugins/slack"
+    },
+    {
+      "path": "plugins/twitter"
+    },
+    {
+      "path": "plugins/upload-assets"
+    }
+  ]
+}
diff --git a/tsconfig.json b/tsconfig.json
index 5b1b235a..84fadf17 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -10,10 +10,6 @@
     "declaration": true,
     "lib": ["es6", "es2017", "es2017.object"],
     "esModuleInterop": true,
-    "experimentalDecorators": true,
-    "plugins": [
-      { "name": "typescript-tslint-plugin", "configFile": "./tslint.json" }
-    ],
-    "typeRoots": ["./node_modules/@types/"]
+    "experimentalDecorators": true
   }
 }
diff --git a/tslint.json b/tslint.json
deleted file mode 100644
index 3555251c..00000000
--- a/tslint.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
-  "defaultSeverity": "error",
-  "extends": [
-    "tslint:recommended",
-    "tslint-config-prettier",
-    "tslint-xo/space"
-  ],
-  "linterOptions": {
-    "exclude": ["**/*.json", "scripts/template-plugin/**/*"]
-  },
-  "jsRules": {},
-  "rules": {
-    // Just let prettier handle it
-    "align": false,
-    // Uppercase comments are dumb
-    "comment-format": false,
-    // Sometimes we done need the first few params
-    "no-unused": [true, "ignore-parameters"],
-    // I like spaces around my objects!
-    "object-curly-spacing": false,
-    // less characters on screen
-    "quotemark": [true, "single", "avoid-escape"],
-    // lots of overhead for little gain
-    "object-literal-sort-keys": false,
-    "max-classes-per-file": [true, 2, "exclude-class-expressions"],
-    // Allow ! character
-    "no-non-null-assertion": false,
-    "no-floating-promises": false,
-    "no-console": false,
-    "no-unsafe-any": false,
-    "no-object-literal-type-assertion": false,
-    "no-unnecessary-callback-wrapper": false,
-    "type-literal-delimiter": { "singleLine": "never" },
-    "typedef": false
-  },
-  "rulesDirectory": []
-}
diff --git a/yarn.lock b/yarn.lock
index 49ab48a0..418405de 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -971,25 +971,6 @@
     unique-filename "^1.1.1"
     which "^1.3.1"
 
-"@fimbul/bifrost@^0.17.0":
-  version "0.17.0"
-  resolved "https://registry.yarnpkg.com/@fimbul/bifrost/-/bifrost-0.17.0.tgz#f0383ba7e40992e3193dc87e2ddfde2ad62a9cf4"
-  integrity sha512-gVTkJAOef5HtN6LPmrtt5fAUmBywwlgmObsU3FBhPoNeXPLaIl2zywXkJEtvvVLQnaFmtff3x+wIj5lHRCDE3Q==
-  dependencies:
-    "@fimbul/ymir" "^0.17.0"
-    get-caller-file "^2.0.0"
-    tslib "^1.8.1"
-    tsutils "^3.5.0"
-
-"@fimbul/ymir@^0.17.0":
-  version "0.17.0"
-  resolved "https://registry.yarnpkg.com/@fimbul/ymir/-/ymir-0.17.0.tgz#4f28389b9f804d1cd202e11983af1743488b7815"
-  integrity sha512-xMXM9KTXRLHLVS6dnX1JhHNEkmWHcAVCQ/4+DA1KKwC/AFnGHzu/7QfQttEPgw3xplT+ILf9e3i64jrFwB3JtA==
-  dependencies:
-    inversify "^5.0.0"
-    reflect-metadata "^0.1.12"
-    tslib "^1.8.1"
-
 "@hapi/address@2.x.x":
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.0.0.tgz#9f05469c88cb2fd3dcd624776b54ee95c312126a"
@@ -2312,6 +2293,11 @@
   resolved "https://registry.yarnpkg.com/@types/env-ci/-/env-ci-3.1.0.tgz#8b8b388641c641fa4eb51862cbb018db013aeb68"
   integrity sha512-oJTxA/GmZavFFr6wtuFcmOUH4Lfj0RU9icnYBzp8gXDj7p8rCr5XRR2W6VzYOoCuEJusQsRUjSMr+3xOJ1S6yw==
 
+"@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/events@*":
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
@@ -2511,6 +2497,26 @@
   dependencies:
     "@types/yargs-parser" "*"
 
+"@typescript-eslint/eslint-plugin@^2.4.0":
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.4.0.tgz#aaf6b542ff75b78f4191a8bf1c519184817caa24"
+  integrity sha512-se/YCk7PUoyMwSm/u3Ii9E+BgDUc736uw/lXCDpXEqRgPGsoBTtS8Mntue/vZX8EGyzGplYuePBuVyhZDM9EpQ==
+  dependencies:
+    "@typescript-eslint/experimental-utils" "2.4.0"
+    eslint-utils "^1.4.2"
+    functional-red-black-tree "^1.0.1"
+    regexpp "^2.0.1"
+    tsutils "^3.17.1"
+
+"@typescript-eslint/experimental-utils@2.4.0":
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.4.0.tgz#dd8f3f466be25c3610a06fed22cfb6e6aa17f6d9"
+  integrity sha512-2cvhNaJoWavgTtnC7e1jUSPZQ7e4U2X9Yoy5sQmkS7lTESuyuZrlRcaoNuFfYEd6hgrmMU7+QoSp8Ad+kT1nfA==
+  dependencies:
+    "@types/json-schema" "^7.0.3"
+    "@typescript-eslint/typescript-estree" "2.4.0"
+    eslint-scope "^5.0.0"
+
 "@typescript-eslint/experimental-utils@^1.13.0":
   version "1.13.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz#b08c60d780c0067de2fb44b04b432f540138301e"
@@ -2520,6 +2526,16 @@
     "@typescript-eslint/typescript-estree" "1.13.0"
     eslint-scope "^4.0.0"
 
+"@typescript-eslint/parser@^2.4.0":
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.4.0.tgz#fe43ed5fec14af03d3594fce2c3b7ec4c8df0243"
+  integrity sha512-IouAKi/grJ4MFrwdXIJ1GHAwbPWYgkT3b/x8Q49F378c9nwgxVkO76e0rZeUVpwHMaUuoKG2sUeK0XGkwdlwkw==
+  dependencies:
+    "@types/eslint-visitor-keys" "^1.0.0"
+    "@typescript-eslint/experimental-utils" "2.4.0"
+    "@typescript-eslint/typescript-estree" "2.4.0"
+    eslint-visitor-keys "^1.1.0"
+
 "@typescript-eslint/typescript-estree@1.13.0":
   version "1.13.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e"
@@ -2528,6 +2544,17 @@
     lodash.unescape "4.0.1"
     semver "5.5.0"
 
+"@typescript-eslint/typescript-estree@2.4.0":
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.4.0.tgz#722c95493e1b7682893edaaaec0e69f36917feef"
+  integrity sha512-/DzDAtMqF5d9IlXrrvu/Id/uoKjnSxf/3FbtKK679a/T7lbDM8qQuirtGvFy6Uh+x0hALuCMwnMfUf0P24/+Iw==
+  dependencies:
+    chokidar "^3.0.2"
+    glob "^7.1.4"
+    is-glob "^4.0.1"
+    lodash.unescape "4.0.1"
+    semver "^6.3.0"
+
 "@webassemblyjs/ast@1.8.5":
   version "1.8.5"
   resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
@@ -2769,6 +2796,11 @@ acorn-globals@^4.1.0:
     acorn "^6.0.1"
     acorn-walk "^6.0.1"
 
+acorn-jsx@^5.0.2:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384"
+  integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==
+
 acorn-walk@^6.0.1:
   version "6.1.1"
   resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913"
@@ -2794,6 +2826,11 @@ acorn@^6.0.7, acorn@^6.2.0:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.1.tgz#3ed8422d6dec09e6121cc7a843ca86a330a86b51"
   integrity sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==
 
+acorn@^7.0.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c"
+  integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==
+
 agent-base@4, agent-base@^4.1.0, agent-base@^4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
@@ -2843,6 +2880,16 @@ ajv@^6.1.0, ajv@^6.5.5:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
+ajv@^6.10.0, ajv@^6.10.2:
+  version "6.10.2"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
+  integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
+  dependencies:
+    fast-deep-equal "^2.0.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
+
 all-contributors-cli@^6.4.0:
   version "6.9.1"
   resolved "https://registry.yarnpkg.com/all-contributors-cli/-/all-contributors-cli-6.9.1.tgz#29f0867c6215a1691b25e83c23bc16f30f83f31a"
@@ -2931,6 +2978,14 @@ anymatch@^2.0.0:
     micromatch "^3.1.4"
     normalize-path "^2.1.1"
 
+anymatch@~3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
+  integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
+  dependencies:
+    normalize-path "^3.0.0"
+    picomatch "^2.0.4"
+
 app-root-path@^2.0.1:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.1.0.tgz#98bf6599327ecea199309866e8140368fd2e646a"
@@ -3405,6 +3460,11 @@ binary-extensions@^1.0.0:
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
   integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
 
+binary-extensions@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
+  integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
+
 bl@^1.0.0:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
@@ -3513,7 +3573,7 @@ braces@^2.3.1, braces@^2.3.2:
     split-string "^3.0.2"
     to-regex "^3.0.1"
 
-braces@^3.0.1:
+braces@^3.0.1, braces@~3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
   integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
@@ -3679,11 +3739,6 @@ buffer@^5.2.0, buffer@^5.2.1:
     base64-js "^1.0.2"
     ieee754 "^1.1.4"
 
-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=
-
 builtin-status-codes@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@@ -4016,6 +4071,21 @@ chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.0.4:
   optionalDependencies:
     fsevents "^1.2.7"
 
+chokidar@^3.0.2:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.2.tgz#a433973350021e09f2b853a2287781022c0dc935"
+  integrity sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg==
+  dependencies:
+    anymatch "~3.1.1"
+    braces "~3.0.2"
+    glob-parent "~5.1.0"
+    is-binary-path "~2.1.0"
+    is-glob "~4.0.1"
+    normalize-path "~3.0.0"
+    readdirp "~3.2.0"
+  optionalDependencies:
+    fsevents "~2.1.1"
+
 chownr@^1.0.1, chownr@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494"
@@ -4276,7 +4346,7 @@ commander@2.17.x, commander@~2.17.1:
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
   integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
 
-commander@^2.11.0, commander@^2.12.1, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.8.1, commander@^2.9.0, commander@~2.20.0:
+commander@^2.11.0, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.8.1, commander@^2.9.0, commander@~2.20.0:
   version "2.20.1"
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.1.tgz#3863ce3ca92d0831dcf2a102f5fb4b5926afd0f9"
   integrity sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==
@@ -5282,11 +5352,6 @@ diff-sequences@^24.9.0:
   resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
   integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
 
-diff@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
-  integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
-
 diffie-hellman@^5.0.0:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@@ -5318,14 +5383,6 @@ dir-glob@^3.0.1:
   dependencies:
     path-type "^4.0.0"
 
-doctrine@0.7.2:
-  version "0.7.2"
-  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-0.7.2.tgz#7cb860359ba3be90e040b26b729ce4bfa654c523"
-  integrity sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=
-  dependencies:
-    esutils "^1.1.6"
-    isarray "0.0.1"
-
 doctrine@1.5.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@@ -5341,6 +5398,13 @@ doctrine@^2.1.0:
   dependencies:
     esutils "^2.0.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-converter@^0.2:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
@@ -5758,6 +5822,18 @@ eslint-config-prettier@^6.0.0:
   dependencies:
     get-stdin "^6.0.0"
 
+eslint-config-prettier@^6.4.0:
+  version "6.4.0"
+  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.4.0.tgz#0a04f147e31d33c6c161b2dd0971418ac52d0477"
+  integrity sha512-YrKucoFdc7SEko5Sxe4r6ixqXPDP1tunGw91POeZTTRKItf/AMFYt/YLEQtZMkR2LVpAVhcAcZgcWpm1oGPW7w==
+  dependencies:
+    get-stdin "^6.0.0"
+
+eslint-config-xo@^0.27.1:
+  version "0.27.1"
+  resolved "https://registry.yarnpkg.com/eslint-config-xo/-/eslint-config-xo-0.27.1.tgz#e302231d4c054e855865c0f7c700ade23f506552"
+  integrity sha512-os4vvQCSZb7WdUMar52z97c2q6j29oIDj/Kb9HeOZ/jW7gqx3JjUQdLm+nq9hHpwgbS0vUx+Hm4HD7lVUltBPw==
+
 eslint-import-resolver-node@^0.3.2:
   version "0.3.2"
   resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a"
@@ -5797,7 +5873,7 @@ eslint-plugin-babel@^5.3.0:
   dependencies:
     eslint-rule-composer "^0.3.0"
 
-eslint-plugin-import@^2.17.1:
+eslint-plugin-import@^2.17.1, eslint-plugin-import@^2.18.2:
   version "2.18.2"
   resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6"
   integrity sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==
@@ -5814,6 +5890,13 @@ eslint-plugin-import@^2.17.1:
     read-pkg-up "^2.0.0"
     resolve "^1.11.0"
 
+eslint-plugin-jest@^22.19.0:
+  version "22.19.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.19.0.tgz#0cf90946a8c927d40a2c64458c89bb635d0f2a0b"
+  integrity sha512-4zUc3rh36ds0SXdl2LywT4YWA3zRe8sfLhz8bPp8qQPIKvynTTkNGwmSCMpl5d9QiZE2JxSinGF+WD8yU+O0Lg==
+  dependencies:
+    "@typescript-eslint/experimental-utils" "^1.13.0"
+
 eslint-plugin-jest@^22.4.1:
   version "22.13.6"
   resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.13.6.tgz#85630e7709fe1ecbe0099956590af32d5e157448"
@@ -5836,6 +5919,13 @@ eslint-plugin-jsx-a11y@^6.2.1:
     has "^1.0.3"
     jsx-ast-utils "^2.2.1"
 
+eslint-plugin-prettier@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.1.tgz#507b8562410d02a03f0ddc949c616f877852f2ba"
+  integrity sha512-A+TZuHZ0KU0cnn56/9mfR7/KjUJ9QNVXUhwvRFSR7PGPe0zQR6PTkmyqg1AtUUEOzTqeRsUwyKFh0oVZKVCrtA==
+  dependencies:
+    prettier-linter-helpers "^1.0.0"
+
 eslint-plugin-react-hooks@^1.6.0:
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.6.1.tgz#3c66a5515ea3e0a221ffc5d4e75c971c217b1a4c"
@@ -5877,11 +5967,83 @@ eslint-scope@^4.0.0:
     esrecurse "^4.1.0"
     estraverse "^4.1.1"
 
+eslint-scope@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
+  integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
+  dependencies:
+    esrecurse "^4.1.0"
+    estraverse "^4.1.1"
+
+eslint-utils@^1.4.2:
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab"
+  integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==
+  dependencies:
+    eslint-visitor-keys "^1.0.0"
+
 eslint-visitor-keys@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
   integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==
 
+eslint-visitor-keys@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
+  integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
+
+eslint@^6.5.1:
+  version "6.5.1"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.5.1.tgz#828e4c469697d43bb586144be152198b91e96ed6"
+  integrity sha512-32h99BoLYStT1iq1v2P9uwpyznQ4M2jRiFB6acitKz52Gqn+vPaMDUTB1bYi1WN4Nquj2w+t+bimYUG83DC55A==
+  dependencies:
+    "@babel/code-frame" "^7.0.0"
+    ajv "^6.10.0"
+    chalk "^2.1.0"
+    cross-spawn "^6.0.5"
+    debug "^4.0.1"
+    doctrine "^3.0.0"
+    eslint-scope "^5.0.0"
+    eslint-utils "^1.4.2"
+    eslint-visitor-keys "^1.1.0"
+    espree "^6.1.1"
+    esquery "^1.0.1"
+    esutils "^2.0.2"
+    file-entry-cache "^5.0.1"
+    functional-red-black-tree "^1.0.1"
+    glob-parent "^5.0.0"
+    globals "^11.7.0"
+    ignore "^4.0.6"
+    import-fresh "^3.0.0"
+    imurmurhash "^0.1.4"
+    inquirer "^6.4.1"
+    is-glob "^4.0.0"
+    js-yaml "^3.13.1"
+    json-stable-stringify-without-jsonify "^1.0.1"
+    levn "^0.3.0"
+    lodash "^4.17.14"
+    minimatch "^3.0.4"
+    mkdirp "^0.5.1"
+    natural-compare "^1.4.0"
+    optionator "^0.8.2"
+    progress "^2.0.0"
+    regexpp "^2.0.1"
+    semver "^6.1.2"
+    strip-ansi "^5.2.0"
+    strip-json-comments "^3.0.1"
+    table "^5.2.3"
+    text-table "^0.2.0"
+    v8-compile-cache "^2.0.3"
+
+espree@^6.1.1:
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.1.tgz#7f80e5f7257fc47db450022d723e356daeb1e5de"
+  integrity sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==
+  dependencies:
+    acorn "^7.0.0"
+    acorn-jsx "^5.0.2"
+    eslint-visitor-keys "^1.1.0"
+
 esprima@^2.6.0:
   version "2.7.3"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
@@ -5897,6 +6059,13 @@ esprima@^4.0.0:
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
   integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
 
+esquery@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708"
+  integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==
+  dependencies:
+    estraverse "^4.0.0"
+
 esrecurse@^4.1.0:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
@@ -5904,16 +6073,16 @@ esrecurse@^4.1.0:
   dependencies:
     estraverse "^4.1.0"
 
+estraverse@^4.0.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
+  integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
+
 estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
   integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
 
-esutils@^1.1.6:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/esutils/-/esutils-1.1.6.tgz#c01ccaa9ae4b897c6d0c3e210ae52f3c7a844375"
-  integrity sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=
-
 esutils@^2.0.0, esutils@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
@@ -6196,6 +6365,11 @@ fast-deep-equal@^2.0.1:
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
   integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
 
+fast-diff@^1.1.2:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
+  integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
+
 fast-glob@^2.0.2:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.4.tgz#e54f4b66d378040e0e4d6a68ec36bbc5b04363c0"
@@ -6297,6 +6471,13 @@ figures@^2.0.0:
   dependencies:
     escape-string-regexp "^1.0.5"
 
+file-entry-cache@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c"
+  integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==
+  dependencies:
+    flat-cache "^2.0.1"
+
 file-loader@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-2.0.0.tgz#39749c82f020b9e85901dcff98e8004e6401cfde"
@@ -6484,6 +6665,20 @@ find-versions@^3.0.0:
     array-uniq "^2.1.0"
     semver-regex "^2.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"
+
+flatted@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
+  integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==
+
 flow-bin@^0.50.0:
   version "0.50.0"
   resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.50.0.tgz#d4cdb2430dee1a3599f0eb6fe551146e3027256a"
@@ -6624,11 +6819,21 @@ fsevents@^1.2.7:
     nan "^2.12.1"
     node-pre-gyp "^0.12.0"
 
+fsevents@~2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.1.tgz#74c64e21df71721845d0c44fe54b7f56b82995a9"
+  integrity sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw==
+
 function-bind@^1.0.2, function-bind@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
   integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
 
+functional-red-black-tree@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
+  integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
+
 gauge@~2.7.3:
   version "2.7.4"
   resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
@@ -6662,12 +6867,12 @@ genfun@^5.0.0:
   resolved "https://registry.yarnpkg.com/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537"
   integrity sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==
 
-get-caller-file@^1.0.1, get-caller-file@^1.0.2:
+get-caller-file@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
   integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
 
-get-caller-file@^2.0.0, get-caller-file@^2.0.1:
+get-caller-file@^2.0.1:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
   integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
@@ -6876,6 +7081,13 @@ glob-parent@^5.0.0:
   dependencies:
     is-glob "^4.0.1"
 
+glob-parent@~5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2"
+  integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==
+  dependencies:
+    is-glob "^4.0.1"
+
 glob-to-regexp@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
@@ -6908,7 +7120,7 @@ global@~4.3.0:
     min-document "^2.19.0"
     process "~0.5.1"
 
-globals@^11.1.0:
+globals@^11.1.0, globals@^11.7.0:
   version "11.12.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
   integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
@@ -7587,7 +7799,7 @@ ignore@^3.3.5:
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
   integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==
 
-ignore@^4.0.3:
+ignore@^4.0.3, ignore@^4.0.6:
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
   integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
@@ -7700,6 +7912,14 @@ import-fresh@^2.0.0:
     caller-path "^2.0.0"
     resolve-from "^3.0.0"
 
+import-fresh@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.1.0.tgz#6d33fa1dcef6df930fae003446f33415af905118"
+  integrity sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==
+  dependencies:
+    parent-module "^1.0.0"
+    resolve-from "^4.0.0"
+
 import-from@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1"
@@ -7828,6 +8048,25 @@ inquirer@^6.2.0, inquirer@^6.2.1:
     strip-ansi "^5.1.0"
     through "^2.3.6"
 
+inquirer@^6.4.1:
+  version "6.5.2"
+  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca"
+  integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==
+  dependencies:
+    ansi-escapes "^3.2.0"
+    chalk "^2.4.2"
+    cli-cursor "^2.1.0"
+    cli-width "^2.0.0"
+    external-editor "^3.0.3"
+    figures "^2.0.0"
+    lodash "^4.17.12"
+    mute-stream "0.0.7"
+    run-async "^2.2.0"
+    rxjs "^6.4.0"
+    string-width "^2.1.0"
+    strip-ansi "^5.1.0"
+    through "^2.3.6"
+
 interpret@^1.0.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
@@ -7856,11 +8095,6 @@ invariant@^2.2.2, invariant@^2.2.4:
   dependencies:
     loose-envify "^1.0.0"
 
-inversify@^5.0.0:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/inversify/-/inversify-5.0.1.tgz#500d709b1434896ce5a0d58915c4a4210e34fb6e"
-  integrity sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==
-
 invert-kv@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
@@ -7927,6 +8161,13 @@ is-binary-path@^1.0.0:
   dependencies:
     binary-extensions "^1.0.0"
 
+is-binary-path@~2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+  integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+  dependencies:
+    binary-extensions "^2.0.0"
+
 is-buffer@^1.1.5:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
@@ -8077,7 +8318,7 @@ is-glob@^3.1.0:
   dependencies:
     is-extglob "^2.1.0"
 
-is-glob@^4.0.0, 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==
@@ -8309,11 +8550,6 @@ is-wsl@^1.1.0:
   resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
   integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
 
-isarray@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
-  integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
-
 isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -8948,6 +9184,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, 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"
@@ -9190,7 +9431,7 @@ leven@^3.1.0:
   resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
   integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
 
-levn@~0.3.0:
+levn@^0.3.0, levn@~0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
   integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=
@@ -9461,7 +9702,7 @@ lodash.uniq@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-lodash@^4.0.1, lodash@^4.11.2, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1:
+lodash@^4.0.1, lodash@^4.11.2, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1:
   version "4.17.15"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
   integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@@ -10174,14 +10415,6 @@ mkdirp@*, mkdirp@0.5.1, mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0,
   dependencies:
     minimist "0.0.8"
 
-mock-require@^3.0.2:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/mock-require/-/mock-require-3.0.3.tgz#ccd544d9eae81dd576b3f219f69ec867318a1946"
-  integrity sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==
-  dependencies:
-    get-caller-file "^1.0.2"
-    normalize-path "^2.1.1"
-
 modify-values@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
@@ -10538,7 +10771,7 @@ normalize-path@^2.1.1:
   dependencies:
     remove-trailing-separator "^1.0.1"
 
-normalize-path@^3.0.0:
+normalize-path@^3.0.0, normalize-path@~3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
   integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
@@ -10849,7 +11082,7 @@ optimist@^0.6.1:
     minimist "~0.0.1"
     wordwrap "~0.0.2"
 
-optionator@^0.8.1:
+optionator@^0.8.1, optionator@^0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
   integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=
@@ -11142,6 +11375,13 @@ param-case@2.1.x, param-case@^2.1.0:
   dependencies:
     no-case "^2.2.0"
 
+parent-module@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+  integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+  dependencies:
+    callsites "^3.0.0"
+
 parse-asn1@^5.0.0:
   version "5.1.4"
   resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc"
@@ -11413,7 +11653,7 @@ phin@^2.9.1:
   resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.3.tgz#f9b6ac10a035636fb65dfc576aaaa17b8743125c"
   integrity sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==
 
-picomatch@^2.0.5:
+picomatch@^2.0.4, picomatch@^2.0.5:
   version "2.0.7"
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6"
   integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==
@@ -11935,6 +12175,13 @@ prepend-http@^2.0.0:
   resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
   integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
 
+prettier-linter-helpers@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
+  integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
+  dependencies:
+    fast-diff "^1.1.2"
+
 prettier@^1.16.4:
   version "1.18.2"
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
@@ -12500,6 +12747,13 @@ readdirp@^2.0.0, readdirp@^2.2.1:
     micromatch "^3.1.10"
     readable-stream "^2.0.2"
 
+readdirp@~3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839"
+  integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==
+  dependencies:
+    picomatch "^2.0.4"
+
 realpath-native@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
@@ -12535,11 +12789,6 @@ 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==
 
-reflect-metadata@^0.1.12:
-  version "0.1.13"
-  resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
-  integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
-
 regenerate-unicode-properties@^8.0.2:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e"
@@ -12577,6 +12826,11 @@ regex-not@^1.0.0, regex-not@^1.0.2:
     extend-shallow "^3.0.2"
     safe-regex "^1.1.0"
 
+regexpp@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
+  integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==
+
 regexpu-core@^4.5.4:
   version "4.5.4"
   resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.4.tgz#080d9d02289aa87fe1667a4f5136bc98a6aebaae"
@@ -12892,7 +13146,7 @@ rgba-regex@^1.0.0:
   resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
   integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
 
-rimraf@2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
+rimraf@2, rimraf@2.6.3, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
   version "2.6.3"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
   integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
@@ -13062,7 +13316,7 @@ semver@5.5.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
   integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==
 
-semver@^6.0.0, semver@^6.1.1, semver@^6.2.0:
+semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
@@ -13251,6 +13505,15 @@ slice-ansi@0.0.4:
   resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
   integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=
 
+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"
+
 slide@^1.1.6:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
@@ -13717,6 +13980,11 @@ strip-indent@^2.0.0:
   resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
   integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=
 
+strip-json-comments@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
+  integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
+
 strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
@@ -13827,6 +14095,16 @@ table-layout@^1.0.0:
     typical "^5.0.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"
+
 tapable@^0.1.8:
   version "0.1.10"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4"
@@ -14216,92 +14494,15 @@ ts-jest@^24.0.2:
     semver "^5.5"
     yargs-parser "10.x"
 
-tslib@1.9.0:
-  version "1.9.0"
-  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8"
-  integrity sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==
-
-tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
+tslib@^1.8.1, tslib@^1.9.0:
   version "1.9.3"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
   integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
 
-tslint-config-prettier@~1.18.0:
-  version "1.18.0"
-  resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz#75f140bde947d35d8f0d238e0ebf809d64592c37"
-  integrity sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==
-
-tslint-consistent-codestyle@^1.15.1:
-  version "1.15.1"
-  resolved "https://registry.yarnpkg.com/tslint-consistent-codestyle/-/tslint-consistent-codestyle-1.15.1.tgz#a0c5cd5a5860d40b659c490d8013c5732e02af8c"
-  integrity sha512-38Y3Dz4zcABe/PlPAQSGNEWPGVq0OzcIQR7SEU6dNujp/SgvhxhJOhIhI9gY4r0I3/TNtvVQwARWor9O9LPZWg==
-  dependencies:
-    "@fimbul/bifrost" "^0.17.0"
-    tslib "^1.7.1"
-    tsutils "^2.29.0"
-
-tslint-eslint-rules@^5.4.0:
-  version "5.4.0"
-  resolved "https://registry.yarnpkg.com/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz#e488cc9181bf193fe5cd7bfca213a7695f1737b5"
-  integrity sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==
-  dependencies:
-    doctrine "0.7.2"
-    tslib "1.9.0"
-    tsutils "^3.0.0"
-
-tslint-microsoft-contrib@^6.1.0:
-  version "6.1.1"
-  resolved "https://registry.yarnpkg.com/tslint-microsoft-contrib/-/tslint-microsoft-contrib-6.1.1.tgz#1de9b5c2867f6cec762bab9d8e1619f2b8eb59fc"
-  integrity sha512-u6tK+tgt8Z1YRJxe4kpWWEx/6FTFxdga50+osnANifsfC7BMzh8c/t/XbNntTRiwMfpHkQ9xtUjizCDLxN1Yeg==
-  dependencies:
-    tsutils "^2.27.2 <2.29.0"
-
-tslint-xo@~0.16.0:
-  version "0.16.0"
-  resolved "https://registry.yarnpkg.com/tslint-xo/-/tslint-xo-0.16.0.tgz#335cf2eeff9d4a600aeccc6e22476a19617c4c52"
-  integrity sha512-n0kE/kpPJqh3zzKheP5kYrsGT2axFfP4ppXsxYXZbqtGJivdaL40rKukkW1/16R67WgjlcoyTxkHh+zLikkawg==
-  dependencies:
-    tslint-consistent-codestyle "^1.15.1"
-    tslint-eslint-rules "^5.4.0"
-    tslint-microsoft-contrib "^6.1.0"
-
-tslint@~5.20.0:
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.0.tgz#fac93bfa79568a5a24e7be9cdde5e02b02d00ec1"
-  integrity sha512-2vqIvkMHbnx8acMogAERQ/IuINOq6DFqgF8/VDvhEkBqQh/x6SP0Y+OHnKth9/ZcHQSroOZwUQSN18v8KKF0/g==
-  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.27.2 <2.29.0":
-  version "2.28.0"
-  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.28.0.tgz#6bd71e160828f9d019b6f4e844742228f85169a1"
-  integrity sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==
-  dependencies:
-    tslib "^1.8.1"
-
-tsutils@^2.29.0:
-  version "2.29.0"
-  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
-  integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==
-  dependencies:
-    tslib "^1.8.1"
-
-tsutils@^3.0.0, tsutils@^3.5.0:
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.7.0.tgz#f97bdd2f109070bd1865467183e015b25734b477"
-  integrity sha512-n+e+3q7Jx2kfZw7tjfI9axEIWBY0sFMOlC+1K70X0SeXpO/UYSB+PN+E9tIJNqViB7oiXQdqD7dNchnvoneZew==
+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"
 
@@ -14390,19 +14591,10 @@ typescript-memoize@^1.0.0-alpha.3:
   dependencies:
     core-js "2.4.1"
 
-typescript-tslint-plugin@^0.5.4:
-  version "0.5.4"
-  resolved "https://registry.yarnpkg.com/typescript-tslint-plugin/-/typescript-tslint-plugin-0.5.4.tgz#febe0cca1fe65133f4f688841eba68262a1d55a5"
-  integrity sha512-CQEfGC+p0SoBARI4N2LrGsWJsp4/OE+uKZ68xsWYKHWqMFq4DFQHqOVlK0deEricSN01NmDTqjap63Pw/DHieg==
-  dependencies:
-    minimatch "^3.0.4"
-    mock-require "^3.0.2"
-    vscode-languageserver "^5.1.0"
-
 typescript@~3.6.2:
-  version "3.6.3"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da"
-  integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==
+  version "3.6.4"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d"
+  integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==
 
 typical@^4.0.0:
   version "4.0.0"
@@ -14767,6 +14959,11 @@ v8-compile-cache@^2.0.0:
   resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
   integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
 
+v8-compile-cache@^2.0.3:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
+  integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==
+
 validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
@@ -14813,37 +15010,6 @@ vm-browserify@^1.0.1:
   resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019"
   integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==
 
-vscode-jsonrpc@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz#a7bf74ef3254d0a0c272fab15c82128e378b3be9"
-  integrity sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg==
-
-vscode-languageserver-protocol@3.14.1:
-  version "3.14.1"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.14.1.tgz#b8aab6afae2849c84a8983d39a1cf742417afe2f"
-  integrity sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g==
-  dependencies:
-    vscode-jsonrpc "^4.0.0"
-    vscode-languageserver-types "3.14.0"
-
-vscode-languageserver-types@3.14.0:
-  version "3.14.0"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz#d3b5952246d30e5241592b6dde8280e03942e743"
-  integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==
-
-vscode-languageserver@^5.1.0:
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-5.2.1.tgz#0d2feddd33f92aadf5da32450df498d52f6f14eb"
-  integrity sha512-GuayqdKZqAwwaCUjDvMTAVRPJOp/SLON3mJ07eGsx/Iq9HjRymhKWztX41rISqDKhHVVyFM+IywICyZDla6U3A==
-  dependencies:
-    vscode-languageserver-protocol "3.14.1"
-    vscode-uri "^1.0.6"
-
-vscode-uri@^1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.6.tgz#6b8f141b0bbc44ad7b07e94f82f168ac7608ad4d"
-  integrity sha512-sLI2L0uGov3wKVb9EB+vIQBl9tVP90nqRvxSoJ35vI3NjxE8jfsE5DSOhWgSunHSZmKS4OCi2jrtfxK7uyp2ww==
-
 w3c-hr-time@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045"
@@ -15221,6 +15387,13 @@ write-pkg@^3.1.0:
     sort-keys "^2.0.0"
     write-json-file "^2.2.0"
 
+write@1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
+  integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==
+  dependencies:
+    mkdirp "^0.5.1"
+
 ws@^4.0.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/ws/-/ws-4.1.0.tgz#a979b5d7d4da68bf54efe0408967c324869a7289"


Test patch:
diff --git a/packages/cli/__tests__/args.test.ts b/packages/cli/__tests__/args.test.ts
index 7d5929f6..1076f121 100644
--- a/packages/cli/__tests__/args.test.ts
+++ b/packages/cli/__tests__/args.test.ts
@@ -1,36 +1,32 @@
 import parseArgs from '../src/parse-args';
 
+const log = jest.fn();
+// @ts-ignore
+global.console = { log };
+
 describe('root parser', () => {
-  test('should print version', () => {
-    console.log = jest.fn();
+  beforeEach(() => {
+    log.mockClear();
+  });
 
+  test('should print version', () => {
     parseArgs('--version'.split(' '));
-
-    expect(console.log).toHaveBeenCalled();
+    expect(log).toHaveBeenCalled();
   });
 
   test('should print help', () => {
-    console.log = jest.fn();
-
     parseArgs('--help'.split(' '));
-
-    expect(console.log).toHaveBeenCalled();
+    expect(log).toHaveBeenCalled();
   });
 
   test('should print help for simple command', () => {
-    console.log = jest.fn();
-
     parseArgs('init --help'.split(' '));
-
-    expect(console.log).toHaveBeenCalled();
+    expect(log).toHaveBeenCalled();
   });
 
   test('should print help for complex command', () => {
-    console.log = jest.fn();
-
     parseArgs('pr --help'.split(' '));
-
-    expect(console.log).toHaveBeenCalled();
+    expect(log).toHaveBeenCalled();
   });
 
   test('should exit when required arg is not included', () => {
@@ -40,14 +36,13 @@ describe('root parser', () => {
   });
 
   test('should exit when required string is provided as flag', () => {
-    console.log = jest.fn() as any;
     process.exit = jest.fn() as any;
     parseArgs(['pr-check', '--pr', '24', '--url']);
     expect(process.exit).toHaveBeenCalled();
   });
 
   test('should parse just provided args', () => {
-    expect(parseArgs('label --pr 2 --owner adam'.split(' '))).toEqual([
+    expect(parseArgs('label --pr 2 --owner adam'.split(' '))).toStrictEqual([
       'label',
       {
         pr: 2,
@@ -57,7 +52,7 @@ describe('root parser', () => {
   });
 
   test('should parse args as camelCase', () => {
-    expect(parseArgs('changelog -d'.split(' '))).toEqual([
+    expect(parseArgs('changelog -d'.split(' '))).toStrictEqual([
       'changelog',
       {
         dryRun: true
@@ -72,7 +67,7 @@ describe('root parser', () => {
   });
 
   test('allow array of options to or', () => {
-    expect(parseArgs(['comment', '--message', 'foo'])).toEqual([
+    expect(parseArgs(['comment', '--message', 'foo'])).toStrictEqual([
       'comment',
       {
         message: 'foo'
@@ -81,7 +76,7 @@ describe('root parser', () => {
   });
 
   test('allow edit in comment', () => {
-    expect(parseArgs(['comment', '--edit', '--message', 'foo'])).toEqual([
+    expect(parseArgs(['comment', '--edit', '--message', 'foo'])).toStrictEqual([
       'comment',
       {
         edit: true,
diff --git a/packages/cli/__tests__/main.test.ts b/packages/cli/__tests__/main.test.ts
index 1d3b3ad1..882abf3e 100644
--- a/packages/cli/__tests__/main.test.ts
+++ b/packages/cli/__tests__/main.test.ts
@@ -1,13 +1,9 @@
 import main, { run } from '../src/run';
 
 test('throws error for unknown args', async () => {
-  expect.assertions(1);
-
-  try {
-    await run('foo', {});
-  } catch (error) {
-    expect(error).toEqual(new Error("idk what i'm doing."));
-  }
+  await expect(run('foo', {})).rejects.toStrictEqual(
+    new Error("idk what i'm doing.")
+  );
 });
 
 test('throws exits for caught error', async () => {
diff --git a/packages/core/src/__tests__/auto-canary-in-pr-ci.test.ts b/packages/core/src/__tests__/auto-canary-in-pr-ci.test.ts
index a821a076..86d08791 100644
--- a/packages/core/src/__tests__/auto-canary-in-pr-ci.test.ts
+++ b/packages/core/src/__tests__/auto-canary-in-pr-ci.test.ts
@@ -28,7 +28,7 @@ describe('canary in ci', () => {
     auto.hooks.canary.tap('test', canary);
     const addToPrBody = jest.fn();
     auto.git!.addToPrBody = addToPrBody;
-    auto.release!.getCommits = jest.fn();
+    jest.spyOn(auto.release!, 'getCommits').mockImplementation();
 
     await auto.canary();
     expect(canary).toHaveBeenCalledWith(SEMVER.patch, '.123.1');
@@ -43,7 +43,7 @@ describe('canary in ci', () => {
       Promise.resolve([makeCommitFromMsg('Test Commit')]);
     const addToPrBody = jest.fn();
     auto.git!.addToPrBody = addToPrBody;
-    auto.release!.getCommits = jest.fn();
+    jest.spyOn(auto.release!, 'getCommits').mockImplementation();
     auto.hooks.canary.tap('test', () => '1.2.4-canary.123.1');
 
     const version = await auto.canary({ pr: 123, build: 1 });
@@ -60,7 +60,7 @@ describe('canary in ci', () => {
       Promise.resolve([makeCommitFromMsg('Test Commit')]);
     const addToPrBody = jest.fn();
     auto.git!.addToPrBody = addToPrBody;
-    auto.release!.getCommits = jest.fn();
+    jest.spyOn(auto.release!, 'getCommits').mockImplementation();
 
     await auto.canary({ pr: 123, build: 1, message: 'false' });
     expect(addToPrBody).not.toHaveBeenCalled();
@@ -74,7 +74,7 @@ describe('canary in ci', () => {
       Promise.resolve([makeCommitFromMsg('Test Commit')]);
     const addToPrBody = jest.fn();
     auto.git!.addToPrBody = addToPrBody;
-    auto.release!.getCommits = jest.fn();
+    jest.spyOn(auto.release!, 'getCommits').mockImplementation();
     auto.hooks.canary.tap('test', (bump, post) => `1.2.4-canary${post}`);
 
     const version = await auto.canary({ pr: 456, build: 5 });
@@ -89,7 +89,7 @@ describe('shipit in ci', () => {
     await auto.loadConfig();
 
     auto.git!.getLatestRelease = () => Promise.resolve('1.2.3');
-    auto.git!.addToPrBody = jest.fn();
+    jest.spyOn(auto.git!, 'addToPrBody').mockImplementation();
     auto.release!.getCommitsInRelease = () => Promise.resolve([]);
     auto.release!.getCommits = () => Promise.resolve([]);
     const canary = jest.fn();
diff --git a/packages/core/src/__tests__/auto-canary-local.test.ts b/packages/core/src/__tests__/auto-canary-local.test.ts
index 85ae62cc..c6f6de11 100644
--- a/packages/core/src/__tests__/auto-canary-local.test.ts
+++ b/packages/core/src/__tests__/auto-canary-local.test.ts
@@ -19,7 +19,7 @@ test('shipit should publish canary in locally when not on master', async () => {
 
   auto.git!.getLatestRelease = () => Promise.resolve('1.2.3');
   auto.git!.getSha = () => Promise.resolve('abc');
-  auto.git!.createComment = jest.fn();
+  jest.spyOn(auto.git!, 'createComment').mockImplementation();
   auto.release!.getCommitsInRelease = () => Promise.resolve([]);
   auto.release!.getCommits = () => Promise.resolve([]);
   const canary = jest.fn();
diff --git a/packages/core/src/__tests__/auto.test.ts b/packages/core/src/__tests__/auto.test.ts
index 7f4553b1..848a7b5d 100644
--- a/packages/core/src/__tests__/auto.test.ts
+++ b/packages/core/src/__tests__/auto.test.ts
@@ -115,7 +115,7 @@ describe('Auto', () => {
     auto.logger = dummyLog();
     await auto.loadConfig();
 
-    expect([...auto.semVerLabels!.values()]).toEqual([
+    expect([...auto.semVerLabels!.values()]).toStrictEqual([
       ['Version: Major'],
       ['Version: Minor'],
       ['Version: Patch'],
@@ -138,7 +138,7 @@ describe('Auto', () => {
     auto.logger = dummyLog();
     await auto.loadConfig();
 
-    expect(auto.release!.options.skipReleaseLabels).toEqual(['NOPE']);
+    expect(auto.release!.options.skipReleaseLabels).toStrictEqual(['NOPE']);
   });
 
   test('should be able to add label as string', async () => {
@@ -154,7 +154,7 @@ describe('Auto', () => {
     auto.logger = dummyLog();
     await auto.loadConfig();
 
-    expect(auto.release!.options.labels.minor).toEqual([
+    expect(auto.release!.options.labels.minor).toStrictEqual([
       {
         description: 'Increment the minor version when merged',
         name: 'feature',
@@ -178,7 +178,7 @@ describe('Auto', () => {
     auto.logger = dummyLog();
     await auto.loadConfig();
 
-    expect(auto.release!.options.labels.minor).toEqual([
+    expect(auto.release!.options.labels.minor).toStrictEqual([
       {
         description: 'This is a test',
         name: 'minor',
@@ -202,7 +202,7 @@ describe('Auto', () => {
     auto.logger = dummyLog();
     await auto.loadConfig();
 
-    expect(auto.release!.options.labels.fooBar).toEqual([
+    expect(auto.release!.options.labels.fooBar).toStrictEqual([
       {
         description: 'This is a test',
         name: 'fooBar'
@@ -217,7 +217,7 @@ describe('Auto', () => {
       });
       const auto = new Auto();
       auto.logger = dummyLog();
-      expect(auto.createLabels()).rejects.toBeTruthy();
+      await expect(auto.createLabels()).rejects.not.toBeUndefined();
     });
 
     test('should create the labels', async () => {
@@ -228,7 +228,7 @@ describe('Auto', () => {
       auto.logger = dummyLog();
       await auto.loadConfig();
 
-      auto.release!.addLabelsToProject = jest.fn();
+      jest.spyOn(auto.release!, 'addLabelsToProject').mockImplementation();
       await auto.createLabels();
       expect(auto.release!.addLabelsToProject).toMatchSnapshot();
     });
@@ -241,7 +241,7 @@ describe('Auto', () => {
       });
       const auto = new Auto();
       auto.logger = dummyLog();
-      expect(auto.label({ pr: 13 })).rejects.toBeTruthy();
+      await expect(auto.label({ pr: 13 })).rejects.not.toBeUndefined();
     });
 
     test('should get labels', async () => {
@@ -252,7 +252,7 @@ describe('Auto', () => {
       const getLabels = jest.fn();
       auto.git!.getLabels = getLabels;
       getLabels.mockReturnValueOnce(['foo']);
-      console.log = jest.fn();
+      jest.spyOn(console, 'log').mockImplementation();
 
       await auto.label({ pr: 13 });
       expect(console.log).toHaveBeenCalledWith('foo');
@@ -276,7 +276,7 @@ describe('Auto', () => {
           labels: [{ name: 'foo' }, { name: 'bar' }]
         }
       ]);
-      console.log = jest.fn();
+      jest.spyOn(console, 'log').mockImplementation();
 
       await auto.label();
       expect(console.log).toHaveBeenCalledWith('foo\nbar');
@@ -291,7 +291,10 @@ describe('Auto', () => {
       const getPullRequests = jest.fn();
       auto.git!.getPullRequests = getPullRequests;
       getPullRequests.mockReturnValueOnce([]);
-      console.log = jest.fn();
+      jest
+        .spyOn(console, 'log')
+        .mockImplementation()
+        .mockReset();
 
       await auto.label();
       expect(console.log).not.toHaveBeenCalled();
@@ -316,7 +319,7 @@ describe('Auto', () => {
       const auto = new Auto(defaults);
       auto.logger = dummyLog();
 
-      expect(auto.prStatus(required)).rejects.toBeTruthy();
+      await expect(auto.prStatus(required)).rejects.not.toBeUndefined();
     });
 
     test('should catch exceptions when status fails to post', async () => {
@@ -412,7 +415,9 @@ describe('Auto', () => {
       const auto = new Auto(defaults);
       auto.logger = dummyLog();
 
-      expect(auto.prCheck({ pr: 13, ...required })).rejects.toBeTruthy();
+      await expect(
+        auto.prCheck({ pr: 13, ...required })
+      ).rejects.not.toBeUndefined();
     });
 
     test('should do nothing with dryRun', async () => {
@@ -520,7 +525,7 @@ describe('Auto', () => {
       );
     });
 
-    test('should pass with skip release label', async () => {
+    test('should pass with release label', async () => {
       const auto = new Auto(defaults);
       auto.logger = dummyLog();
 
@@ -551,7 +556,7 @@ describe('Auto', () => {
 
       await expect(
         auto.comment({ pr: 10, message: 'foo' })
-      ).rejects.toBeTruthy();
+      ).rejects.not.toBeUndefined();
     });
 
     test('should make a comment', async () => {
@@ -606,7 +611,12 @@ describe('Auto', () => {
       await auto.comment({ pr: 10, delete: true, dryRun: true });
       expect(deleteComment).not.toHaveBeenCalled();
 
-      await auto.comment({ pr: 10, message: 'foo bar', edit: true, dryRun: true });
+      await auto.comment({
+        pr: 10,
+        message: 'foo bar',
+        edit: true,
+        dryRun: true
+      });
       expect(deleteComment).not.toHaveBeenCalled();
       expect(editComment).not.toHaveBeenCalled();
     });
@@ -619,7 +629,7 @@ describe('Auto', () => {
 
       await expect(
         auto.prBody({ pr: 10, message: 'foo' })
-      ).rejects.toBeTruthy();
+      ).rejects.not.toBeUndefined();
     });
 
     test('should make a pr body update', async () => {
@@ -666,7 +676,7 @@ describe('Auto', () => {
     test('should throw when not initialized', async () => {
       const auto = new Auto(defaults);
       auto.logger = dummyLog();
-      expect(auto.version()).rejects.toBeTruthy();
+      await expect(auto.version()).rejects.not.toBeUndefined();
     });
 
     test('should calculate version with default options', async () => {
@@ -674,11 +684,13 @@ describe('Auto', () => {
       auto.logger = dummyLog();
       await auto.loadConfig();
 
-      auto.git!.getLatestRelease = jest.fn(() => Promise.resolve('v1.2.3'));
-      auto.release!.getSemverBump = jest.fn(() =>
-        Promise.resolve(SEMVER.patch)
-      );
-      console.log = jest.fn();
+      jest
+        .spyOn(auto.git!, 'getLatestRelease')
+        .mockImplementation(() => Promise.resolve('v1.2.3'));
+      jest
+        .spyOn(auto.release!, 'getSemverBump')
+        .mockImplementation(() => Promise.resolve(SEMVER.patch));
+      jest.spyOn(console, 'log').mockImplementation();
 
       await auto.version();
       expect(auto.release!.getSemverBump).toHaveBeenCalledWith('v1.2.3');
@@ -690,11 +702,13 @@ describe('Auto', () => {
       auto.logger = dummyLog();
       await auto.loadConfig();
 
-      auto.git!.getLatestRelease = jest.fn(() => Promise.resolve('v1.2.3'));
-      auto.release!.getSemverBump = jest.fn(() =>
-        Promise.resolve(SEMVER.minor)
-      );
-      console.log = jest.fn();
+      jest
+        .spyOn(auto.git!, 'getLatestRelease')
+        .mockImplementation(() => Promise.resolve('v1.2.3'));
+      jest
+        .spyOn(auto.release!, 'getSemverBump')
+        .mockImplementation(() => Promise.resolve(SEMVER.minor));
+      jest.spyOn(console, 'log').mockImplementation();
 
       await auto.version({ from: 'v1.1.0' });
       expect(auto.release!.getSemverBump).toHaveBeenCalledWith('v1.1.0');
@@ -707,7 +721,7 @@ describe('Auto', () => {
       const auto = new Auto(defaults);
       auto.logger = dummyLog();
 
-      expect(auto.changelog()).rejects.toBeTruthy();
+      await expect(auto.changelog()).rejects.not.toBeUndefined();
     });
 
     test('should do nothing on a dryRun', async () => {
@@ -718,7 +732,7 @@ describe('Auto', () => {
 
       const addToChangelog = jest.fn();
       auto.release!.addToChangelog = addToChangelog;
-      auto.release!.generateReleaseNotes = jest.fn();
+      jest.spyOn(auto.release!, 'generateReleaseNotes').mockImplementation();
 
       await auto.changelog({ from: 'v1.0.0', dryRun: true });
       expect(addToChangelog).not.toHaveBeenCalled();
@@ -735,7 +749,7 @@ describe('Auto', () => {
 
       const addToChangelog = jest.fn();
       auto.release!.addToChangelog = addToChangelog;
-      auto.release!.generateReleaseNotes = jest.fn();
+      jest.spyOn(auto.release!, 'generateReleaseNotes').mockImplementation();
 
       await auto.changelog({ from: 'v1.0.0' });
       expect(addToChangelog).toHaveBeenCalled();
@@ -760,7 +774,7 @@ describe('Auto', () => {
         dryRun: true
       });
 
-      expect(hookFn).not.toBeCalled();
+      expect(hookFn).not.toHaveBeenCalled();
     });
   });
 
@@ -770,14 +784,14 @@ describe('Auto', () => {
       auto.logger = dummyLog();
       await auto.loadConfig();
       auto.git!.getLatestRelease = () => Promise.resolve('1.2.3');
-      auto.release!.generateReleaseNotes = jest.fn();
+      jest.spyOn(auto.release!, 'generateReleaseNotes').mockImplementation();
       auto.release!.getCommitsInRelease = () =>
         Promise.resolve([makeCommitFromMsg('Test Commit')]);
 
       auto.hooks.getPreviousVersion.tap('test', () => '1.2.3');
       const afterRelease = jest.fn();
       auto.hooks.afterRelease.tap('test', afterRelease);
-      auto.release!.getCommits = jest.fn();
+      jest.spyOn(auto.release!, 'getCommits').mockImplementation();
 
       await auto.runRelease();
       expect(afterRelease).not.toHaveBeenCalled();
@@ -788,17 +802,17 @@ describe('Auto', () => {
       auto.logger = dummyLog();
       await auto.loadConfig();
       auto.git!.getLatestRelease = () => Promise.resolve('1.2.3');
-      auto.git!.publish = jest.fn();
-      auto.release!.generateReleaseNotes = jest.fn(() =>
-        Promise.resolve('releaseNotes')
-      );
+      jest.spyOn(auto.git!, 'publish').mockImplementation();
+      jest
+        .spyOn(auto.release!, 'generateReleaseNotes')
+        .mockImplementation(() => Promise.resolve('releaseNotes'));
       auto.release!.getCommitsInRelease = () =>
         Promise.resolve([makeCommitFromMsg('Test Commit')]);
 
       auto.hooks.getPreviousVersion.tap('test', () => '1.2.4');
       const afterRelease = jest.fn();
       auto.hooks.afterRelease.tap('test', afterRelease);
-      auto.release!.getCommits = jest.fn();
+      jest.spyOn(auto.release!, 'getCommits').mockImplementation();
 
       await auto.runRelease();
       expect(auto.release!.generateReleaseNotes).toHaveBeenCalledWith(
@@ -820,17 +834,17 @@ describe('Auto', () => {
       auto.logger = dummyLog();
       await auto.loadConfig();
       auto.git!.getLatestRelease = () => Promise.resolve('1.2.3');
-      auto.git!.publish = jest.fn();
-      auto.release!.generateReleaseNotes = jest.fn(() =>
-        Promise.resolve('releaseNotes')
-      );
+      jest.spyOn(auto.git!, 'publish').mockImplementation();
+      jest
+        .spyOn(auto.release!, 'generateReleaseNotes')
+        .mockImplementation(() => Promise.resolve('releaseNotes'));
       auto.release!.getCommitsInRelease = () =>
         Promise.resolve([makeCommitFromMsg('Test Commit')]);
 
       auto.hooks.getPreviousVersion.tap('test', () => '1.2.4');
       const afterRelease = jest.fn();
       auto.hooks.afterRelease.tap('test', afterRelease);
-      auto.release!.getCommits = jest.fn();
+      jest.spyOn(auto.release!, 'getCommits').mockImplementation();
 
       await auto.runRelease({
         from: 'v1.2.0'
@@ -854,17 +868,17 @@ describe('Auto', () => {
       auto.logger = dummyLog();
       await auto.loadConfig();
       auto.git!.getLatestRelease = () => Promise.resolve('1.2.3');
-      auto.git!.publish = jest.fn();
-      auto.release!.generateReleaseNotes = jest.fn(() =>
-        Promise.resolve('releaseNotes')
-      );
+      jest.spyOn(auto.git!, 'publish').mockImplementation();
+      jest
+        .spyOn(auto.release!, 'generateReleaseNotes')
+        .mockImplementation(() => Promise.resolve('releaseNotes'));
       auto.release!.getCommitsInRelease = () =>
         Promise.resolve([makeCommitFromMsg('Test Commit')]);
 
       auto.hooks.getPreviousVersion.tap('test', () => '1.2.4');
       const afterRelease = jest.fn();
       auto.hooks.afterRelease.tap('test', afterRelease);
-      auto.release!.getCommits = jest.fn();
+      jest.spyOn(auto.release!, 'getCommits').mockImplementation();
 
       await auto.runRelease({
         useVersion: 'v1.3.0'
@@ -889,7 +903,7 @@ describe('Auto', () => {
       const auto = new Auto(defaults);
       auto.logger = dummyLog();
 
-      expect(auto.canary()).rejects.toBeTruthy();
+      await expect(auto.canary()).rejects.not.toBeUndefined();
     });
 
     test('does not call canary hook in dry-run', async () => {
@@ -901,7 +915,7 @@ describe('Auto', () => {
         Promise.resolve([makeCommitFromMsg('Test Commit')]);
       const canary = jest.fn();
       auto.hooks.canary.tap('test', canary);
-      auto.release!.getCommits = jest.fn();
+      jest.spyOn(auto.release!, 'getCommits').mockImplementation();
 
       await auto.canary({ pr: 123, build: 1, dryRun: true });
       expect(canary).not.toHaveBeenCalled();
@@ -913,12 +927,12 @@ describe('Auto', () => {
       await auto.loadConfig();
       auto.git!.getLatestRelease = () => Promise.resolve('1.2.3');
       auto.git!.getSha = () => Promise.resolve('abc');
-      auto.git!.addToPrBody = jest.fn();
+      jest.spyOn(auto.git!, 'addToPrBody').mockImplementation();
       auto.release!.getCommitsInRelease = () =>
         Promise.resolve([makeCommitFromMsg('Test Commit')]);
       const canary = jest.fn();
       auto.hooks.canary.tap('test', canary);
-      auto.release!.getCommits = jest.fn();
+      jest.spyOn(auto.release!, 'getCommits').mockImplementation();
 
       await auto.canary({ pr: 123, build: 1 });
       expect(canary).toHaveBeenCalledWith(SEMVER.patch, '.123.1.abc');
@@ -932,17 +946,19 @@ describe('Auto', () => {
       auto.git!.getLatestTagInBranch = () => {
         throw new Error();
       };
+
       auto.git!.getLatestRelease = () => Promise.resolve('1.2.3');
       auto.git!.getSha = () => Promise.resolve('abc');
       auto.release!.getCommits = () => Promise.resolve([]);
       auto.git!.getFirstCommit = () => Promise.resolve('abcd');
-      auto.git!.addToPrBody = jest.fn();
-      auto.release!.getCommitsInRelease = jest
-        .fn()
+      jest.spyOn(auto.git!, 'addToPrBody').mockImplementation();
+      jest
+        .spyOn(auto.release!, 'getCommitsInRelease')
+        .mockImplementation()
         .mockReturnValue(Promise.resolve([makeCommitFromMsg('Test Commit')]));
       const canary = jest.fn();
       auto.hooks.canary.tap('test', canary);
-      auto.release!.getCommits = jest.fn();
+      jest.spyOn(auto.release!, 'getCommits').mockImplementation();
 
       await auto.canary({ pr: 123, build: 1 });
       expect(auto.release!.getCommits).toHaveBeenCalledWith('abcd');
@@ -954,12 +970,12 @@ describe('Auto', () => {
       await auto.loadConfig();
       auto.git!.getLatestRelease = () => Promise.resolve('1.2.3');
       auto.git!.getSha = () => Promise.resolve('abc');
-      auto.git!.addToPrBody = jest.fn();
+      jest.spyOn(auto.git!, 'addToPrBody').mockImplementation();
       auto.release!.getCommitsInRelease = () =>
         Promise.resolve([makeCommitFromMsg('Test Commit')]);
       const canary = jest.fn();
       auto.hooks.canary.tap('test', canary);
-      auto.release!.getCommits = jest.fn();
+      jest.spyOn(auto.release!, 'getCommits').mockImplementation();
 
       await auto.canary();
       expect(canary).toHaveBeenCalledWith(SEMVER.patch, '.abc');
@@ -969,7 +985,7 @@ describe('Auto', () => {
       const auto = new Auto({ ...defaults, plugins: [] });
       auto.logger = dummyLog();
       await auto.loadConfig();
-      auto.prBody = jest.fn();
+      jest.spyOn(auto, 'prBody').mockImplementation();
       auto.git!.getLatestRelease = () => Promise.resolve('1.2.3');
       auto.git!.getSha = () => Promise.resolve('abc');
       auto.release!.getCommitsInRelease = () =>
@@ -978,7 +994,7 @@ describe('Auto', () => {
       const canary = jest.fn();
       canary.mockReturnValue({ error: 'ooops' });
       auto.hooks.canary.tap('test', canary);
-      auto.release!.getCommits = jest.fn();
+      jest.spyOn(auto.release!, 'getCommits').mockImplementation();
 
       await auto.canary({ pr: 123, build: 1 });
       expect(auto.prBody).not.toHaveBeenCalled();
@@ -1026,7 +1042,7 @@ describe('Auto', () => {
       const auto = new Auto(defaults);
       auto.logger = dummyLog();
 
-      expect(auto.shipit()).rejects.toBeTruthy();
+      await expect(auto.shipit()).rejects.not.toBeUndefined();
     });
 
     test('should not publish when no latest version found', async () => {
@@ -1049,10 +1065,10 @@ describe('Auto', () => {
       await auto.loadConfig();
 
       auto.git!.getLatestRelease = () => Promise.resolve('1.2.3');
-      auto.git!.publish = jest.fn();
-      auto.release!.getCommitsInRelease = jest.fn();
-      auto.release!.generateReleaseNotes = jest.fn();
-      auto.release!.addToChangelog = jest.fn();
+      jest.spyOn(auto.git!, 'publish').mockImplementation();
+      jest.spyOn(auto.release!, 'getCommitsInRelease').mockImplementation();
+      jest.spyOn(auto.release!, 'generateReleaseNotes').mockImplementation();
+      jest.spyOn(auto.release!, 'addToChangelog').mockImplementation();
       const afterShipIt = jest.fn();
       auto.hooks.afterShipIt.tap('test', afterShipIt);
 
@@ -1066,10 +1082,10 @@ describe('Auto', () => {
       await auto.loadConfig();
 
       auto.git!.getLatestRelease = () => Promise.resolve('1.2.3');
-      auto.git!.publish = jest.fn();
-      auto.release!.getCommitsInRelease = jest.fn();
-      auto.release!.generateReleaseNotes = jest.fn();
-      auto.release!.addToChangelog = jest.fn();
+      jest.spyOn(auto.git!, 'publish').mockImplementation();
+      jest.spyOn(auto.release!, 'getCommitsInRelease').mockImplementation();
+      jest.spyOn(auto.release!, 'generateReleaseNotes').mockImplementation();
+      jest.spyOn(auto.release!, 'addToChangelog').mockImplementation();
       const version = jest.fn();
       auto.hooks.version.tap('test', version);
 
@@ -1097,7 +1113,7 @@ describe('hooks', () => {
 
     await auto.loadConfig();
 
-    expect(auto.labels!.released).toEqual([
+    expect(auto.labels!.released).toStrictEqual([
       {
         description: 'This issue/pull request has been released',
         name: 'released'
@@ -1120,7 +1136,7 @@ describe('hooks', () => {
       await auto.loadConfig();
       auto.git!.getLatestRelease = async () => Promise.resolve('1.0.0');
 
-      console.log = jest.fn();
+      jest.spyOn(console, 'log').mockImplementation();
       await auto.version();
 
       expect(console.log).toHaveBeenCalledWith('major');
@@ -1148,7 +1164,7 @@ describe('hooks', () => {
       await auto.loadConfig();
       auto.git!.getLatestRelease = async () => Promise.resolve('1.0.0');
 
-      console.log = jest.fn();
+      jest.spyOn(console, 'log').mockImplementation();
       await auto.version();
 
       expect(console.log).toHaveBeenCalledWith('patch');
diff --git a/packages/core/src/__tests__/config.test.ts b/packages/core/src/__tests__/config.test.ts
index ec40aa68..83f497cd 100644
--- a/packages/core/src/__tests__/config.test.ts
+++ b/packages/core/src/__tests__/config.test.ts
@@ -17,24 +17,24 @@ jest.mock('import-cwd', () => (path: string) => importMock(path));
 
 describe('normalizeLabel', () => {
   test('should handle string', () => {
-    expect(normalizeLabel('foo', 'foo')).toEqual([{ name: 'foo' }]);
+    expect(normalizeLabel('foo', 'foo')).toStrictEqual([{ name: 'foo' }]);
   });
 
   test('should handle object', () => {
     const label = { name: 'foo', description: 'something' };
-    expect(normalizeLabel('foo', label)).toEqual([label]);
+    expect(normalizeLabel('foo', label)).toStrictEqual([label]);
   });
 
   test('should attach name', () => {
     const label = { description: 'something' };
-    expect(normalizeLabel('foo', label)).toEqual([
+    expect(normalizeLabel('foo', label)).toStrictEqual([
       { name: 'foo', description: 'something' }
     ]);
   });
 
   test('should handle arrays', () => {
     const label = { name: 'foo', description: 'something' };
-    expect(normalizeLabel('major', ['major', label])).toEqual([
+    expect(normalizeLabel('major', ['major', label])).toStrictEqual([
       {
         description: 'Increment the major version when merged',
         name: 'major',
@@ -47,7 +47,7 @@ describe('normalizeLabel', () => {
 
 describe('normalizeLabels', () => {
   test('user labels should override defaults', () => {
-    expect(normalizeLabels({}).minor).toEqual([
+    expect(normalizeLabels({}).minor).toStrictEqual([
       {
         description: 'Increment the minor version when merged',
         name: 'minor',
@@ -55,13 +55,15 @@ describe('normalizeLabels', () => {
       }
     ]);
 
-    expect(normalizeLabels({ labels: { minor: ['foo'] } }).minor).toEqual([
-      {
-        description: 'Increment the minor version when merged',
-        name: 'foo',
-        title: '🚀  Enhancement'
-      }
-    ]);
+    expect(normalizeLabels({ labels: { minor: ['foo'] } }).minor).toStrictEqual(
+      [
+        {
+          description: 'Increment the minor version when merged',
+          name: 'foo',
+          title: '🚀  Enhancement'
+        }
+      ]
+    );
   });
 });
 
@@ -79,7 +81,7 @@ describe('loadExtendConfig', () => {
     importMock.mockImplementation(path =>
       path === '../fake/path.json' ? { someOption: 'url' } : undefined
     );
-    expect(await config.loadExtendConfig('../fake/path.json')).toEqual({
+    expect(await config.loadExtendConfig('../fake/path.json')).toStrictEqual({
       someOption: 'url'
     });
   });
@@ -90,7 +92,7 @@ describe('loadExtendConfig', () => {
     importMock.mockImplementation(path =>
       path === './package.json' ? { auto: { someOption: 'url' } } : undefined
     );
-    expect(await config.loadExtendConfig('./package.json')).toEqual({
+    expect(await config.loadExtendConfig('./package.json')).toStrictEqual({
       someOption: 'url'
     });
   });
@@ -139,7 +141,7 @@ describe('loadExtendConfig', () => {
         : undefined
     );
 
-    expect(await config.loadExtendConfig('@artsy')).toEqual({
+    expect(await config.loadExtendConfig('@artsy')).toStrictEqual({
       onlyPublishWithReleaseLabel: true
     });
   });
@@ -153,7 +155,7 @@ describe('loadExtendConfig', () => {
         : undefined
     );
 
-    expect(await config.loadExtendConfig('fuego')).toEqual({
+    expect(await config.loadExtendConfig('fuego')).toStrictEqual({
       noVersionPrefix: true
     });
   });
diff --git a/packages/core/src/__tests__/git.test.ts b/packages/core/src/__tests__/git.test.ts
index 87f7e75c..70f00025 100644
--- a/packages/core/src/__tests__/git.test.ts
+++ b/packages/core/src/__tests__/git.test.ts
@@ -114,7 +114,7 @@ describe('github', () => {
         ]
       });
 
-      expect(await gh.getLabels(123)).toEqual([
+      expect(await gh.getLabels(123)).toStrictEqual([
         'minor',
         'documentation',
         'major',
@@ -125,7 +125,7 @@ describe('github', () => {
     test('handles errors', async () => {
       const gh = new Git(options);
 
-      expect(gh.getLabels(123)).rejects.toBeTruthy();
+      await expect(gh.getLabels(123)).rejects.toBeInstanceOf(Error);
     });
   });
 
@@ -216,7 +216,7 @@ describe('github', () => {
         target_url: 'google.com',
         description: 'testing'
       })
-    ).toBeTruthy();
+    ).not.toBeUndefined();
   });
 
   test('search', async () => {
@@ -239,7 +239,7 @@ describe('github', () => {
 
     getProject.mockReturnValueOnce({ data: true });
 
-    expect(await gh.getProject()).toBeTruthy();
+    expect(await gh.getProject()).not.toBeUndefined();
   });
 
   describe('createComment', () => {
@@ -351,21 +351,21 @@ describe('github', () => {
       });
 
       const commentId = await gh.getCommentId(22, 'default');
-      expect(commentId).toEqual(1337);
+      expect(commentId).toStrictEqual(1337);
     });
 
     test('should return -1 if context does not exist', async () => {
       const gh = new Git(options);
 
-      listComments.mockReturnValue({data: [] });
+      listComments.mockReturnValue({ data: [] });
 
       const commentId = await gh.getCommentId(22, 'default');
-      expect(commentId).toEqual(-1);
+      expect(commentId).toStrictEqual(-1);
     });
   });
 
   describe('addToPrBody', () => {
-    test('should add to PR body if none exists', async () => {
+    test('should add to PR body if exists', async () => {
       const gh = new Git(options);
 
       get.mockReturnValueOnce({ data: { body: '# My Content' } });
@@ -460,7 +460,7 @@ describe('github', () => {
     expect(listCommits).toHaveBeenCalled();
   });
 
-  test('getCommitsForPR', async () => {
+  test('getPullRequests', async () => {
     const gh = new Git(options);
 
     list.mockReturnValueOnce({
@@ -479,7 +479,7 @@ describe('github', () => {
         data: { name: 'Andrew Lisowski' }
       });
 
-      expect(await gh.getUserByUsername('andrew')).toEqual({
+      expect(await gh.getUserByUsername('andrew')).toStrictEqual({
         name: 'Andrew Lisowski'
       });
     });
@@ -501,7 +501,7 @@ describe('github', () => {
         data: { items: [{ login: 'hipstersmoothie' }] }
       });
 
-      expect(await gh.getUserByEmail('lisowski54@gmail.com')).toEqual({
+      expect(await gh.getUserByEmail('lisowski54@gmail.com')).toStrictEqual({
         login: 'hipstersmoothie'
       });
     });
@@ -513,7 +513,7 @@ describe('github', () => {
         data: undefined
       });
 
-      expect(await gh.getUserByEmail('lisowski54@gmail.com')).toEqual({});
+      expect(await gh.getUserByEmail('lisowski54@gmail.com')).toStrictEqual({});
     });
 
     test('errors', async () => {
@@ -547,7 +547,7 @@ describe('github', () => {
     test('handles errors', async () => {
       const gh = new Git(options);
 
-      expect(gh.getLatestRelease()).rejects.toBeTruthy();
+      await expect(gh.getLatestRelease()).rejects.not.toBeUndefined();
     });
   });
 
@@ -559,7 +559,7 @@ describe('github', () => {
         data: [{ name: 'first label' }, { name: 'second label' }]
       });
 
-      expect(await gh.getProjectLabels()).toEqual([
+      expect(await gh.getProjectLabels()).toStrictEqual([
         'first label',
         'second label'
       ]);
@@ -568,7 +568,7 @@ describe('github', () => {
     test('throw for errors', async () => {
       const gh = new Git(options);
 
-      expect(gh.getProjectLabels()).rejects.toBeTruthy();
+      await expect(gh.getProjectLabels()).rejects.not.toBeUndefined();
     });
   });
 
@@ -576,13 +576,13 @@ describe('github', () => {
     test('return pr', async () => {
       const gh = new Git(options);
       get.mockReturnValueOnce({ success: true });
-      expect(await gh.getPr(123)).toEqual({ success: true });
+      expect(await gh.getPr(123)).toStrictEqual({ success: true });
     });
 
     test('throw for errors', async () => {
       const gh = new Git(options);
       get.mockRejectedValueOnce(Error);
-      await expect(gh.getPr(120)).rejects.toBeTruthy();
+      await expect(gh.getPr(120)).rejects.not.toBeUndefined();
     });
   });
 
@@ -619,7 +619,7 @@ describe('github', () => {
     test('throw for errors', async () => {
       const gh = new Git(options);
 
-      expect(gh.getProjectLabels()).rejects.toBeTruthy();
+      await expect(gh.getProjectLabels()).rejects.not.toBeUndefined();
     });
   });
 
@@ -633,10 +633,10 @@ describe('github', () => {
         headers
       });
 
-      // tslint:disable-next-line:no-unused
+      // eslint-disable-next-line @typescript-eslint/no-unused-vars
       const gh = new Git(options);
 
-      expect(errorHook).toBeCalled();
+      expect(errorHook).toHaveBeenCalled();
 
       const errorHandler = errorHook.mock.calls[0][1] as (
         error: HookError
diff --git a/packages/core/src/__tests__/log-parse.test.ts b/packages/core/src/__tests__/log-parse.test.ts
index edccb0da..5b9d9670 100644
--- a/packages/core/src/__tests__/log-parse.test.ts
+++ b/packages/core/src/__tests__/log-parse.test.ts
@@ -5,7 +5,7 @@ import makeCommitFromMsg from './make-commit-from-msg';
 describe('parsePR', () => {
   test('should do nothing without merge commit', () => {
     const commit = makeCommitFromMsg('Not a merge');
-    expect(parsePR(commit)).toEqual(commit);
+    expect(parsePR(commit)).toStrictEqual(commit);
   });
 
   test('should parse number base and comment', () => {
@@ -13,7 +13,7 @@ describe('parsePR', () => {
       'Merge pull request #1234 from Another PR\nComments about the PR'
     );
 
-    expect(parsePR(commit)).toEqual({
+    expect(parsePR(commit)).toStrictEqual({
       ...commit,
       pullRequest: {
         number: 1234,
@@ -27,12 +27,12 @@ describe('parsePR', () => {
 describe('parseSquashPR', () => {
   test('should do nothing squash merge commit', () => {
     const commit = makeCommitFromMsg('Not a squash');
-    expect(parseSquashPR(commit)).toEqual(commit);
+    expect(parseSquashPR(commit)).toStrictEqual(commit);
   });
 
   test('should parse PR number and comment', () => {
     const commit = makeCommitFromMsg('Some Message (#1234)');
-    expect(parseSquashPR(commit)).toEqual({
+    expect(parseSquashPR(commit)).toStrictEqual({
       ...commit,
       pullRequest: {
         number: 1234
diff --git a/packages/core/src/__tests__/release.test.ts b/packages/core/src/__tests__/release.test.ts
index f55730c0..1217f1de 100644
--- a/packages/core/src/__tests__/release.test.ts
+++ b/packages/core/src/__tests__/release.test.ts
@@ -110,7 +110,7 @@ const git = new Git({
 
 describe('getVersionMap', () => {
   test('should return the default map', () => {
-    expect(getVersionMap()).toEqual(
+    expect(getVersionMap()).toStrictEqual(
       new Map([
         ['major', ['major']],
         ['minor', ['minor']],
@@ -125,7 +125,7 @@ describe('getVersionMap', () => {
   test('should add custom labels', () => {
     expect(
       getVersionMap({ major: [{ name: 'major' }, { name: 'BREAKING' }] })
-    ).toEqual(new Map([['major', ['major', 'BREAKING']]]));
+    ).toStrictEqual(new Map([['major', ['major', 'BREAKING']]]));
   });
 });
 
@@ -328,7 +328,7 @@ describe('Release', () => {
   });
 
   describe('addToChangelog', () => {
-    test("creates new changelog if one didn't exist", async () => {
+    test("creates new changelog if one didn't exist - from 0", async () => {
       const gh = new Release(git);
       await gh.addToChangelog(
         '# My new Notes',
@@ -718,8 +718,8 @@ describe('Release', () => {
       getGitLog.mockReturnValueOnce(commits);
 
       getCommitsForPR
-        .mockReturnValueOnce(Promise.reject('bah'))
-        .mockReturnValueOnce(Promise.reject('bah'));
+        .mockReturnValueOnce(Promise.reject(new Error('bah')))
+        .mockReturnValueOnce(Promise.reject(new Error('bah')));
 
       await expect(
         gh.generateReleaseNotes('1234', '123')
@@ -925,7 +925,7 @@ describe('Release', () => {
 
     test('should log that it has created the labels', async () => {
       const mockLogger = dummyLog();
-      mockLogger.log.log = jest.fn();
+      jest.spyOn(mockLogger.log, 'log').mockImplementation();
 
       const gh = new Release(
         git,
diff --git a/packages/core/src/utils/__tests__/exec-promise.test.ts b/packages/core/src/utils/__tests__/exec-promise.test.ts
index 9cfae6cd..1a8f9cd9 100644
--- a/packages/core/src/utils/__tests__/exec-promise.test.ts
+++ b/packages/core/src/utils/__tests__/exec-promise.test.ts
@@ -31,7 +31,7 @@ this error
 });
 
 test('prints stderr when exec exits without a code', async () => {
-  console.log = jest.fn();
+  jest.spyOn(console, 'log').mockImplementation();
   await exec('>&2 echo "this error"');
 
   return expect(console.log).toHaveBeenCalledWith('this error\n');
diff --git a/packages/core/src/utils/__tests__/load-plugin.test.ts b/packages/core/src/utils/__tests__/load-plugin.test.ts
index 1b39f39c..c5b040e5 100644
--- a/packages/core/src/utils/__tests__/load-plugin.test.ts
+++ b/packages/core/src/utils/__tests__/load-plugin.test.ts
@@ -8,10 +8,12 @@ describe('loadPlugins', () => {
   test('should require custom plugins -- fallback to cwd', async () => {
     expect(
       loadPlugin([path.join(__dirname, './test-plugin.ts'), {}], logger)
-    ).toEqual({
-      name: 'foo',
-      config: {}
-    });
+    ).toStrictEqual(
+      expect.objectContaining({
+        name: 'foo',
+        config: {}
+      })
+    );
   });
 
   test('should load config', async () => {
@@ -20,9 +22,11 @@ describe('loadPlugins', () => {
         [path.join(__dirname, './test-plugin.ts'), 'do the thing'],
         logger
       )
-    ).toEqual({
-      name: 'foo',
-      config: 'do the thing'
-    });
+    ).toStrictEqual(
+      expect.objectContaining({
+        name: 'foo',
+        config: 'do the thing'
+      })
+    );
   });
 });
diff --git a/packages/core/src/utils/__tests__/test-plugin.ts b/packages/core/src/utils/__tests__/test-plugin.ts
index 8602cf62..566f210d 100644
--- a/packages/core/src/utils/__tests__/test-plugin.ts
+++ b/packages/core/src/utils/__tests__/test-plugin.ts
@@ -1,8 +1,8 @@
 module.exports = class Test {
   name = 'foo';
-  config: any;
+  config: {};
 
-  constructor(config: any) {
+  constructor(config: {}) {
     this.config = config;
   }
 };
diff --git a/plugins/conventional-commits/__tests__/conventional-commits.test.ts b/plugins/conventional-commits/__tests__/conventional-commits.test.ts
index 6a19f102..e238970a 100644
--- a/plugins/conventional-commits/__tests__/conventional-commits.test.ts
+++ b/plugins/conventional-commits/__tests__/conventional-commits.test.ts
@@ -31,7 +31,7 @@ test('should do nothing when conventional commit message is not present', async
   } as LogParse);
 
   const commit = makeCommitFromMsg('normal commit with no bump');
-  expect(await logParseHooks.parseCommit.promise({ ...commit })).toEqual(
+  expect(await logParseHooks.parseCommit.promise({ ...commit })).toStrictEqual(
     commit
   );
 });
@@ -52,7 +52,7 @@ test('should add correct semver label to commit', async () => {
   } as LogParse);
 
   const commit = makeCommitFromMsg('fix: normal commit with no bump');
-  expect(await logParseHooks.parseCommit.promise({ ...commit })).toEqual({
+  expect(await logParseHooks.parseCommit.promise({ ...commit })).toStrictEqual({
     ...commit,
     labels: ['patch']
   });
@@ -74,7 +74,7 @@ test('should add major semver label to commit', async () => {
   } as LogParse);
 
   const commit = makeCommitFromMsg('BREAKING: normal commit with no bump');
-  expect(await logParseHooks.parseCommit.promise({ ...commit })).toEqual({
+  expect(await logParseHooks.parseCommit.promise({ ...commit })).toStrictEqual({
     ...commit,
     labels: ['major']
   });
diff --git a/plugins/jira/__tests__/jira.test.ts b/plugins/jira/__tests__/jira.test.ts
index ca7db5a9..3736f5bc 100644
--- a/plugins/jira/__tests__/jira.test.ts
+++ b/plugins/jira/__tests__/jira.test.ts
@@ -18,7 +18,7 @@ describe('parse jira', () => {
       ...makeCommitFromMsg('Add log')
     };
 
-    expect(parseJira(commit)).toEqual(commit);
+    expect(parseJira(commit)).toStrictEqual(commit);
   });
 
   test('story found', () => {
@@ -26,11 +26,15 @@ describe('parse jira', () => {
       number: ['P-5052']
     };
 
-    expect(parseJira(makeCommitFromMsg('P-5052: Add log')).jira).toEqual(jira);
-    expect(parseJira(makeCommitFromMsg('[P-5052] - Add log')).jira).toEqual(
+    expect(parseJira(makeCommitFromMsg('P-5052: Add log')).jira).toStrictEqual(
+      jira
+    );
+    expect(
+      parseJira(makeCommitFromMsg('[P-5052] - Add log')).jira
+    ).toStrictEqual(jira);
+    expect(parseJira(makeCommitFromMsg('[P-5052] Add log')).jira).toStrictEqual(
       jira
     );
-    expect(parseJira(makeCommitFromMsg('[P-5052] Add log')).jira).toEqual(jira);
   });
 
   test('story found, pr no title', () => {
@@ -38,26 +42,28 @@ describe('parse jira', () => {
       number: ['PLAYA-5052']
     };
 
-    expect(parseJira(makeCommitFromMsg('[PLAYA-5052]')).jira).toEqual(jira);
+    expect(parseJira(makeCommitFromMsg('[PLAYA-5052]')).jira).toStrictEqual(
+      jira
+    );
   });
 
-  test('story found', () => {
+  test('story found multiple', () => {
     const jira = {
       number: ['PLAYA-5052', 'PLAYA-6000']
     };
 
     expect(
       parseJira(makeCommitFromMsg('PLAYA-5052 PLAYA-6000: Add log')).jira
-    ).toEqual(jira);
+    ).toStrictEqual(jira);
     expect(
       parseJira(makeCommitFromMsg('[PLAYA-5052][PLAYA-6000] - Add log')).jira
-    ).toEqual(jira);
+    ).toStrictEqual(jira);
     expect(
       parseJira(makeCommitFromMsg('[PLAYA-5052] PLAYA-6000: Add log')).jira
-    ).toEqual(jira);
+    ).toStrictEqual(jira);
     expect(
       parseJira(makeCommitFromMsg('PLAYA-5052 [PLAYA-6000] - Add log')).jira
-    ).toEqual(jira);
+    ).toStrictEqual(jira);
   });
 });
 
diff --git a/plugins/maven/__tests__/maven.test.ts b/plugins/maven/__tests__/maven.test.ts
index 1f28fce2..e288e302 100644
--- a/plugins/maven/__tests__/maven.test.ts
+++ b/plugins/maven/__tests__/maven.test.ts
@@ -36,7 +36,7 @@ describe('maven', () => {
         </project>
       `);
 
-      expect(await hooks.getAuthor.promise()).toEqual(
+      expect(await hooks.getAuthor.promise()).toStrictEqual(
         expect.objectContaining({
           email: 'test@email.com',
           name: 'Andrew Lisowski'
@@ -63,7 +63,7 @@ describe('maven', () => {
         </project>
       `);
 
-      expect(await hooks.getAuthor.promise()).toEqual(
+      expect(await hooks.getAuthor.promise()).toStrictEqual(
         expect.objectContaining({
           email: 'test@email.com',
           name: 'Andrew Lisowski'
@@ -71,7 +71,7 @@ describe('maven', () => {
       );
     });
 
-    test('should get author from pom.xml', async () => {
+    test('should get author from pom.xml - simple', async () => {
       mockRead(`
         <project
           xmlns="http://maven.apache.org/POM/4.0.0"
@@ -99,7 +99,7 @@ describe('maven', () => {
         </project>
       `);
 
-      expect(await hooks.getRepository.promise()).toEqual(
+      expect(await hooks.getRepository.promise()).toStrictEqual(
         expect.objectContaining({
           owner: 'Fuego-Tools',
           repo: 'java-test-project'
diff --git a/plugins/npm/__tests__/npm.test.ts b/plugins/npm/__tests__/npm.test.ts
index 9ce68a74..282bee55 100644
--- a/plugins/npm/__tests__/npm.test.ts
+++ b/plugins/npm/__tests__/npm.test.ts
@@ -58,7 +58,14 @@ describe('changedPackages ', () => {
   test('should return nothing without a package directory', async () => {
     exec.mockReturnValueOnce(`packages/README.md\npackage.json`);
 
-    expect(await changedPackages('sha', [], {}, dummyLog())).toEqual([]);
+    expect(
+      await changedPackages({
+        sha: 'sha',
+        packages: [],
+        lernaJson: {},
+        logger: dummyLog()
+      })
+    ).toStrictEqual([]);
   });
 
   test('should match files in package directory', async () => {
@@ -67,9 +74,9 @@ describe('changedPackages ', () => {
     );
 
     expect(
-      await changedPackages(
-        'sha',
-        [
+      await changedPackages({
+        sha: 'sha',
+        packages: [
           {
             name: 'foo',
             path: 'packages/foo',
@@ -81,10 +88,10 @@ describe('changedPackages ', () => {
             version: '1.0.0'
           }
         ],
-        {},
-        dummyLog()
-      )
-    ).toEqual(['foo', 'bar']);
+        lernaJson: {},
+        logger: dummyLog()
+      })
+    ).toStrictEqual(['foo', 'bar']);
   });
 
   test('should match files in package directory with @scope/ names', async () => {
@@ -93,9 +100,9 @@ describe('changedPackages ', () => {
     );
 
     expect(
-      await changedPackages(
-        'sha',
-        [
+      await changedPackages({
+        sha: 'sha',
+        packages: [
           {
             name: '@scope/foo',
             path: 'packages/@scope/foo',
@@ -107,22 +114,22 @@ describe('changedPackages ', () => {
             version: '1.0.0'
           }
         ],
-        {},
-        dummyLog()
-      )
-    ).toEqual(['@scope/foo', '@scope/bar']);
+        lernaJson: {},
+        logger: dummyLog()
+      })
+    ).toStrictEqual(['@scope/foo', '@scope/bar']);
   });
 });
 
 describe('getMonorepoPackage', () => {
   test('should default to 0.0.0', () => {
     monorepoPackages.mockReturnValueOnce([]);
-    expect(getMonorepoPackage()).toEqual({});
+    expect(getMonorepoPackage()).toStrictEqual({});
   });
 
   test('should find greatest package version', () => {
     monorepoPackages.mockReturnValueOnce(monorepoPackagesResult);
-    expect(getMonorepoPackage()).toEqual({ version: '0.1.2' });
+    expect(getMonorepoPackage()).toStrictEqual({ version: '0.1.2' });
   });
 });
 
@@ -181,7 +188,7 @@ describe('getAuthor', () => {
         }
       }
     `;
-    expect(await hooks.getAuthor.promise()).toEqual({
+    expect(await hooks.getAuthor.promise()).toStrictEqual({
       name: 'Adam',
       email: 'adam@email.com'
     });
@@ -199,7 +206,7 @@ describe('getAuthor', () => {
         "author": "Adam<adam@email.com>"
       }
     `;
-    expect(await hooks.getAuthor.promise()).toEqual({
+    expect(await hooks.getAuthor.promise()).toStrictEqual({
       name: 'Adam',
       email: 'adam@email.com'
     });
@@ -282,11 +289,13 @@ describe('getPreviousVersion', () => {
 test('should use string semver if no published package', async () => {
   const plugin = new NPMPlugin({ setRcToken: false });
 
-  expect(plugin).toEqual({
-    forcePublish: true,
-    name: 'NPM',
-    setRcToken: false
-  });
+  expect(plugin).toStrictEqual(
+    expect.objectContaining({
+      forcePublish: true,
+      name: 'NPM',
+      setRcToken: false
+    })
+  );
 });
 
 describe('publish', () => {
@@ -524,23 +533,6 @@ describe('publish', () => {
     await hooks.publish.promise(Auto.SEMVER.patch);
     expect(exec).toHaveBeenCalledWith('npm', ['publish']);
   });
-
-  test('should publish private scoped packages to private', async () => {
-    const plugin = new NPMPlugin();
-    const hooks = makeHooks();
-
-    plugin.apply({ hooks, logger: dummyLog() } as Auto.Auto);
-
-    readResult = `
-      {
-        "name": "@scope/test",
-        "private": true
-      }
-    `;
-
-    await hooks.publish.promise(Auto.SEMVER.patch);
-    expect(exec).toHaveBeenCalledWith('npm', ['publish']);
-  });
 });
 
 describe('canary', () => {
@@ -659,7 +651,7 @@ describe('canary', () => {
     );
 
     const value = await hooks.canary.promise(Auto.SEMVER.patch, '');
-    expect(value).toEqual({
+    expect(value).toStrictEqual({
       error: 'No packages were changed. No canary published.'
     });
   });
@@ -718,7 +710,7 @@ describe('canary', () => {
     );
 
     const value = await hooks.canary.promise(Auto.SEMVER.patch, '');
-    expect(value).toEqual({
+    expect(value).toStrictEqual({
       error: 'No packages were changed. No canary published.'
     });
   });
diff --git a/plugins/npm/__tests__/package-config.test.ts b/plugins/npm/__tests__/package-config.test.ts
index f481e3a7..e077253e 100644
--- a/plugins/npm/__tests__/package-config.test.ts
+++ b/plugins/npm/__tests__/package-config.test.ts
@@ -16,13 +16,9 @@ jest.mock('parse-github-url', () => () => parseResult);
 test('should throw without a repo', async () => {
   expect.assertions(1);
 
-  try {
-    await packageConfig();
-  } catch (error) {
-    expect(error.message.trim()).toBe(
-      'Cannot read repo info from package.json'
-    );
-  }
+  await expect(packageConfig()).rejects.toStrictEqual(
+    new Error('Cannot read repo info from package.json')
+  );
 });
 
 test('should throw without an owner', async () => {
@@ -32,13 +28,11 @@ test('should throw without an owner', async () => {
   });
   parseResult = undefined;
 
-  try {
-    await packageConfig();
-  } catch (error) {
-    expect(error.message.trim()).toBe(
+  await expect(packageConfig()).rejects.toStrictEqual(
+    new Error(
       'Cannot read owner and package name from GitHub URL in package.json'
-    );
-  }
+    )
+  );
 });
 
 test('should throw without an package name', async () => {
@@ -50,13 +44,11 @@ test('should throw without an package name', async () => {
     owner: 'black-panther'
   };
 
-  try {
-    await packageConfig();
-  } catch (error) {
-    expect(error.message.trim()).toBe(
+  await expect(packageConfig()).rejects.toStrictEqual(
+    new Error(
       'Cannot read owner and package name from GitHub URL in package.json'
-    );
-  }
+    )
+  );
 });
 
 test('should correctly parse package info', async () => {
@@ -69,7 +61,7 @@ test('should correctly parse package info', async () => {
     name: 'operation-foo'
   };
 
-  expect(await packageConfig()).toEqual({
+  expect(await packageConfig()).toStrictEqual({
     repo: 'operation-foo',
     owner: 'black-panther'
   });
diff --git a/plugins/npm/__tests__/set-npm-token.test.ts b/plugins/npm/__tests__/set-npm-token.test.ts
index ff13d851..a6b3b22b 100644
--- a/plugins/npm/__tests__/set-npm-token.test.ts
+++ b/plugins/npm/__tests__/set-npm-token.test.ts
@@ -1,4 +1,4 @@
-// tslint:disable no-invalid-template-strings
+/* eslint-disable no-template-curly-in-string */
 
 import { dummyLog } from '@auto-it/core/dist/utils/logger';
 import setNpmToken from '../src/set-npm-token';
diff --git a/plugins/released/__tests__/released-label.test.ts b/plugins/released/__tests__/released-label.test.ts
index 7ea7aa9e..42c34ae4 100644
--- a/plugins/released/__tests__/released-label.test.ts
+++ b/plugins/released/__tests__/released-label.test.ts
@@ -49,7 +49,7 @@ describe('release label plugin', () => {
 
     expect(
       await autoHooks.modifyConfig.promise({ labels: {} } as IAutoConfig)
-    ).toEqual({
+    ).toStrictEqual({
       labels: {
         released: {
           description: 'This issue/pull request has been released.',
diff --git a/plugins/slack/__tests__/slack.test.ts b/plugins/slack/__tests__/slack.test.ts
index 0977ffb7..6d43a943 100644
--- a/plugins/slack/__tests__/slack.test.ts
+++ b/plugins/slack/__tests__/slack.test.ts
@@ -33,7 +33,7 @@ describe('postToSlack', () => {
     const plugin = new SlackPlugin('https://custom-slack-url');
     const hooks = makeHooks();
 
-    plugin.postToSlack = jest.fn();
+    jest.spyOn(plugin, 'postToSlack').mockImplementation();
     // @ts-ignore
     plugin.apply({ hooks } as Auto);
 
@@ -50,7 +50,7 @@ describe('postToSlack', () => {
     const plugin = new SlackPlugin('https://custom-slack-url');
     const hooks = makeHooks();
 
-    plugin.postToSlack = jest.fn();
+    jest.spyOn(plugin, 'postToSlack').mockImplementation();
     // @ts-ignore
     plugin.apply({ hooks, options: { dryRun: true } } as Auto);
 
@@ -68,7 +68,7 @@ describe('postToSlack', () => {
     const plugin = new SlackPlugin('https://custom-slack-url');
     const hooks = makeHooks();
 
-    plugin.postToSlack = jest.fn();
+    jest.spyOn(plugin, 'postToSlack').mockImplementation();
     // @ts-ignore
     plugin.apply({ hooks, options: {} } as Auto);
 
@@ -86,7 +86,7 @@ describe('postToSlack', () => {
     const plugin = new SlackPlugin('https://custom-slack-url');
     const hooks = makeHooks();
 
-    plugin.postToSlack = jest.fn();
+    jest.spyOn(plugin, 'postToSlack').mockImplementation();
     // @ts-ignore
     plugin.apply({
       hooks,
@@ -109,7 +109,7 @@ describe('postToSlack', () => {
     const plugin = new SlackPlugin({ url: undefined });
     const hooks = makeHooks();
 
-    plugin.postToSlack = jest.fn();
+    jest.spyOn(plugin, 'postToSlack').mockImplementation();
     // @ts-ignore
     plugin.apply({ hooks, options: {} } as Auto);
 
@@ -126,7 +126,7 @@ describe('postToSlack', () => {
   test('should warn when no token', async () => {
     const plugin = new SlackPlugin('https://custom-slack-url');
     const logger = dummyLog();
-    logger.verbose.warn = jest.fn();
+    jest.spyOn(logger.verbose, 'warn').mockImplementation();
     process.env.SLACK_TOKEN = '';
 
     await plugin.postToSlack(
@@ -195,7 +195,7 @@ describe('postToSlack', () => {
     expect(fetchSpy.mock.calls[0][0]).toBe(
       'https://custom-slack-url?token=MY_TOKEN'
     );
-    expect(fetchSpy.mock.calls[0][1].body.includes('@here'));
+    expect(fetchSpy.mock.calls[0][1].body.includes('@here')).toBe(true);
     expect(fetchSpy.mock.calls[0][1].body).toMatchSnapshot();
   });
 });
diff --git a/plugins/twitter/__tests__/twitter.test.ts b/plugins/twitter/__tests__/twitter.test.ts
index 89b070d0..58e70f69 100644
--- a/plugins/twitter/__tests__/twitter.test.ts
+++ b/plugins/twitter/__tests__/twitter.test.ts
@@ -13,10 +13,8 @@ const mockGit = {
   options: { repo: 'Test-Repo' }
 } as any;
 
-describe('Twitter Plugin', () => {
-  test('should throw without proper tokens set', async () => {
-    expect(() => new TwitterPlugin()).toThrow();
-  });
+test('Twitter Plugin should throw without proper tokens set', async () => {
+  expect(() => new TwitterPlugin()).toThrow();
 });
 
 describe('Twitter Plugin', () => {