diff --git a/.babelrc b/.babelrc
index 58ef8769d905c6105a329d349bccfb9cbf6c36ec..1caf31a8b372456d2af2a06a048e2e5ccb5e71f1 100644
--- a/.babelrc
+++ b/.babelrc
@@ -15,5 +15,12 @@
     "@babel/proposal-object-rest-spread"
   ],
   "sourceMaps": true,
-  "retainLines": true
+  "retainLines": true,
+
+  // https://github.com/facebook/jest/issues/5920
+  "env": {
+    "test": {
+      "plugins": ["dynamic-import-node"]
+    }
+  }
 }
diff --git a/.vscode/launch.json b/.vscode/launch.json
index ac62892b78639fbce05e340d081a2b7631d70505..c72c3d30887e38f7276bf24c7790bfd4e42dadac 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -24,7 +24,7 @@
         "--collectCoverage=false",
         "${fileBasenameNoExtension}"
       ],
-      "env": { "LOG_LEVEL": "debug" },
+      "env": { "NODE_ENV": "test", "LOG_LEVEL": "debug" },
       "console": "integratedTerminal",
       "disableOptimisticBPs": true,
       "windows": {
@@ -40,7 +40,7 @@
       "name": "Jest All",
       "program": "${workspaceFolder}/node_modules/.bin/jest",
       "args": ["--runInBand", "--collectCoverage=false"],
-      "env": { "LOG_LEVEL": "debug" },
+      "env": { "NODE_ENV": "test", "LOG_LEVEL": "debug" },
       "console": "integratedTerminal",
       "disableOptimisticBPs": true,
       "windows": {
diff --git a/lib/datasource/github/index.js b/lib/datasource/github/index.js
index a00b22ad41a5a0f4109d6d1f75e96fe250c75de0..aef7ae9b67fbecfb48d3635d5c86be1f2334f1b4 100644
--- a/lib/datasource/github/index.js
+++ b/lib/datasource/github/index.js
@@ -1,4 +1,5 @@
-const ghGot = require('../../platform/github/gh-got-wrapper');
+import ghGot from '../../platform/github/gh-got-wrapper';
+
 const got = require('../../util/got');
 
 module.exports = {
diff --git a/lib/platform/github/gh-got-wrapper.js b/lib/platform/github/gh-got-wrapper.ts
similarity index 89%
rename from lib/platform/github/gh-got-wrapper.js
rename to lib/platform/github/gh-got-wrapper.ts
index 11dac1623a8fbfd77a9a1930a55f1120adca83c0..8b8662a925e098472c035881c7fe3677624ea87b 100644
--- a/lib/platform/github/gh-got-wrapper.js
+++ b/lib/platform/github/gh-got-wrapper.ts
@@ -1,14 +1,19 @@
-const URL = require('url');
-const parseLinkHeader = require('parse-link-header');
-const pAll = require('p-all');
+import URL from 'url';
+import parseLinkHeader from 'parse-link-header';
+import pAll from 'p-all';
 
-const got = require('../../util/got');
-const { maskToken } = require('../../util/mask');
+import got from '../../util/got';
+import { maskToken } from '../../util/mask';
+import { GotApi } from '../common';
 
 const hostType = 'github';
 let baseUrl = 'https://api.github.com/';
 
-async function get(path, options, okToRetry = true) {
+async function get(
+  path: string,
+  options?: any,
+  okToRetry = true
+): Promise<any> {
   const opts = {
     hostType,
     baseUrl,
@@ -55,14 +60,14 @@ async function get(path, options, okToRetry = true) {
         const queue = pageNumbers.map(page => () => {
           const nextUrl = URL.parse(linkHeader.next.url, true);
           delete nextUrl.search;
-          nextUrl.query.page = page;
+          nextUrl.query.page = page.toString();
           return get(
             URL.format(nextUrl),
             { ...opts, paginate: false },
             okToRetry
           );
         });
-        const pages = await pAll(queue, { concurrency: 5 });
+        const pages = await pAll<{ body: any[] }>(queue, { concurrency: 5 });
         res.body = res.body.concat(
           ...pages.filter(Boolean).map(page => page.body)
         );
@@ -148,7 +153,7 @@ async function get(path, options, okToRetry = true) {
 const helpers = ['get', 'post', 'put', 'patch', 'head', 'delete'];
 
 for (const x of helpers) {
-  get[x] = (url, opts) =>
+  (get as any)[x] = (url: string, opts: any) =>
     get(url, Object.assign({}, opts, { method: x.toUpperCase() }));
 }
 
@@ -156,8 +161,9 @@ get.setAppMode = function setAppMode() {
   // no-op
 };
 
-get.setBaseUrl = u => {
+get.setBaseUrl = (u: string) => {
   baseUrl = u;
 };
 
-module.exports = get;
+export const api: GotApi = get as any;
+export default api;
diff --git a/lib/platform/github/index.js b/lib/platform/github/index.ts
similarity index 80%
rename from lib/platform/github/index.js
rename to lib/platform/github/index.ts
index 840dfe2cd940be983be53ffc249c3ce56ad3a316..eb54fd4d7e813bd2eb706be0f485bad201970896 100644
--- a/lib/platform/github/index.js
+++ b/lib/platform/github/index.ts
@@ -1,94 +1,93 @@
-const is = require('@sindresorhus/is');
-const delay = require('delay');
-const semver = require('semver');
-const URL = require('url');
+import is from '@sindresorhus/is';
+import delay from 'delay';
+import semver from 'semver';
+import URL from 'url';
 
-const get = require('./gh-got-wrapper');
-const hostRules = require('../../util/host-rules');
-const GitStorage = require('../git/storage').Storage;
+import { api } from './gh-got-wrapper';
+import * as hostRules from '../../util/host-rules';
+import GitStorage from '../git/storage';
 
-const {
+import {
   appName,
   appSlug,
   configFileNames,
   urls,
-} = require('../../config/app-strings');
+} from '../../config/app-strings';
 
 const defaultConfigFile = configFileNames[0];
 
-let config = {};
+interface Comment {
+  id: number;
+  body: string;
+}
+
+interface Pr {
+  displayNumber: string;
+  state: string;
+  title: string;
+  branchName: string;
+  number: number;
+  comments: Comment[];
+}
+
+interface RepoConfig {
+  repositoryName: string;
+  pushProtection: boolean;
+  prReviewsRequired: boolean;
+  repoForceRebase?: boolean;
+  storage: GitStorage;
+  parentRepo: string;
+  baseCommitSHA: string | null;
+  forkToken?: string;
+  closedPrList: { [num: number]: Pr } | null;
+  openPrList: { [num: number]: Pr } | null;
+  prList: Pr[] | null;
+  issueList: any[] | null;
+  mergeMethod: string;
+  baseBranch: string;
+  defaultBranch: string;
+  enterpriseVersion: string;
+  gitPrivateKey?: string;
+  repositoryOwner: string;
+  repository: string | null;
+  localDir: string;
+  isGhe: boolean;
+  renovateUsername: string;
+}
+
+let config: RepoConfig = {} as any;
 
 const defaults = {
   hostType: 'github',
   endpoint: 'https://api.github.com/',
 };
 
-module.exports = {
-  // Initialization
-  initPlatform,
-  getRepos,
-  cleanRepo,
-  initRepo,
-  getRepoStatus,
-  getRepoForceRebase,
-  setBaseBranch,
-  setBranchPrefix,
-  // Search
-  getFileList,
-  // Branch
-  branchExists,
-  getAllRenovateBranches,
-  isBranchStale,
-  getBranchPr,
-  getBranchStatus,
-  getBranchStatusCheck,
-  setBranchStatus,
-  deleteBranch,
-  mergeBranch,
-  getBranchLastCommitTime,
-  // issue
-  findIssue,
-  ensureIssue,
-  ensureIssueClosing,
-  addAssignees,
-  addReviewers,
-  deleteLabel,
-  getIssueList,
-  // Comments
-  ensureComment,
-  ensureCommentRemoval,
-  // PR
-  getPrList,
-  findPr,
-  createPr,
-  getPr,
-  getPrFiles,
-  updatePr,
-  mergePr,
-  getPrBody,
-  // file
-  commitFilesToBranch,
-  getFile,
-  // Commits
-  getCommitMessages,
-  // vulnerability alerts
-  getVulnerabilityAlerts,
-};
-
-async function initPlatform({ endpoint, token }) {
+export async function initPlatform({
+  endpoint,
+  token,
+}: {
+  endpoint: string;
+  token: string;
+}) {
   if (!token) {
     throw new Error('Init: You must configure a GitHub personal access token');
   }
-  const res = {};
+  interface PlatformConfig {
+    gitAuthor: string;
+    renovateUsername: string;
+    endpoint: string;
+  }
+
+  const res: PlatformConfig = {} as any;
   if (endpoint) {
     defaults.endpoint = endpoint.replace(/\/?$/, '/'); // always add a trailing slash
-    get.setBaseUrl(defaults.endpoint);
+    api.setBaseUrl(defaults.endpoint);
   } else {
     logger.info('Using default github endpoint: ' + defaults.endpoint);
   }
   res.endpoint = defaults.endpoint;
   try {
-    const userData = (await get(res.endpoint + 'user', {
+    const userData = (await api.get(res.endpoint + 'user', {
       token,
     })).body;
     res.renovateUsername = userData.login;
@@ -98,7 +97,7 @@ async function initPlatform({ endpoint, token }) {
     throw new Error('Init: Authentication failure');
   }
   try {
-    const userEmail = (await get(res.endpoint + 'user/emails', {
+    const userEmail = (await api.get(res.endpoint + 'user/emails', {
       token,
     })).body;
     if (userEmail.length && userEmail[0].email) {
@@ -118,28 +117,28 @@ async function initPlatform({ endpoint, token }) {
 }
 
 // Get all repositories that the user has access to
-async function getRepos() {
+export async function getRepos() {
   logger.info('Autodiscovering GitHub repositories');
   try {
-    const res = await get('user/repos?per_page=100', { paginate: true });
-    return res.body.map(repo => repo.full_name);
+    const res = await api.get('user/repos?per_page=100', { paginate: true });
+    return res.body.map((repo: { full_name: string }) => repo.full_name);
   } catch (err) /* istanbul ignore next */ {
     logger.error({ err }, `GitHub getRepos error`);
     throw err;
   }
 }
 
-function cleanRepo() {
+export function cleanRepo() {
   // istanbul ignore if
   if (config.storage) {
     config.storage.cleanRepo();
   }
   // In theory most of this isn't necessary. In practice..
-  config = {};
+  config = {} as any;
 }
 
 // Initialize GitHub by getting base branch and SHA
-async function initRepo({
+export async function initRepo({
   endpoint,
   repository,
   forkMode,
@@ -148,6 +147,15 @@ async function initRepo({
   localDir,
   includeForks,
   renovateUsername,
+}: {
+  endpoint: string;
+  repository: string;
+  forkMode?: boolean;
+  forkToken?: string;
+  gitPrivateKey?: string;
+  localDir: string;
+  includeForks: boolean;
+  renovateUsername: string;
 }) {
   logger.debug(`initRepo("${repository}")`);
   logger.info('Authenticated as user: ' + renovateUsername);
@@ -159,7 +167,7 @@ async function initRepo({
     // Necessary for Renovate Pro - do not remove
     logger.debug('Overriding default GitHub endpoint');
     defaults.endpoint = endpoint;
-    get.setBaseUrl(endpoint);
+    api.setBaseUrl(endpoint);
   }
   const opts = hostRules.find({
     hostType: 'github',
@@ -172,19 +180,19 @@ async function initRepo({
   [config.repositoryOwner, config.repositoryName] = repository.split('/');
   config.gitPrivateKey = gitPrivateKey;
   // platformConfig is passed back to the app layer and contains info about the platform they require
-  const platformConfig = {};
+  const platformConfig: { privateRepo: boolean; isFork: boolean } = {} as any;
   let res;
   try {
-    res = await get(`repos/${repository}`);
+    res = await api.get(`repos/${repository}`);
     logger.trace({ repositoryDetails: res.body }, 'Repository details');
     config.enterpriseVersion =
-      res.headers && res.headers['x-github-enterprise-version'];
+      res.headers && (res.headers['x-github-enterprise-version'] as string);
     // istanbul ignore if
     if (res.body.fork && !includeForks) {
       try {
         const renovateConfig = JSON.parse(
           Buffer.from(
-            (await get(
+            (await api.get(
               `repos/${config.repository}/contents/${defaultConfigFile}`
             )).body.content,
             'base64'
@@ -268,19 +276,22 @@ async function initRepo({
     config.parentRepo = config.repository;
     config.repository = null;
     // Get list of existing repos
-    const existingRepos = (await get('user/repos?per_page=100', {
-      token: forkToken || opts.token,
-      paginate: true,
-    })).body.map(r => r.full_name);
+    const existingRepos = (await api.get<{ full_name: string }[]>(
+      'user/repos?per_page=100',
+      {
+        token: forkToken || opts.token,
+        paginate: true,
+      }
+    )).body.map(r => r.full_name);
     try {
-      config.repository = (await get.post(`repos/${repository}/forks`, {
+      config.repository = (await api.post(`repos/${repository}/forks`, {
         token: forkToken || opts.token,
       })).body.full_name;
     } catch (err) /* istanbul ignore next */ {
       logger.info({ err }, 'Error forking repository');
       throw new Error('cannot-fork');
     }
-    if (existingRepos.includes(config.repository)) {
+    if (existingRepos.includes(config.repository!)) {
       logger.info(
         { repository_fork: config.repository },
         'Found existing fork'
@@ -293,7 +304,7 @@ async function initRepo({
       // This is a lovely "hack" by GitHub that lets us force update our fork's master
       // with the base commit from the parent repository
       try {
-        await get.patch(
+        await api.patch(
           `repos/${config.repository}/git/refs/heads/${config.baseBranch}`,
           {
             body: {
@@ -333,7 +344,7 @@ async function initRepo({
     logger.debug('Using personal access token for git init');
     parsedEndpoint.auth = opts.token;
   }
-  parsedEndpoint.host = parsedEndpoint.host.replace(
+  parsedEndpoint.host = parsedEndpoint.host!.replace(
     'api.github.com',
     'github.com'
   );
@@ -348,7 +359,7 @@ async function initRepo({
   return platformConfig;
 }
 
-async function getRepoForceRebase() {
+export async function getRepoForceRebase() {
   if (config.repoForceRebase === undefined) {
     try {
       config.repoForceRebase = false;
@@ -394,9 +405,9 @@ async function getRepoForceRebase() {
 }
 
 // Return the commit SHA for a branch
-async function getBranchCommit(branchName) {
+async function getBranchCommit(branchName: string) {
   try {
-    const res = await get(
+    const res = await api.get(
       `repos/${config.repository}/git/refs/heads/${branchName}`
     );
     return res.body.object.sha;
@@ -419,75 +430,75 @@ async function getBaseCommitSHA() {
   return config.baseCommitSHA;
 }
 
-async function getBranchProtection(branchName) {
+async function getBranchProtection(branchName: string) {
   // istanbul ignore if
   if (config.parentRepo) {
     return {};
   }
-  const res = await get(
+  const res = await api.get(
     `repos/${config.repository}/branches/${branchName}/protection`
   );
   return res.body;
 }
 
 // istanbul ignore next
-async function setBaseBranch(branchName = config.baseBranch) {
+export async function setBaseBranch(branchName = config.baseBranch) {
   config.baseBranch = branchName;
   config.baseCommitSHA = null;
   await config.storage.setBaseBranch(branchName);
 }
 
 // istanbul ignore next
-function setBranchPrefix(branchPrefix) {
+export function setBranchPrefix(branchPrefix: string) {
   return config.storage.setBranchPrefix(branchPrefix);
 }
 
 // Search
 
 // istanbul ignore next
-function getFileList(branchName = config.baseBranch) {
+export function getFileList(branchName = config.baseBranch) {
   return config.storage.getFileList(branchName);
 }
 
 // Branch
 
 // istanbul ignore next
-function branchExists(branchName) {
+export function branchExists(branchName: string) {
   return config.storage.branchExists(branchName);
 }
 
 // istanbul ignore next
-function getAllRenovateBranches(branchPrefix) {
+export function getAllRenovateBranches(branchPrefix: string) {
   return config.storage.getAllRenovateBranches(branchPrefix);
 }
 
 // istanbul ignore next
-function isBranchStale(branchName) {
+export function isBranchStale(branchName: string) {
   return config.storage.isBranchStale(branchName);
 }
 
 // istanbul ignore next
-function getFile(filePath, branchName) {
+export function getFile(filePath: string, branchName?: string) {
   return config.storage.getFile(filePath, branchName);
 }
 
 // istanbul ignore next
-function deleteBranch(branchName) {
+export function deleteBranch(branchName: string) {
   return config.storage.deleteBranch(branchName);
 }
 
 // istanbul ignore next
-function getBranchLastCommitTime(branchName) {
+export function getBranchLastCommitTime(branchName: string) {
   return config.storage.getBranchLastCommitTime(branchName);
 }
 
 // istanbul ignore next
-function getRepoStatus() {
+export function getRepoStatus() {
   return config.storage.getRepoStatus();
 }
 
 // istanbul ignore next
-function mergeBranch(branchName) {
+export function mergeBranch(branchName: string) {
   if (config.pushProtection) {
     logger.info(
       { branch: branchName },
@@ -498,10 +509,10 @@ function mergeBranch(branchName) {
 }
 
 // istanbul ignore next
-function commitFilesToBranch(
-  branchName,
-  files,
-  message,
+export function commitFilesToBranch(
+  branchName: string,
+  files: any[],
+  message: string,
   parentBranch = config.baseBranch
 ) {
   return config.storage.commitFilesToBranch(
@@ -513,19 +524,22 @@ function commitFilesToBranch(
 }
 
 // istanbul ignore next
-function getCommitMessages() {
+export function getCommitMessages() {
   return config.storage.getCommitMessages();
 }
 
 // Returns the Pull Request for a branch. Null if not exists.
-async function getBranchPr(branchName) {
+export async function getBranchPr(branchName: string) {
   logger.debug(`getBranchPr(${branchName})`);
   const existingPr = await findPr(branchName, null, 'open');
   return existingPr ? getPr(existingPr.number) : null;
 }
 
 // Returns the combined status for a branch.
-async function getBranchStatus(branchName, requiredStatusChecks) {
+export async function getBranchStatus(
+  branchName: string,
+  requiredStatusChecks: any
+) {
   logger.debug(`getBranchStatus(${branchName})`);
   if (!requiredStatusChecks) {
     // null means disable status checks, so it always succeeds
@@ -540,7 +554,7 @@ async function getBranchStatus(branchName, requiredStatusChecks) {
   const commitStatusUrl = `repos/${config.repository}/commits/${branchName}/status`;
   let commitStatus;
   try {
-    commitStatus = (await get(commitStatusUrl)).body;
+    commitStatus = (await api.get(commitStatusUrl)).body;
   } catch (err) /* istanbul ignore next */ {
     if (err.statusCode === 404) {
       logger.info(
@@ -555,7 +569,7 @@ async function getBranchStatus(branchName, requiredStatusChecks) {
     { state: commitStatus.state, statuses: commitStatus.statuses },
     'branch status check result'
   );
-  let checkRuns = [];
+  let checkRuns: { name: string; status: string; conclusion: string }[] = [];
   if (!config.isGhe) {
     try {
       const checkRunsUrl = `repos/${config.repository}/commits/${branchName}/check-runs`;
@@ -564,13 +578,15 @@ async function getBranchStatus(branchName, requiredStatusChecks) {
           Accept: 'application/vnd.github.antiope-preview+json',
         },
       };
-      const checkRunsRaw = (await get(checkRunsUrl, opts)).body;
+      const checkRunsRaw = (await api.get(checkRunsUrl, opts)).body;
       if (checkRunsRaw.check_runs && checkRunsRaw.check_runs.length) {
-        checkRuns = checkRunsRaw.check_runs.map(run => ({
-          name: run.name,
-          status: run.status,
-          conclusion: run.conclusion,
-        }));
+        checkRuns = checkRunsRaw.check_runs.map(
+          (run: { name: string; status: string; conclusion: string }) => ({
+            name: run.name,
+            status: run.status,
+            conclusion: run.conclusion,
+          })
+        );
         logger.debug({ checkRuns }, 'check runs result');
       } else {
         // istanbul ignore next
@@ -605,10 +621,13 @@ async function getBranchStatus(branchName, requiredStatusChecks) {
   return 'pending';
 }
 
-async function getBranchStatusCheck(branchName, context) {
+export async function getBranchStatusCheck(
+  branchName: string,
+  context: string
+) {
   const branchCommit = await config.storage.getBranchCommit(branchName);
   const url = `repos/${config.repository}/commits/${branchCommit}/statuses`;
-  const res = await get(url);
+  const res = await api.get(url);
   for (const check of res.body) {
     if (check.context === context) {
       return check.state;
@@ -617,12 +636,12 @@ async function getBranchStatusCheck(branchName, context) {
   return null;
 }
 
-async function setBranchStatus(
-  branchName,
-  context,
-  description,
-  state,
-  targetUrl
+export async function setBranchStatus(
+  branchName: string,
+  context: string,
+  description: string,
+  state: string,
+  targetUrl: string
 ) {
   // istanbul ignore if
   if (config.parentRepo) {
@@ -636,7 +655,7 @@ async function setBranchStatus(
   logger.info({ branch: branchName, context, state }, 'Setting branch status');
   const branchCommit = await config.storage.getBranchCommit(branchName);
   const url = `repos/${config.repository}/statuses/${branchCommit}`;
-  const options = {
+  const options: any = {
     state,
     description,
     context,
@@ -644,13 +663,13 @@ async function setBranchStatus(
   if (targetUrl) {
     options.target_url = targetUrl;
   }
-  await get.post(url, { body: options });
+  await api.post(url, { body: options });
 }
 
 // Issue
 
 /* istanbul ignore next */
-async function getGraphqlIssues(afterCursor = null) {
+async function getGraphqlIssues(afterCursor: string | null = null) {
   const url = 'graphql';
   const headers = {
     accept: 'application/vnd.github.merge-info-preview+json',
@@ -682,7 +701,7 @@ async function getGraphqlIssues(afterCursor = null) {
   };
 
   try {
-    const res = JSON.parse((await get.post(url, options)).body);
+    const res = JSON.parse((await api.post(url, options)).body);
 
     if (!res.data) {
       logger.info({ query, res }, 'No graphql res.data');
@@ -703,7 +722,14 @@ async function getGraphqlIssues(afterCursor = null) {
 // istanbul ignore next
 async function getRestIssues() {
   logger.debug('Retrieving issueList');
-  const res = await get(
+  const res = await api.get<
+    {
+      pull_request: boolean;
+      number: number;
+      state: string;
+      title: string;
+    }[]
+  >(
     `repos/${config.repository}/issues?creator=${config.renovateUsername}&state=all&per_page=100&sort=created&direction=asc`,
     { paginate: 'all', useCache: false }
   );
@@ -721,7 +747,7 @@ async function getRestIssues() {
     }));
 }
 
-async function getIssueList() {
+export async function getIssueList() {
   if (!config.issueList) {
     logger.debug('Retrieving issueList');
     const filterBySupportMinimumGheVersion = '2.17.0';
@@ -752,7 +778,7 @@ async function getIssueList() {
   return config.issueList;
 }
 
-async function findIssue(title) {
+export async function findIssue(title: string) {
   logger.debug(`findIssue(${title})`);
   const [issue] = (await getIssueList()).filter(
     i => i.state === 'open' && i.title === title
@@ -761,7 +787,7 @@ async function findIssue(title) {
     return null;
   }
   logger.debug('Found issue ' + issue.number);
-  const issueBody = (await get(
+  const issueBody = (await api.get(
     `repos/${config.parentRepo || config.repository}/issues/${issue.number}`
   )).body.body;
   return {
@@ -770,7 +796,7 @@ async function findIssue(title) {
   };
 }
 
-async function ensureIssue(title, body, once = false) {
+export async function ensureIssue(title: string, body: string, once = false) {
   logger.debug(`ensureIssue()`);
   try {
     const issueList = await getIssueList();
@@ -791,7 +817,7 @@ async function ensureIssue(title, body, once = false) {
           await closeIssue(i.number);
         }
       }
-      const issueBody = (await get(
+      const issueBody = (await api.get(
         `repos/${config.parentRepo || config.repository}/issues/${issue.number}`
       )).body.body;
       if (issueBody === body && issue.state === 'open') {
@@ -799,7 +825,7 @@ async function ensureIssue(title, body, once = false) {
         return null;
       }
       logger.info('Patching issue');
-      await get.patch(
+      await api.patch(
         `repos/${config.parentRepo || config.repository}/issues/${
           issue.number
         }`,
@@ -810,7 +836,7 @@ async function ensureIssue(title, body, once = false) {
       logger.info('Issue updated');
       return 'updated';
     }
-    await get.post(`repos/${config.parentRepo || config.repository}/issues`, {
+    await api.post(`repos/${config.parentRepo || config.repository}/issues`, {
       body: {
         title,
         body,
@@ -836,9 +862,9 @@ async function ensureIssue(title, body, once = false) {
   return null;
 }
 
-async function closeIssue(issueNumber) {
+async function closeIssue(issueNumber: number) {
   logger.debug(`closeIssue(${issueNumber})`);
-  await get.patch(
+  await api.patch(
     `repos/${config.parentRepo || config.repository}/issues/${issueNumber}`,
     {
       body: { state: 'closed' },
@@ -846,7 +872,7 @@ async function closeIssue(issueNumber) {
   );
 }
 
-async function ensureIssueClosing(title) {
+export async function ensureIssueClosing(title: string) {
   logger.debug(`ensureIssueClosing(${title})`);
   const issueList = await getIssueList();
   for (const issue of issueList) {
@@ -857,17 +883,17 @@ async function ensureIssueClosing(title) {
   }
 }
 
-async function addAssignees(issueNo, assignees) {
+export async function addAssignees(issueNo: number, assignees: string[]) {
   logger.debug(`Adding assignees ${assignees} to #${issueNo}`);
   const repository = config.parentRepo || config.repository;
-  await get.post(`repos/${repository}/issues/${issueNo}/assignees`, {
+  await api.post(`repos/${repository}/issues/${issueNo}/assignees`, {
     body: {
       assignees,
     },
   });
 }
 
-async function addReviewers(prNo, reviewers) {
+export async function addReviewers(prNo: number, reviewers: string[]) {
   logger.debug(`Adding reviewers ${reviewers} to #${prNo}`);
 
   const userReviewers = reviewers.filter(e => !e.startsWith('team:'));
@@ -875,7 +901,7 @@ async function addReviewers(prNo, reviewers) {
     .filter(e => e.startsWith('team:'))
     .map(e => e.replace(/^team:/, ''));
 
-  await get.post(
+  await api.post(
     `repos/${config.parentRepo ||
       config.repository}/pulls/${prNo}/requested_reviewers`,
     {
@@ -887,27 +913,27 @@ async function addReviewers(prNo, reviewers) {
   );
 }
 
-async function addLabels(issueNo, labels) {
+async function addLabels(issueNo: number, labels: string[] | null) {
   logger.debug(`Adding labels ${labels} to #${issueNo}`);
   const repository = config.parentRepo || config.repository;
   if (is.array(labels) && labels.length) {
-    await get.post(`repos/${repository}/issues/${issueNo}/labels`, {
+    await api.post(`repos/${repository}/issues/${issueNo}/labels`, {
       body: labels,
     });
   }
 }
 
-async function deleteLabel(issueNo, label) {
+export async function deleteLabel(issueNo: number, label: string) {
   logger.debug(`Deleting label ${label} from #${issueNo}`);
   const repository = config.parentRepo || config.repository;
   try {
-    await get.delete(`repos/${repository}/issues/${issueNo}/labels/${label}`);
+    await api.delete(`repos/${repository}/issues/${issueNo}/labels/${label}`);
   } catch (err) /* istanbul ignore next */ {
     logger.warn({ err, issueNo, label }, 'Failed to delete label');
   }
 }
 
-async function getComments(issueNo) {
+async function getComments(issueNo: number) {
   const pr = (await getClosedPrs())[issueNo];
   if (pr) {
     logger.debug('Returning closed PR list comments');
@@ -918,7 +944,9 @@ async function getComments(issueNo) {
   const url = `repos/${config.parentRepo ||
     config.repository}/issues/${issueNo}/comments?per_page=100`;
   try {
-    const comments = (await get(url, { paginate: true })).body;
+    const comments = (await api.get<Comment[]>(url, {
+      paginate: true,
+    })).body;
     logger.debug(`Found ${comments.length} comments`);
     return comments;
   } catch (err) /* istanbul ignore next */ {
@@ -930,9 +958,9 @@ async function getComments(issueNo) {
   }
 }
 
-async function addComment(issueNo, body) {
+async function addComment(issueNo: number, body: string) {
   // POST /repos/:owner/:repo/issues/:number/comments
-  await get.post(
+  await api.post(
     `repos/${config.parentRepo ||
       config.repository}/issues/${issueNo}/comments`,
     {
@@ -941,9 +969,9 @@ async function addComment(issueNo, body) {
   );
 }
 
-async function editComment(commentId, body) {
+async function editComment(commentId: number, body: string) {
   // PATCH /repos/:owner/:repo/issues/comments/:id
-  await get.patch(
+  await api.patch(
     `repos/${config.parentRepo ||
       config.repository}/issues/comments/${commentId}`,
     {
@@ -952,20 +980,24 @@ async function editComment(commentId, body) {
   );
 }
 
-async function deleteComment(commentId) {
+async function deleteComment(commentId: number) {
   // DELETE /repos/:owner/:repo/issues/comments/:id
-  await get.delete(
+  await api.delete(
     `repos/${config.parentRepo ||
       config.repository}/issues/comments/${commentId}`
   );
 }
 
-async function ensureComment(issueNo, topic, content) {
+export async function ensureComment(
+  issueNo: number,
+  topic: string | null,
+  content: string
+) {
   try {
     const comments = await getComments(issueNo);
-    let body;
-    let commentId;
-    let commentNeedsUpdating;
+    let body: string;
+    let commentId: number | null = null;
+    let commentNeedsUpdating = false;
     if (topic) {
       logger.debug(`Ensuring comment "${topic}" in #${issueNo}`);
       body = `### ${topic}\n\n${content}`;
@@ -1010,7 +1042,7 @@ async function ensureComment(issueNo, topic, content) {
   }
 }
 
-async function ensureCommentRemoval(issueNo, topic) {
+export async function ensureCommentRemoval(issueNo: number, topic: string) {
   logger.debug(`Ensuring comment "${topic}" in #${issueNo} is removed`);
   const comments = await getComments(issueNo);
   let commentId;
@@ -1030,34 +1062,45 @@ async function ensureCommentRemoval(issueNo, topic) {
 
 // Pull Request
 
-async function getPrList() {
+export async function getPrList() {
   logger.trace('getPrList()');
   if (!config.prList) {
     logger.debug('Retrieving PR list');
-    const res = await get(
+    const res = await api.get(
       `repos/${config.parentRepo ||
         config.repository}/pulls?per_page=100&state=all`,
       { paginate: true }
     );
-    config.prList = res.body.map(pr => ({
-      number: pr.number,
-      branchName: pr.head.ref,
-      sha: pr.head.sha,
-      title: pr.title,
-      state:
-        pr.state === 'closed' && pr.merged_at && pr.merged_at.length
-          ? /* istanbul ignore next */ 'merged'
-          : pr.state,
-      createdAt: pr.created_at,
-      closed_at: pr.closed_at,
-      sourceRepo: pr.head && pr.head.repo ? pr.head.repo.full_name : undefined,
-    }));
-    logger.debug(`Retrieved ${config.prList.length} Pull Requests`);
+    config.prList = res.body.map(
+      (pr: {
+        number: number;
+        head: { ref: string; sha: string; repo: { full_name: string } };
+        title: string;
+        state: string;
+        merged_at: string;
+        created_at: string;
+        closed_at: string;
+      }) => ({
+        number: pr.number,
+        branchName: pr.head.ref,
+        sha: pr.head.sha,
+        title: pr.title,
+        state:
+          pr.state === 'closed' && pr.merged_at && pr.merged_at.length
+            ? /* istanbul ignore next */ 'merged'
+            : pr.state,
+        createdAt: pr.created_at,
+        closed_at: pr.closed_at,
+        sourceRepo:
+          pr.head && pr.head.repo ? pr.head.repo.full_name : undefined,
+      })
+    );
+    logger.debug(`Retrieved ${config.prList!.length} Pull Requests`);
   }
-  return config.prList;
+  return config.prList!;
 }
 
-function matchesState(state, desiredState) {
+function matchesState(state: string, desiredState: string) {
   if (desiredState === 'all') {
     return true;
   }
@@ -1067,7 +1110,11 @@ function matchesState(state, desiredState) {
   return state === desiredState;
 }
 
-async function findPr(branchName, prTitle, state = 'all') {
+export async function findPr(
+  branchName: string,
+  prTitle?: string | null,
+  state = 'all'
+) {
   logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`);
   const prList = await getPrList();
   const pr = prList.find(
@@ -1083,18 +1130,18 @@ async function findPr(branchName, prTitle, state = 'all') {
 }
 
 // Creates PR and returns PR number
-async function createPr(
-  branchName,
-  title,
-  body,
-  labels,
-  useDefaultBranch,
-  platformOptions = {}
+export async function createPr(
+  branchName: string,
+  title: string,
+  body: string,
+  labels: string[] | null,
+  useDefaultBranch: boolean,
+  platformOptions: { statusCheckVerify?: boolean } = {}
 ) {
   const base = useDefaultBranch ? config.defaultBranch : config.baseBranch;
   // Include the repository owner to handle forkMode and regular mode
-  const head = `${config.repository.split('/')[0]}:${branchName}`;
-  const options = {
+  const head = `${config.repository!.split('/')[0]}:${branchName}`;
+  const options: any = {
     body: {
       title,
       head,
@@ -1108,7 +1155,7 @@ async function createPr(
     options.body.maintainer_can_modify = true;
   }
   logger.debug({ title, head, base }, 'Creating PR');
-  const pr = (await get.post(
+  const pr = (await api.post<Pr>(
     `repos/${config.parentRepo || config.repository}/pulls`,
     options
   )).body;
@@ -1202,7 +1249,7 @@ async function getOpenPrs() {
         body: JSON.stringify({ query }),
         json: false,
       };
-      const res = JSON.parse((await get.post(url, options)).body);
+      const res = JSON.parse((await api.post(url, options)).body);
       const prNumbers = [];
       // istanbul ignore if
       if (!res.data) {
@@ -1277,7 +1324,9 @@ async function getOpenPrs() {
           }
         }
         if (pr.labels) {
-          pr.labels = pr.labels.nodes.map(label => label.name);
+          pr.labels = pr.labels.nodes.map(
+            (label: { name: string }) => label.name
+          );
         }
         delete pr.mergeable;
         delete pr.mergeStateStatus;
@@ -1325,7 +1374,7 @@ async function getClosedPrs() {
         body: JSON.stringify({ query }),
         json: false,
       };
-      const res = JSON.parse((await get.post(url, options)).body);
+      const res = JSON.parse((await api.post(url, options)).body);
       const prNumbers = [];
       // istanbul ignore if
       if (!res.data) {
@@ -1341,10 +1390,12 @@ async function getClosedPrs() {
         pr.state = pr.state.toLowerCase();
         pr.branchName = pr.headRefName;
         delete pr.headRefName;
-        pr.comments = pr.comments.nodes.map(comment => ({
-          id: comment.databaseId,
-          body: comment.body,
-        }));
+        pr.comments = pr.comments.nodes.map(
+          (comment: { databaseId: number; body: string }) => ({
+            id: comment.databaseId,
+            body: comment.body,
+          })
+        );
         pr.body = 'dummy body'; // just in case
         config.closedPrList[pr.number] = pr;
         prNumbers.push(pr.number);
@@ -1359,7 +1410,7 @@ async function getClosedPrs() {
 }
 
 // Gets details for a PR
-async function getPr(prNo) {
+export async function getPr(prNo: number) {
   if (!prNo) {
     return null;
   }
@@ -1377,7 +1428,7 @@ async function getPr(prNo) {
     { prNo },
     'PR not found in open or closed PRs list - trying to fetch it directly'
   );
-  const pr = (await get(
+  const pr = (await api.get(
     `repos/${config.parentRepo || config.repository}/pulls/${prNo}`
   )).body;
   if (!pr) {
@@ -1398,7 +1449,7 @@ async function getPr(prNo) {
     if (pr.commits === 1) {
       if (global.gitAuthor) {
         // Check against gitAuthor
-        const commitAuthorEmail = (await get(
+        const commitAuthorEmail = (await api.get(
           `repos/${config.parentRepo ||
             config.repository}/pulls/${prNo}/commits`
         )).body[0].commit.author.email;
@@ -1429,28 +1480,33 @@ async function getPr(prNo) {
     } else {
       // Check if only one author of all commits
       logger.debug({ prNo }, 'Checking all commits');
-      const prCommits = (await get(
+      const prCommits = (await api.get(
         `repos/${config.parentRepo || config.repository}/pulls/${prNo}/commits`
       )).body;
       // Filter out "Update branch" presses
-      const remainingCommits = prCommits.filter(commit => {
-        const isWebflow =
-          commit.committer && commit.committer.login === 'web-flow';
-        if (!isWebflow) {
-          // Not a web UI commit, so keep it
+      const remainingCommits = prCommits.filter(
+        (commit: {
+          committer: { login: string };
+          commit: { message: string };
+        }) => {
+          const isWebflow =
+            commit.committer && commit.committer.login === 'web-flow';
+          if (!isWebflow) {
+            // Not a web UI commit, so keep it
+            return true;
+          }
+          const isUpdateBranch =
+            commit.commit &&
+            commit.commit.message &&
+            commit.commit.message.startsWith("Merge branch 'master' into");
+          if (isUpdateBranch) {
+            // They just clicked the button
+            return false;
+          }
+          // They must have done some other edit through the web UI
           return true;
         }
-        const isUpdateBranch =
-          commit.commit &&
-          commit.commit.message &&
-          commit.commit.message.startsWith("Merge branch 'master' into");
-        if (isUpdateBranch) {
-          // They just clicked the button
-          return false;
-        }
-        // They must have done some other edit through the web UI
-        return true;
-      });
+      );
       if (remainingCommits.length <= 1) {
         pr.canRebase = true;
       }
@@ -1464,24 +1520,24 @@ async function getPr(prNo) {
 }
 
 // Return a list of all modified files in a PR
-async function getPrFiles(prNo) {
+export async function getPrFiles(prNo: number) {
   logger.debug({ prNo }, 'getPrFiles');
   if (!prNo) {
     return [];
   }
-  const files = (await get(
+  const files = (await api.get(
     `repos/${config.parentRepo || config.repository}/pulls/${prNo}/files`
   )).body;
-  return files.map(f => f.filename);
+  return files.map((f: { filename: string }) => f.filename);
 }
 
-async function updatePr(prNo, title, body) {
+export async function updatePr(prNo: number, title: string, body: string) {
   logger.debug(`updatePr(${prNo}, ${title}, body)`);
-  const patchBody = { title };
+  const patchBody: any = { title };
   if (body) {
     patchBody.body = body;
   }
-  const options = {
+  const options: any = {
     body: patchBody,
   };
   // istanbul ignore if
@@ -1489,7 +1545,7 @@ async function updatePr(prNo, title, body) {
     options.token = config.forkToken;
   }
   try {
-    await get.patch(
+    await api.patch(
       `repos/${config.parentRepo || config.repository}/pulls/${prNo}`,
       options
     );
@@ -1502,7 +1558,7 @@ async function updatePr(prNo, title, body) {
   }
 }
 
-async function mergePr(prNo, branchName) {
+export async function mergePr(prNo: number, branchName: string) {
   logger.debug(`mergePr(${prNo}, ${branchName})`);
   // istanbul ignore if
   if (config.pushProtection) {
@@ -1519,8 +1575,10 @@ async function mergePr(prNo, branchName) {
       'Branch protection: Attempting to merge PR when PR reviews are enabled'
     );
     const repository = config.parentRepo || config.repository;
-    const reviews = await get(`repos/${repository}/pulls/${prNo}/reviews`);
-    const isApproved = reviews.body.some(review => review.state === 'APPROVED');
+    const reviews = await api.get(`repos/${repository}/pulls/${prNo}/reviews`);
+    const isApproved = reviews.body.some(
+      (review: { state: string }) => review.state === 'APPROVED'
+    );
     if (!isApproved) {
       logger.info(
         { branch: branchName, prNo },
@@ -1533,7 +1591,7 @@ async function mergePr(prNo, branchName) {
   const url = `repos/${config.parentRepo ||
     config.repository}/pulls/${prNo}/merge`;
   const options = {
-    body: {},
+    body: {} as any,
   };
   let automerged = false;
   if (config.mergeMethod) {
@@ -1541,7 +1599,7 @@ async function mergePr(prNo, branchName) {
     options.body.merge_method = config.mergeMethod;
     try {
       logger.debug({ options, url }, `mergePr`);
-      await get.put(url, options);
+      await api.put(url, options);
       automerged = true;
     } catch (err) {
       if (err.statusCode === 405) {
@@ -1561,13 +1619,13 @@ async function mergePr(prNo, branchName) {
     options.body.merge_method = 'rebase';
     try {
       logger.debug({ options, url }, `mergePr`);
-      await get.put(url, options);
+      await api.put(url, options);
     } catch (err1) {
       logger.debug({ err: err1 }, `Failed to ${options.body.merge_method} PR`);
       try {
         options.body.merge_method = 'squash';
         logger.debug({ options, url }, `mergePr`);
-        await get.put(url, options);
+        await api.put(url, options);
       } catch (err2) {
         logger.debug(
           { err: err2 },
@@ -1576,7 +1634,7 @@ async function mergePr(prNo, branchName) {
         try {
           options.body.merge_method = 'merge';
           logger.debug({ options, url }, `mergePr`);
-          await get.put(url, options);
+          await api.put(url, options);
         } catch (err3) {
           logger.debug(
             { err: err3 },
@@ -1597,7 +1655,7 @@ async function mergePr(prNo, branchName) {
 }
 
 // istanbul ignore next
-function smartTruncate(input) {
+function smartTruncate(input: string) {
   if (input.length < 60000) {
     return input;
   }
@@ -1619,7 +1677,7 @@ function smartTruncate(input) {
   return input.substring(0, 60000);
 }
 
-function getPrBody(input) {
+export function getPrBody(input: string) {
   if (config.isGhe) {
     return smartTruncate(input);
   }
@@ -1631,7 +1689,7 @@ function getPrBody(input) {
   return smartTruncate(massagedInput);
 }
 
-async function getVulnerabilityAlerts() {
+export async function getVulnerabilityAlerts() {
   // istanbul ignore if
   if (config.isGhe) {
     logger.debug(
@@ -1677,10 +1735,10 @@ async function getVulnerabilityAlerts() {
   };
   let alerts = [];
   try {
-    const res = JSON.parse((await get.post(url, options)).body);
+    const res = JSON.parse((await api.post(url, options)).body);
     if (res.data.repository.vulnerabilityAlerts) {
       alerts = res.data.repository.vulnerabilityAlerts.edges.map(
-        edge => edge.node
+        (edge: { node: any }) => edge.node
       );
       if (alerts.length) {
         logger.info({ alerts }, 'Found GitHub vulnerability alerts');
diff --git a/lib/types.d.ts b/lib/types.d.ts
index 2b8c8fbfa91aa3e1b21c2a78eab21764fab6004e..572e06a9ad460bfe9440fe61c86efaef6ab34a5a 100644
--- a/lib/types.d.ts
+++ b/lib/types.d.ts
@@ -23,7 +23,10 @@ declare interface Error {
 
 declare namespace NodeJS {
   interface Global {
+    appMode?: boolean;
     gitAuthor?: { name: string; email: string };
     logger: Renovate.Logger;
+
+    renovateVersion: string;
   }
 }
diff --git a/lib/workers/pr/changelog/release-notes.js b/lib/workers/pr/changelog/release-notes.js
index 704cfbf0bbc0a3cfa615bde61d9548dd52c6c2b1..3e5b935853b851197e324b29d357d08790464cb4 100644
--- a/lib/workers/pr/changelog/release-notes.js
+++ b/lib/workers/pr/changelog/release-notes.js
@@ -1,7 +1,8 @@
+import ghGot from '../../../platform/github/gh-got-wrapper';
+
 const changelogFilenameRegex = require('changelog-filename-regex');
 const { linkify } = require('linkify-markdown');
 const MarkdownIt = require('markdown-it');
-const ghGot = require('../../../platform/github/gh-got-wrapper');
 
 const markdown = new MarkdownIt('zero');
 markdown.enable(['heading', 'lheading']);
diff --git a/lib/workers/pr/changelog/source-github.js b/lib/workers/pr/changelog/source-github.js
index 305d0c04b6b36766935b3b2fbb7c8ca62e6e08c7..0162daacc44e97d72a9e2d97da2148da663aacc3 100644
--- a/lib/workers/pr/changelog/source-github.js
+++ b/lib/workers/pr/changelog/source-github.js
@@ -1,7 +1,8 @@
+import ghGot from '../../../platform/github/gh-got-wrapper';
+
 const URL = require('url');
 const hostRules = require('../../../util/host-rules');
 const versioning = require('../../../versioning');
-const ghGot = require('../../../platform/github/gh-got-wrapper');
 const { addReleaseNotes } = require('./release-notes');
 
 module.exports = {
diff --git a/package.json b/package.json
index 7a7b72d19bbe0d79fced2cca6e7fa0ebe702fa4e..d510c706216590b6827fd0864fe6ba02c94e1d9e 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
     "eslint-fix": "eslint --ext .js,.ts --fix lib/ test/",
     "jest": "yarn clean-cache && cross-env NODE_ENV=test LOG_LEVEL=fatal jest",
     "jest-debug": "cross-env NODE_ENV=test LOG_LEVEL=fatal node --inspect-brk node_modules/jest/bin/jest.js",
-    "jest-silent": "yarn jest --reporters jest-silent-reporter",
+    "jest-silent": "cross-env NODE_ENV=test yarn jest --reporters jest-silent-reporter",
     "lint": "yarn eslint && yarn prettier",
     "lint-fix": "yarn eslint-fix && yarn prettier-fix",
     "prettier": "prettier --list-different \"**/*.{ts,js,json,md}\"",
@@ -157,6 +157,7 @@
     "@babel/node": "7.4.5",
     "@babel/plugin-proposal-class-properties": "7.4.4",
     "@babel/plugin-proposal-object-rest-spread": "7.4.4",
+    "@babel/plugin-syntax-dynamic-import": "7.2.0",
     "@babel/preset-env": "7.4.5",
     "@babel/preset-typescript": "7.3.3",
     "@types/bunyan": "1.8.6",
@@ -166,10 +167,12 @@
     "@types/jest": "24.0.15",
     "@types/node": "11.13.15",
     "@types/parse-link-header": "1.0.0",
+    "@types/semver": "6.0.1",
     "@types/tmp": "0.1.0",
     "@typescript-eslint/eslint-plugin": "1.11.0",
     "@typescript-eslint/parser": "1.11.0",
     "babel-jest": "24.8.0",
+    "babel-plugin-dynamic-import-node": "2.3.0",
     "chai": "4.2.0",
     "copyfiles": "2.1.0",
     "cross-env": "5.2.0",
diff --git a/test/datasource/github.spec.js b/test/datasource/github.spec.js
index 562553254f1dbffaa225cf420f385fa2fe511c40..6e4871fd9143a77c6a7eef2095ed90f61854cec7 100644
--- a/test/datasource/github.spec.js
+++ b/test/datasource/github.spec.js
@@ -1,6 +1,7 @@
+import ghGot from '../../lib/platform/github/gh-got-wrapper';
+
 const datasource = require('../../lib/datasource');
 const github = require('../../lib/datasource/github');
-const ghGot = require('../../lib/platform/github/gh-got-wrapper');
 const got = require('../../lib/util/got');
 const hostRules = require('../../lib/util/host-rules');
 
diff --git a/test/platform/__snapshots__/index.spec.js.snap b/test/platform/__snapshots__/index.spec.js.snap
index 079eeee37eda8ac8d6306f80a9f9c3c39972c7ac..23316d10118bf9cbf6817da0e1d037d21949770b 100644
--- a/test/platform/__snapshots__/index.spec.js.snap
+++ b/test/platform/__snapshots__/index.spec.js.snap
@@ -47,46 +47,46 @@ Array [
 
 exports[`platform has a list of supported methods for github 1`] = `
 Array [
-  "initPlatform",
-  "getRepos",
-  "cleanRepo",
-  "initRepo",
-  "getRepoStatus",
-  "getRepoForceRebase",
-  "setBaseBranch",
-  "setBranchPrefix",
-  "getFileList",
-  "branchExists",
-  "getAllRenovateBranches",
-  "isBranchStale",
-  "getBranchPr",
-  "getBranchStatus",
-  "getBranchStatusCheck",
-  "setBranchStatus",
-  "deleteBranch",
-  "mergeBranch",
-  "getBranchLastCommitTime",
-  "findIssue",
-  "ensureIssue",
-  "ensureIssueClosing",
   "addAssignees",
   "addReviewers",
+  "branchExists",
+  "cleanRepo",
+  "commitFilesToBranch",
+  "createPr",
+  "deleteBranch",
   "deleteLabel",
-  "getIssueList",
   "ensureComment",
   "ensureCommentRemoval",
-  "getPrList",
+  "ensureIssue",
+  "ensureIssueClosing",
+  "findIssue",
   "findPr",
-  "createPr",
+  "getAllRenovateBranches",
+  "getBranchLastCommitTime",
+  "getBranchPr",
+  "getBranchStatus",
+  "getBranchStatusCheck",
+  "getCommitMessages",
+  "getFile",
+  "getFileList",
+  "getIssueList",
   "getPr",
-  "getPrFiles",
-  "updatePr",
-  "mergePr",
   "getPrBody",
-  "commitFilesToBranch",
-  "getFile",
-  "getCommitMessages",
+  "getPrFiles",
+  "getPrList",
+  "getRepoForceRebase",
+  "getRepoStatus",
+  "getRepos",
   "getVulnerabilityAlerts",
+  "initPlatform",
+  "initRepo",
+  "isBranchStale",
+  "mergeBranch",
+  "mergePr",
+  "setBaseBranch",
+  "setBranchPrefix",
+  "setBranchStatus",
+  "updatePr",
 ]
 `;
 
diff --git a/test/platform/github/__snapshots__/index.spec.js.snap b/test/platform/github/__snapshots__/index.spec.ts.snap
similarity index 100%
rename from test/platform/github/__snapshots__/index.spec.js.snap
rename to test/platform/github/__snapshots__/index.spec.ts.snap
diff --git a/test/platform/github/gh-got-wrapper.spec.js b/test/platform/github/gh-got-wrapper.spec.ts
similarity index 81%
rename from test/platform/github/gh-got-wrapper.spec.js
rename to test/platform/github/gh-got-wrapper.spec.ts
index 179642b79a99f42f3757f387748f26b90c903f61..73c3d725cbbb0b5390f2ac07bc7669570b6e7ec3 100644
--- a/test/platform/github/gh-got-wrapper.spec.js
+++ b/test/platform/github/gh-got-wrapper.spec.ts
@@ -1,19 +1,26 @@
-const delay = require('delay');
-const got = require('../../../lib/util/got');
-const get = require('../../../lib/platform/github/gh-got-wrapper');
+import delay from 'delay';
+import { Response } from 'got';
+import got from '../../../lib/util/got';
+import { api } from '../../../lib/platform/github/gh-got-wrapper';
 
 jest.mock('../../../lib/util/got');
 jest.mock('delay');
 
+const get: <T extends object = any>(
+  path: string,
+  options?: any,
+  okToRetry?: boolean
+) => Promise<Response<T>> = api as any;
+
 describe('platform/gh-got-wrapper', () => {
   beforeEach(() => {
     jest.resetAllMocks();
     delete global.appMode;
-    delay.mockImplementation(() => Promise.resolve());
+    (delay as any).mockImplementation(() => Promise.resolve());
   });
   it('supports app mode', async () => {
     global.appMode = true;
-    await get('some-url', { headers: { accept: 'some-accept' } });
+    await api.get('some-url', { headers: { accept: 'some-accept' } });
     expect(got.mock.calls[0][1].headers.accept).toBe(
       'application/vnd.github.machine-man-preview+json, some-accept'
     );
@@ -22,8 +29,8 @@ describe('platform/gh-got-wrapper', () => {
     got.mockImplementationOnce(() => ({
       body: '{"data":{',
     }));
-    get.setBaseUrl('https://ghe.mycompany.com/api/v3/');
-    await get.post('graphql', {
+    api.setBaseUrl('https://ghe.mycompany.com/api/v3/');
+    await api.post('graphql', {
       body: 'abc',
     });
     expect(got.mock.calls[0][0].includes('/v3')).toBe(false);
@@ -47,7 +54,7 @@ describe('platform/gh-got-wrapper', () => {
       headers: {},
       body: ['d'],
     });
-    const res = await get('some-url', { paginate: true });
+    const res = await api.get('some-url', { paginate: true });
     expect(res.body).toEqual(['a', 'b', 'c', 'd']);
     expect(got).toHaveBeenCalledTimes(3);
   });
@@ -63,7 +70,7 @@ describe('platform/gh-got-wrapper', () => {
       headers: {},
       body: ['b'],
     });
-    const res = await get('some-url', { paginate: true });
+    const res = await api.get('some-url', { paginate: true });
     expect(res.body).toHaveLength(1);
     expect(got).toHaveBeenCalledTimes(1);
   });
@@ -75,7 +82,7 @@ describe('platform/gh-got-wrapper', () => {
           'Error updating branch: API rate limit exceeded for installation ID 48411. (403)',
       })
     );
-    await expect(get('some-url')).rejects.toThrow();
+    await expect(api.get('some-url')).rejects.toThrow();
   });
   it('should throw Bad credentials', async () => {
     got.mockImplementationOnce(() =>
@@ -86,7 +93,7 @@ describe('platform/gh-got-wrapper', () => {
     );
     let e;
     try {
-      await get('some-url');
+      await api.get('some-url');
     } catch (err) {
       e = err;
     }
@@ -105,7 +112,7 @@ describe('platform/gh-got-wrapper', () => {
     );
     let e;
     try {
-      await get('some-url');
+      await api.get('some-url');
     } catch (err) {
       e = err;
     }
@@ -121,7 +128,7 @@ describe('platform/gh-got-wrapper', () => {
     );
     let e;
     try {
-      await get('some-url', {}, 0);
+      await get('some-url', {}, false);
     } catch (err) {
       e = err;
     }
@@ -137,7 +144,7 @@ describe('platform/gh-got-wrapper', () => {
     );
     let e;
     try {
-      await get('some-url', {}, 0);
+      await get('some-url', {}, false);
     } catch (err) {
       e = err;
     }
@@ -152,7 +159,7 @@ describe('platform/gh-got-wrapper', () => {
     );
     let e;
     try {
-      await get('some-url', {}, 0);
+      await get('some-url', {}, false);
     } catch (err) {
       e = err;
     }
@@ -168,7 +175,7 @@ describe('platform/gh-got-wrapper', () => {
     );
     let e;
     try {
-      await get('some-url', {}, 0);
+      await get('some-url', {}, false);
     } catch (err) {
       e = err;
     }
diff --git a/test/platform/github/index.spec.js b/test/platform/github/index.spec.js
deleted file mode 100644
index 50b0b0fdd954dd5681668513e6407cce4d3e063f..0000000000000000000000000000000000000000
--- a/test/platform/github/index.spec.js
+++ /dev/null
@@ -1,1718 +0,0 @@
-const fs = require('fs-extra');
-
-describe('platform/github', () => {
-  let github;
-  let get;
-  let hostRules;
-  let GitStorage;
-  beforeEach(() => {
-    // reset module
-    jest.resetModules();
-    jest.mock('delay');
-    jest.mock('../../../lib/platform/github/gh-got-wrapper');
-    jest.mock('../../../lib/util/host-rules');
-    jest.mock('../../../lib/util/got');
-    get = require('../../../lib/platform/github/gh-got-wrapper');
-    github = require('../../../lib/platform/github');
-    hostRules = require('../../../lib/util/host-rules');
-    jest.mock('../../../lib/platform/git/storage');
-    GitStorage = require('../../../lib/platform/git/storage').Storage;
-    GitStorage.mockImplementation(() => ({
-      initRepo: jest.fn(),
-      cleanRepo: jest.fn(),
-      getFileList: jest.fn(),
-      branchExists: jest.fn(() => true),
-      isBranchStale: jest.fn(() => false),
-      setBaseBranch: jest.fn(),
-      getBranchLastCommitTime: jest.fn(),
-      getAllRenovateBranches: jest.fn(),
-      getCommitMessages: jest.fn(),
-      getFile: jest.fn(),
-      commitFilesToBranch: jest.fn(),
-      mergeBranch: jest.fn(),
-      deleteBranch: jest.fn(),
-      getRepoStatus: jest.fn(),
-      getBranchCommit: jest.fn(
-        () => '0d9c7726c3d628b7e28af234595cfd20febdbf8e'
-      ),
-    }));
-    delete global.gitAuthor;
-    hostRules.find.mockReturnValue({
-      token: 'abc123',
-    });
-  });
-
-  const graphqlOpenPullRequests = fs.readFileSync(
-    'test/platform/github/_fixtures/graphql/pullrequest-1.json',
-    'utf8'
-  );
-  const graphqlClosedPullrequests = fs.readFileSync(
-    'test/platform/github/_fixtures/graphql/pullrequests-closed.json',
-    'utf8'
-  );
-
-  function getRepos(...args) {
-    // repo info
-    get.mockImplementationOnce(() => ({
-      body: [
-        {
-          full_name: 'a/b',
-        },
-        {
-          full_name: 'c/d',
-        },
-      ],
-    }));
-    return github.getRepos(...args);
-  }
-
-  describe('initPlatform()', () => {
-    it('should throw if no token', async () => {
-      await expect(github.initPlatform({})).rejects.toThrow();
-    });
-    it('should throw if user failure', async () => {
-      get.mockImplementationOnce(() => ({}));
-      await expect(github.initPlatform({ token: 'abc123' })).rejects.toThrow();
-    });
-    it('should support default endpoint no email access', async () => {
-      get.mockImplementationOnce(() => ({
-        body: {
-          login: 'renovate-bot',
-        },
-      }));
-      expect(await github.initPlatform({ token: 'abc123' })).toMatchSnapshot();
-    });
-    it('should support default endpoint no email result', async () => {
-      get.mockImplementationOnce(() => ({
-        body: {
-          login: 'renovate-bot',
-        },
-      }));
-      get.mockImplementationOnce(() => ({
-        body: [{}],
-      }));
-      expect(await github.initPlatform({ token: 'abc123' })).toMatchSnapshot();
-    });
-    it('should support default endpoint with email', async () => {
-      get.mockImplementationOnce(() => ({
-        body: {
-          login: 'renovate-bot',
-        },
-      }));
-      get.mockImplementationOnce(() => ({
-        body: [
-          {
-            email: 'user@domain.com',
-          },
-        ],
-      }));
-      expect(await github.initPlatform({ token: 'abc123' })).toMatchSnapshot();
-    });
-    it('should support custom endpoint', async () => {
-      get.mockImplementationOnce(() => ({
-        body: {
-          login: 'renovate-bot',
-        },
-      }));
-      expect(
-        await github.initPlatform({
-          endpoint: 'https://ghe.renovatebot.com',
-          token: 'abc123',
-        })
-      ).toMatchSnapshot();
-    });
-  });
-
-  describe('getRepos', () => {
-    it('should return an array of repos', async () => {
-      const repos = await getRepos();
-      expect(get.mock.calls).toMatchSnapshot();
-      expect(repos).toMatchSnapshot();
-    });
-  });
-
-  function initRepo(...args) {
-    // repo info
-    get.mockImplementationOnce(() => ({
-      body: {
-        owner: {
-          login: 'theowner',
-        },
-        default_branch: 'master',
-        allow_rebase_merge: true,
-        allow_squash_merge: true,
-        allow_merge_commit: true,
-      },
-    }));
-    if (args.length) {
-      return github.initRepo(...args);
-    }
-    return github.initRepo({
-      endpoint: 'https://github.com',
-      repository: 'some/repo',
-      token: 'token',
-    });
-  }
-
-  describe('initRepo', () => {
-    it('should rebase', async () => {
-      function squashInitRepo(...args) {
-        // repo info
-        get.mockImplementationOnce(() => ({
-          body: {
-            owner: {
-              login: 'theowner',
-            },
-            default_branch: 'master',
-            allow_rebase_merge: true,
-            allow_squash_merge: true,
-            allow_merge_commit: true,
-          },
-        }));
-        return github.initRepo(...args);
-      }
-      const config = await squashInitRepo({
-        repository: 'some/repo',
-      });
-      expect(config).toMatchSnapshot();
-    });
-    it('should forks when forkMode', async () => {
-      function forkInitRepo(...args) {
-        // repo info
-        get.mockImplementationOnce(() => ({
-          body: {
-            owner: {
-              login: 'theowner',
-            },
-            default_branch: 'master',
-            allow_rebase_merge: true,
-            allow_squash_merge: true,
-            allow_merge_commit: true,
-          },
-        }));
-        // getBranchCommit
-        get.mockImplementationOnce(() => ({
-          body: {
-            object: {
-              sha: '1234',
-            },
-          },
-        }));
-        // getRepos
-        get.mockImplementationOnce(() => ({
-          body: [],
-        }));
-        // getBranchCommit
-        get.post.mockImplementationOnce(() => ({
-          body: {},
-        }));
-        return github.initRepo(...args);
-      }
-      const config = await forkInitRepo({
-        repository: 'some/repo',
-        forkMode: true,
-      });
-      expect(config).toMatchSnapshot();
-    });
-    it('should update fork when forkMode', async () => {
-      function forkInitRepo(...args) {
-        // repo info
-        get.mockImplementationOnce(() => ({
-          body: {
-            owner: {
-              login: 'theowner',
-            },
-            default_branch: 'master',
-            allow_rebase_merge: true,
-            allow_squash_merge: true,
-            allow_merge_commit: true,
-          },
-        }));
-        // getBranchCommit
-        get.mockImplementationOnce(() => ({
-          body: {
-            object: {
-              sha: '1234',
-            },
-          },
-        }));
-        // getRepos
-        get.mockImplementationOnce(() => ({
-          body: [
-            {
-              full_name: 'forked_repo',
-            },
-          ],
-        }));
-        // fork
-        get.post.mockImplementationOnce(() => ({
-          body: { full_name: 'forked_repo' },
-        }));
-        return github.initRepo(...args);
-      }
-      const config = await forkInitRepo({
-        repository: 'some/repo',
-        forkMode: true,
-      });
-      expect(config).toMatchSnapshot();
-    });
-    it('should squash', async () => {
-      function mergeInitRepo(...args) {
-        // repo info
-        get.mockImplementationOnce(() => ({
-          body: {
-            owner: {
-              login: 'theowner',
-            },
-            default_branch: 'master',
-            allow_rebase_merge: false,
-            allow_squash_merge: true,
-            allow_merge_commit: true,
-          },
-        }));
-        return github.initRepo(...args);
-      }
-      const config = await mergeInitRepo({
-        repository: 'some/repo',
-      });
-      expect(config).toMatchSnapshot();
-    });
-    it('should merge', async () => {
-      function mergeInitRepo(...args) {
-        // repo info
-        get.mockImplementationOnce(() => ({
-          body: {
-            owner: {
-              login: 'theowner',
-            },
-            default_branch: 'master',
-            allow_rebase_merge: false,
-            allow_squash_merge: false,
-            allow_merge_commit: true,
-          },
-        }));
-        return github.initRepo(...args);
-      }
-      const config = await mergeInitRepo({
-        repository: 'some/repo',
-      });
-      expect(config).toMatchSnapshot();
-    });
-    it('should not guess at merge', async () => {
-      function mergeInitRepo(...args) {
-        // repo info
-        get.mockImplementationOnce(() => ({
-          body: {
-            owner: {
-              login: 'theowner',
-            },
-            default_branch: 'master',
-          },
-        }));
-        return github.initRepo(...args);
-      }
-      const config = await mergeInitRepo({
-        repository: 'some/repo',
-      });
-      expect(config).toMatchSnapshot();
-    });
-    it('should throw error if archived', async () => {
-      get.mockReturnValueOnce({
-        body: {
-          archived: true,
-          owner: {},
-        },
-      });
-      await expect(
-        github.initRepo({
-          repository: 'some/repo',
-        })
-      ).rejects.toThrow();
-    });
-    it('throws not-found', async () => {
-      get.mockImplementationOnce(() =>
-        Promise.reject({
-          statusCode: 404,
-        })
-      );
-      await expect(
-        github.initRepo({
-          repository: 'some/repo',
-        })
-      ).rejects.toThrow('not-found');
-    });
-    it('should throw error if renamed', async () => {
-      get.mockReturnValueOnce({
-        body: {
-          fork: true,
-          full_name: 'some/other',
-          owner: {},
-        },
-      });
-      await expect(
-        github.initRepo({
-          includeForks: true,
-          repository: 'some/repo',
-        })
-      ).rejects.toThrow('renamed');
-    });
-  });
-  describe('getRepoForceRebase', () => {
-    it('should detect repoForceRebase', async () => {
-      get.mockImplementationOnce(() => ({
-        body: {
-          required_pull_request_reviews: {
-            dismiss_stale_reviews: false,
-            require_code_owner_reviews: false,
-          },
-          required_status_checks: {
-            strict: true,
-            contexts: [],
-          },
-          restrictions: {
-            users: [
-              {
-                login: 'rarkins',
-                id: 6311784,
-                type: 'User',
-                site_admin: false,
-              },
-            ],
-            teams: [],
-          },
-        },
-      }));
-      const res = await github.getRepoForceRebase();
-      expect(res).toBe(true);
-    });
-    it('should handle 404', async () => {
-      get.mockImplementationOnce(() =>
-        Promise.reject({
-          statusCode: 404,
-        })
-      );
-      const res = await github.getRepoForceRebase();
-      expect(res).toBe(false);
-    });
-    it('should handle 403', async () => {
-      get.mockImplementationOnce(() =>
-        Promise.reject({
-          statusCode: 403,
-        })
-      );
-      const res = await github.getRepoForceRebase();
-      expect(res).toBe(false);
-    });
-    it('should throw 401', async () => {
-      get.mockImplementationOnce(() =>
-        Promise.reject({
-          statusCode: 401,
-        })
-      );
-      await expect(github.getRepoForceRebase()).rejects.toEqual({
-        statusCode: 401,
-      });
-    });
-  });
-  describe('getBranchPr(branchName)', () => {
-    it('should return null if no PR exists', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockImplementationOnce(() => ({
-        body: [],
-      }));
-      const pr = await github.getBranchPr('somebranch');
-      expect(pr).toBeNull();
-    });
-    it('should return the PR object', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockImplementationOnce(() => ({
-        body: [{ number: 91, head: { ref: 'somebranch' }, state: 'open' }],
-      }));
-      get.mockImplementationOnce(() => ({
-        body: {
-          number: 91,
-          additions: 1,
-          deletions: 1,
-          commits: 1,
-          base: {
-            sha: '1234',
-          },
-          head: { ref: 'somebranch' },
-          state: 'open',
-        },
-      }));
-      get.mockResolvedValue({ body: { object: { sha: '12345' } } });
-      const pr = await github.getBranchPr('somebranch');
-      expect(get.mock.calls).toMatchSnapshot();
-      expect(pr).toMatchSnapshot();
-    });
-  });
-  describe('getBranchStatus()', () => {
-    it('returns success if requiredStatusChecks null', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      const res = await github.getBranchStatus('somebranch', null);
-      expect(res).toEqual('success');
-    });
-    it('return failed if unsupported requiredStatusChecks', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      const res = await github.getBranchStatus('somebranch', ['foo']);
-      expect(res).toEqual('failed');
-    });
-    it('should pass through success', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockImplementationOnce(() => ({
-        body: {
-          state: 'success',
-        },
-      }));
-      const res = await github.getBranchStatus('somebranch', []);
-      expect(res).toEqual('success');
-    });
-    it('should pass through failed', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockImplementationOnce(() => ({
-        body: {
-          state: 'failed',
-        },
-      }));
-      const res = await github.getBranchStatus('somebranch', []);
-      expect(res).toEqual('failed');
-    });
-    it('should fail if a check run has failed', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockImplementationOnce(() => ({
-        body: {
-          state: 'pending',
-          statuses: [],
-        },
-      }));
-      get.mockImplementationOnce(() => ({
-        body: {
-          total_count: 2,
-          check_runs: [
-            {
-              id: 23950198,
-              status: 'completed',
-              conclusion: 'success',
-              name: 'Travis CI - Pull Request',
-            },
-            {
-              id: 23950195,
-              status: 'completed',
-              conclusion: 'failed',
-              name: 'Travis CI - Branch',
-            },
-          ],
-        },
-      }));
-      const res = await github.getBranchStatus('somebranch', []);
-      expect(res).toEqual('failed');
-    });
-    it('should suceed if no status and all passed check runs', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockImplementationOnce(() => ({
-        body: {
-          state: 'pending',
-          statuses: [],
-        },
-      }));
-      get.mockImplementationOnce(() => ({
-        body: {
-          total_count: 2,
-          check_runs: [
-            {
-              id: 23950198,
-              status: 'completed',
-              conclusion: 'success',
-              name: 'Travis CI - Pull Request',
-            },
-            {
-              id: 23950195,
-              status: 'completed',
-              conclusion: 'success',
-              name: 'Travis CI - Branch',
-            },
-          ],
-        },
-      }));
-      const res = await github.getBranchStatus('somebranch', []);
-      expect(res).toEqual('success');
-    });
-    it('should fail if a check run has failed', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockImplementationOnce(() => ({
-        body: {
-          state: 'pending',
-          statuses: [],
-        },
-      }));
-      get.mockImplementationOnce(() => ({
-        body: {
-          total_count: 2,
-          check_runs: [
-            {
-              id: 23950198,
-              status: 'completed',
-              conclusion: 'success',
-              name: 'Travis CI - Pull Request',
-            },
-            {
-              id: 23950195,
-              status: 'pending',
-              name: 'Travis CI - Branch',
-            },
-          ],
-        },
-      }));
-      const res = await github.getBranchStatus('somebranch', []);
-      expect(res).toEqual('pending');
-    });
-  });
-  describe('getBranchStatusCheck', () => {
-    it('returns state if found', async () => {
-      await initRepo({
-        repository: 'some/repo',
-        token: 'token',
-      });
-      get.mockImplementationOnce(() => ({
-        body: [
-          {
-            context: 'context-1',
-            state: 'state-1',
-          },
-          {
-            context: 'context-2',
-            state: 'state-2',
-          },
-          {
-            context: 'context-3',
-            state: 'state-3',
-          },
-        ],
-      }));
-      const res = await github.getBranchStatusCheck(
-        'renovate/future_branch',
-        'context-2'
-      );
-      expect(res).toEqual('state-2');
-    });
-    it('returns null', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockImplementationOnce(() => ({
-        body: [
-          {
-            context: 'context-1',
-            state: 'state-1',
-          },
-          {
-            context: 'context-2',
-            state: 'state-2',
-          },
-          {
-            context: 'context-3',
-            state: 'state-3',
-          },
-        ],
-      }));
-      const res = await github.getBranchStatusCheck('somebranch', 'context-4');
-      expect(res).toBeNull();
-    });
-  });
-  describe('setBranchStatus', () => {
-    it('returns if already set', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockImplementationOnce(() => ({
-        body: [
-          {
-            context: 'some-context',
-            state: 'some-state',
-          },
-        ],
-      }));
-      await github.setBranchStatus(
-        'some-branch',
-        'some-context',
-        'some-description',
-        'some-state',
-        'some-url'
-      );
-      expect(get.post).toHaveBeenCalledTimes(0);
-    });
-    it('sets branch status', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockImplementationOnce(() => ({
-        body: [
-          {
-            context: 'context-1',
-            state: 'state-1',
-          },
-          {
-            context: 'context-2',
-            state: 'state-2',
-          },
-          {
-            context: 'context-3',
-            state: 'state-3',
-          },
-        ],
-      }));
-      // getBranchCommit
-      get.mockImplementationOnce(() => ({
-        body: {
-          object: {
-            sha: '1235',
-          },
-        },
-      }));
-      await github.setBranchStatus(
-        'some-branch',
-        'some-context',
-        'some-description',
-        'some-state',
-        'some-url'
-      );
-      expect(get.post).toHaveBeenCalledTimes(1);
-    });
-  });
-  describe('findIssue()', () => {
-    beforeEach(() => {
-      get.post.mockImplementationOnce(() => ({
-        body: JSON.stringify({
-          data: {
-            repository: {
-              issues: {
-                pageInfo: {
-                  startCursor: null,
-                  hasNextPage: false,
-                  endCursor: null,
-                },
-                nodes: [
-                  {
-                    number: 2,
-                    state: 'open',
-                    title: 'title-2',
-                  },
-                  {
-                    number: 1,
-                    state: 'open',
-                    title: 'title-1',
-                  },
-                ],
-              },
-            },
-          },
-        }),
-      }));
-    });
-    it('returns null if no issue', async () => {
-      const res = await github.findIssue('title-3');
-      expect(res).toBeNull();
-    });
-    it('finds issue', async () => {
-      get.post.mockImplementationOnce(() => ({
-        body: JSON.stringify({
-          data: {
-            repository: {
-              issues: {
-                pageInfo: {
-                  startCursor: null,
-                  hasNextPage: false,
-                  endCursor: null,
-                },
-                nodes: [
-                  {
-                    number: 2,
-                    state: 'open',
-                    title: 'title-2',
-                  },
-                  {
-                    number: 1,
-                    state: 'open',
-                    title: 'title-1',
-                  },
-                ],
-              },
-            },
-          },
-        }),
-      }));
-      get.mockReturnValueOnce({ body: { body: 'new-content' } });
-      const res = await github.findIssue('title-2');
-      expect(res).not.toBeNull();
-    });
-  });
-  describe('ensureIssue()', () => {
-    it('creates issue', async () => {
-      get.post.mockImplementationOnce(() => ({
-        body: JSON.stringify({
-          data: {
-            repository: {
-              issues: {
-                pageInfo: {
-                  startCursor: null,
-                  hasNextPage: false,
-                  endCursor: null,
-                },
-                nodes: [
-                  {
-                    number: 2,
-                    state: 'open',
-                    title: 'title-2',
-                  },
-                  {
-                    number: 1,
-                    state: 'open',
-                    title: 'title-1',
-                  },
-                ],
-              },
-            },
-          },
-        }),
-      }));
-      const res = await github.ensureIssue('new-title', 'new-content');
-      expect(res).toEqual('created');
-    });
-    it('creates issue if not ensuring only once', async () => {
-      get.post.mockImplementationOnce(() => ({
-        body: JSON.stringify({
-          data: {
-            repository: {
-              issues: {
-                pageInfo: {
-                  startCursor: null,
-                  hasNextPage: false,
-                  endCursor: null,
-                },
-                nodes: [
-                  {
-                    number: 2,
-                    state: 'open',
-                    title: 'title-2',
-                  },
-                  {
-                    number: 1,
-                    state: 'closed',
-                    title: 'title-1',
-                  },
-                ],
-              },
-            },
-          },
-        }),
-      }));
-      const res = await github.ensureIssue('title-1', 'new-content');
-      expect(res).toBeNull();
-    });
-    it('does not create issue if ensuring only once', async () => {
-      get.post.mockImplementationOnce(() => ({
-        body: JSON.stringify({
-          data: {
-            repository: {
-              issues: {
-                pageInfo: {
-                  startCursor: null,
-                  hasNextPage: false,
-                  endCursor: null,
-                },
-                nodes: [
-                  {
-                    number: 2,
-                    state: 'open',
-                    title: 'title-2',
-                  },
-                  {
-                    number: 1,
-                    state: 'closed',
-                    title: 'title-1',
-                  },
-                ],
-              },
-            },
-          },
-        }),
-      }));
-      const once = true;
-      const res = await github.ensureIssue('title-1', 'new-content', once);
-      expect(res).toBeNull();
-    });
-    it('closes others if ensuring only once', async () => {
-      get.post.mockImplementationOnce(() => ({
-        body: JSON.stringify({
-          data: {
-            repository: {
-              issues: {
-                pageInfo: {
-                  startCursor: null,
-                  hasNextPage: false,
-                  endCursor: null,
-                },
-                nodes: [
-                  {
-                    number: 3,
-                    state: 'open',
-                    title: 'title-1',
-                  },
-                  {
-                    number: 2,
-                    state: 'open',
-                    title: 'title-2',
-                  },
-                  {
-                    number: 1,
-                    state: 'closed',
-                    title: 'title-1',
-                  },
-                ],
-              },
-            },
-          },
-        }),
-      }));
-      const once = true;
-      const res = await github.ensureIssue('title-1', 'new-content', once);
-      expect(res).toBeNull();
-    });
-    it('updates issue', async () => {
-      get.post.mockImplementationOnce(() => ({
-        body: JSON.stringify({
-          data: {
-            repository: {
-              issues: {
-                pageInfo: {
-                  startCursor: null,
-                  hasNextPage: false,
-                  endCursor: null,
-                },
-                nodes: [
-                  {
-                    number: 2,
-                    state: 'open',
-                    title: 'title-2',
-                  },
-                  {
-                    number: 1,
-                    state: 'open',
-                    title: 'title-1',
-                  },
-                ],
-              },
-            },
-          },
-        }),
-      }));
-      get.mockReturnValueOnce({ body: { body: 'new-content' } });
-      const res = await github.ensureIssue('title-2', 'newer-content');
-      expect(res).toEqual('updated');
-    });
-    it('skips update if unchanged', async () => {
-      get.post.mockImplementationOnce(() => ({
-        body: JSON.stringify({
-          data: {
-            repository: {
-              issues: {
-                pageInfo: {
-                  startCursor: null,
-                  hasNextPage: false,
-                  endCursor: null,
-                },
-                nodes: [
-                  {
-                    number: 2,
-                    state: 'open',
-                    title: 'title-2',
-                  },
-                  {
-                    number: 1,
-                    state: 'open',
-                    title: 'title-1',
-                  },
-                ],
-              },
-            },
-          },
-        }),
-      }));
-      get.mockReturnValueOnce({ body: { body: 'newer-content' } });
-      const res = await github.ensureIssue('title-2', 'newer-content');
-      expect(res).toBeNull();
-    });
-    it('deletes if duplicate', async () => {
-      get.post.mockImplementationOnce(() => ({
-        body: JSON.stringify({
-          data: {
-            repository: {
-              issues: {
-                pageInfo: {
-                  startCursor: null,
-                  hasNextPage: false,
-                  endCursor: null,
-                },
-                nodes: [
-                  {
-                    number: 2,
-                    state: 'open',
-                    title: 'title-1',
-                  },
-                  {
-                    number: 1,
-                    state: 'open',
-                    title: 'title-1',
-                  },
-                ],
-              },
-            },
-          },
-        }),
-      }));
-      get.mockReturnValueOnce({ body: { body: 'newer-content' } });
-      const res = await github.ensureIssue('title-1', 'newer-content');
-      expect(res).toBeNull();
-    });
-  });
-  describe('ensureIssueClosing()', () => {
-    it('closes issue', async () => {
-      get.post.mockImplementationOnce(() => ({
-        body: JSON.stringify({
-          data: {
-            repository: {
-              issues: {
-                pageInfo: {
-                  startCursor: null,
-                  hasNextPage: false,
-                  endCursor: null,
-                },
-                nodes: [
-                  {
-                    number: 2,
-                    state: 'open',
-                    title: 'title-2',
-                  },
-                  {
-                    number: 1,
-                    state: 'open',
-                    title: 'title-1',
-                  },
-                ],
-              },
-            },
-          },
-        }),
-      }));
-      await github.ensureIssueClosing('title-2');
-    });
-  });
-  describe('deleteLabel(issueNo, label)', () => {
-    it('should delete the label', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      await github.deleteLabel(42, 'rebase');
-      expect(get.delete.mock.calls).toMatchSnapshot();
-    });
-  });
-  describe('addAssignees(issueNo, assignees)', () => {
-    it('should add the given assignees to the issue', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      await github.addAssignees(42, ['someuser', 'someotheruser']);
-      expect(get.post.mock.calls).toMatchSnapshot();
-    });
-  });
-  describe('addReviewers(issueNo, reviewers)', () => {
-    it('should add the given reviewers to the PR', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.post.mockReturnValueOnce({});
-      await github.addReviewers(42, [
-        'someuser',
-        'someotheruser',
-        'team:someteam',
-      ]);
-      expect(get.post.mock.calls).toMatchSnapshot();
-    });
-  });
-  describe('ensureComment', () => {
-    it('add comment if not found', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockReturnValueOnce({ body: [] });
-      await github.ensureComment(42, 'some-subject', 'some\ncontent');
-      expect(get.post).toHaveBeenCalledTimes(2);
-      expect(get.post.mock.calls[1]).toMatchSnapshot();
-    });
-    it('adds comment if found in closed PR list', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.post.mockImplementationOnce(() => ({
-        body: graphqlClosedPullrequests,
-      }));
-      await github.ensureComment(2499, 'some-subject', 'some\ncontent');
-      expect(get.post).toHaveBeenCalledTimes(2);
-      expect(get.patch).toHaveBeenCalledTimes(0);
-    });
-    it('add updates comment if necessary', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockReturnValueOnce({
-        body: [{ id: 1234, body: '### some-subject\n\nblablabla' }],
-      });
-      await github.ensureComment(42, 'some-subject', 'some\ncontent');
-      expect(get.post).toHaveBeenCalledTimes(1);
-      expect(get.patch).toHaveBeenCalledTimes(1);
-      expect(get.patch.mock.calls).toMatchSnapshot();
-    });
-    it('skips comment', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockReturnValueOnce({
-        body: [{ id: 1234, body: '### some-subject\n\nsome\ncontent' }],
-      });
-      await github.ensureComment(42, 'some-subject', 'some\ncontent');
-      expect(get.post).toHaveBeenCalledTimes(1);
-      expect(get.patch).toHaveBeenCalledTimes(0);
-    });
-    it('handles comment with no description', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockReturnValueOnce({ body: [{ id: 1234, body: '!merge' }] });
-      await github.ensureComment(42, null, '!merge');
-      expect(get.post).toHaveBeenCalledTimes(1);
-      expect(get.patch).toHaveBeenCalledTimes(0);
-    });
-  });
-  describe('ensureCommentRemoval', () => {
-    it('deletes comment if found', async () => {
-      await initRepo({ repository: 'some/repo', token: 'token' });
-      get.mockReturnValueOnce({
-        body: [{ id: 1234, body: '### some-subject\n\nblablabla' }],
-      });
-      await github.ensureCommentRemoval(42, 'some-subject');
-      expect(get.delete).toHaveBeenCalledTimes(1);
-    });
-  });
-  describe('findPr(branchName, prTitle, state)', () => {
-    it('returns true if no title and all state', async () => {
-      get.mockReturnValueOnce({
-        body: [
-          {
-            number: 1,
-            head: { ref: 'branch-a' },
-            title: 'branch a pr',
-            state: 'open',
-          },
-        ],
-      });
-      const res = await github.findPr('branch-a', null);
-      expect(res).toBeDefined();
-    });
-    it('returns true if not open', async () => {
-      get.mockReturnValueOnce({
-        body: [
-          {
-            number: 1,
-            head: { ref: 'branch-a' },
-            title: 'branch a pr',
-            state: 'closed',
-          },
-        ],
-      });
-      const res = await github.findPr('branch-a', null, '!open');
-      expect(res).toBeDefined();
-    });
-    it('caches pr list', async () => {
-      get.mockReturnValueOnce({
-        body: [
-          {
-            number: 1,
-            head: { ref: 'branch-a' },
-            title: 'branch a pr',
-            state: 'open',
-          },
-        ],
-      });
-      let res = await github.findPr('branch-a', null);
-      expect(res).toBeDefined();
-      res = await github.findPr('branch-a', 'branch a pr');
-      expect(res).toBeDefined();
-      res = await github.findPr('branch-a', 'branch a pr', 'open');
-      expect(res).toBeDefined();
-      res = await github.findPr('branch-b');
-      expect(res).not.toBeDefined();
-    });
-  });
-  describe('createPr()', () => {
-    it('should create and return a PR object', async () => {
-      await initRepo({ repository: 'some/repo', token: 'token' });
-      get.post.mockImplementationOnce(() => ({
-        body: {
-          number: 123,
-        },
-      }));
-      get.mockImplementationOnce(() => ({
-        body: [],
-      }));
-      // res.body.object.sha
-      get.mockImplementationOnce(() => ({
-        body: {
-          object: { sha: 'some-sha' },
-        },
-      }));
-      const pr = await github.createPr(
-        'some-branch',
-        'The Title',
-        'Hello world',
-        ['deps', 'renovate'],
-        false,
-        { statusCheckVerify: true }
-      );
-      expect(pr).toMatchSnapshot();
-      expect(get.post.mock.calls).toMatchSnapshot();
-    });
-    it('should use defaultBranch', async () => {
-      await initRepo({ repository: 'some/repo', token: 'token' });
-      get.post.mockImplementationOnce(() => ({
-        body: {
-          number: 123,
-        },
-      }));
-      const pr = await github.createPr(
-        'some-branch',
-        'The Title',
-        'Hello world',
-        null,
-        true
-      );
-      expect(pr).toMatchSnapshot();
-      expect(get.post.mock.calls).toMatchSnapshot();
-    });
-  });
-  describe('getPr(prNo)', () => {
-    it('should return null if no prNo is passed', async () => {
-      const pr = await github.getPr(null);
-      expect(pr).toBeNull();
-    });
-    it('should return PR from graphql result', async () => {
-      global.gitAuthor = {
-        name: 'Renovate Bot',
-        email: 'bot@renovateapp.com',
-      };
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.post.mockImplementationOnce(() => ({
-        body: graphqlOpenPullRequests,
-      }));
-      // getBranchCommit
-      get.mockImplementationOnce(() => ({
-        body: {
-          object: {
-            sha: '1234123412341234123412341234123412341234',
-          },
-        },
-      }));
-      const pr = await github.getPr(2500);
-      expect(pr).toBeDefined();
-      expect(pr).toMatchSnapshot();
-    });
-    it('should return PR from closed graphql result', async () => {
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.post.mockImplementationOnce(() => ({
-        body: graphqlOpenPullRequests,
-      }));
-      get.post.mockImplementationOnce(() => ({
-        body: graphqlClosedPullrequests,
-      }));
-      const pr = await github.getPr(2499);
-      expect(pr).toBeDefined();
-      expect(pr).toMatchSnapshot();
-    });
-    it('should return null if no PR is returned from GitHub', async () => {
-      await initRepo({ repository: 'some/repo', token: 'token' });
-      get.mockImplementationOnce(() => ({
-        body: null,
-      }));
-      const pr = await github.getPr(1234);
-      expect(pr).toBeNull();
-    });
-    [
-      {
-        number: 1,
-        state: 'closed',
-        base: { sha: '1234' },
-        mergeable: true,
-        merged_at: 'sometime',
-      },
-      {
-        number: 1,
-        state: 'open',
-        mergeable_state: 'dirty',
-        base: { sha: '1234' },
-        commits: 1,
-      },
-      {
-        number: 1,
-        state: 'open',
-        base: { sha: '5678' },
-        commits: 1,
-        mergeable: true,
-      },
-    ].forEach((body, i) => {
-      it(`should return a PR object - ${i}`, async () => {
-        await initRepo({ repository: 'some/repo', token: 'token' });
-        get.mockImplementationOnce(() => ({
-          body,
-        }));
-        // getBranchCommit
-        get.mockImplementationOnce(() => ({
-          body: {
-            object: {
-              sha: '1234',
-            },
-          },
-        }));
-        const pr = await github.getPr(1234);
-        expect(pr).toMatchSnapshot();
-      });
-    });
-    it('should return a rebaseable PR despite multiple commits', async () => {
-      await initRepo({ repository: 'some/repo', token: 'token' });
-      get.mockImplementationOnce(() => ({
-        body: {
-          number: 1,
-          state: 'open',
-          mergeable_state: 'dirty',
-          base: { sha: '1234' },
-          commits: 2,
-        },
-      }));
-      get.mockImplementationOnce(() => ({
-        body: [
-          {
-            author: {
-              login: 'foo',
-            },
-          },
-        ],
-      }));
-      // getBranchCommit
-      get.mockImplementationOnce(() => ({
-        body: {
-          object: {
-            sha: '1234',
-          },
-        },
-      }));
-      const pr = await github.getPr(1234);
-      expect(pr).toMatchSnapshot();
-    });
-    it('should return an unrebaseable PR if multiple authors', async () => {
-      await initRepo({ repository: 'some/repo', token: 'token' });
-      get.mockImplementationOnce(() => ({
-        body: {
-          number: 1,
-          state: 'open',
-          mergeable_state: 'dirty',
-          base: { sha: '1234' },
-          commits: 2,
-        },
-      }));
-      get.mockImplementationOnce(() => ({
-        body: [
-          {
-            commit: {
-              author: {
-                email: 'bar',
-              },
-            },
-          },
-          {
-            committer: {
-              login: 'web-flow',
-            },
-          },
-          {},
-        ],
-      }));
-      // getBranchCommit
-      get.mockImplementationOnce(() => ({
-        body: {
-          object: {
-            sha: '1234',
-          },
-        },
-      }));
-      const pr = await github.getPr(1234);
-      expect(pr).toMatchSnapshot();
-    });
-    it('should return a rebaseable PR if web-flow is second author', async () => {
-      await initRepo({ repository: 'some/repo', token: 'token' });
-      get.mockImplementationOnce(() => ({
-        body: {
-          number: 1,
-          state: 'open',
-          mergeable_state: 'dirty',
-          base: { sha: '1234' },
-          commits: 2,
-        },
-      }));
-      get.mockImplementationOnce(() => ({
-        body: [
-          {
-            author: {
-              login: 'foo',
-            },
-          },
-          {
-            committer: {
-              login: 'web-flow',
-            },
-            commit: {
-              message: "Merge branch 'master' into renovate/foo",
-            },
-            parents: [1, 2],
-          },
-        ],
-      }));
-      // getBranchCommit
-      get.mockImplementationOnce(() => ({
-        body: {
-          object: {
-            sha: '1234',
-          },
-        },
-      }));
-      const pr = await github.getPr(1234);
-      expect(pr.canRebase).toBe(true);
-      expect(pr).toMatchSnapshot();
-    });
-    it('should return a rebaseable PR if gitAuthor matches 1 commit', async () => {
-      global.gitAuthor = {
-        name: 'Renovate Bot',
-        email: 'bot@renovateapp.com',
-      };
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockImplementationOnce(() => ({
-        body: {
-          number: 1,
-          state: 'open',
-          mergeable_state: 'dirty',
-          base: { sha: '1234' },
-          commits: 1,
-        },
-      }));
-      get.mockImplementationOnce(() => ({
-        body: [
-          {
-            commit: {
-              author: {
-                email: 'bot@renovateapp.com',
-              },
-            },
-          },
-        ],
-      }));
-      // getBranchCommit
-      get.mockImplementationOnce(() => ({
-        body: {
-          object: {
-            sha: '1234',
-          },
-        },
-      }));
-      const pr = await github.getPr(1234);
-      expect(pr.canRebase).toBe(true);
-      expect(pr).toMatchSnapshot();
-    });
-    it('should return a not rebaseable PR if gitAuthor does not match 1 commit', async () => {
-      global.gitAuthor = {
-        name: 'Renovate Bot',
-        email: 'bot@renovateapp.com',
-      };
-      await initRepo({
-        repository: 'some/repo',
-      });
-      get.mockImplementationOnce(() => ({
-        body: {
-          number: 1,
-          state: 'open',
-          mergeable_state: 'dirty',
-          base: { sha: '1234' },
-          commits: 1,
-        },
-      }));
-      get.mockImplementationOnce(() => ({
-        body: [
-          {
-            commit: {
-              author: {
-                email: 'foo@bar.com',
-              },
-            },
-          },
-        ],
-      }));
-      // getBranchCommit
-      get.mockImplementationOnce(() => ({
-        body: {
-          object: {
-            sha: '1234',
-          },
-        },
-      }));
-      const pr = await github.getPr(1234);
-      expect(pr.canRebase).toBe(false);
-      expect(pr).toMatchSnapshot();
-    });
-  });
-  describe('getPrFiles()', () => {
-    it('should return empty if no prNo is passed', async () => {
-      const prFiles = await github.getPrFiles(null);
-      expect(prFiles).toEqual([]);
-    });
-    it('returns files', async () => {
-      get.mockReturnValueOnce({
-        body: [
-          { filename: 'renovate.json' },
-          { filename: 'not renovate.json' },
-        ],
-      });
-      const prFiles = await github.getPrFiles(123);
-      expect(prFiles).toMatchSnapshot();
-      expect(prFiles).toHaveLength(2);
-    });
-  });
-  describe('updatePr(prNo, title, body)', () => {
-    it('should update the PR', async () => {
-      await initRepo({ repository: 'some/repo', token: 'token' });
-      await github.updatePr(1234, 'The New Title', 'Hello world again');
-      expect(get.patch.mock.calls).toMatchSnapshot();
-    });
-  });
-  describe('mergePr(prNo)', () => {
-    it('should merge the PR', async () => {
-      await initRepo({ repository: 'some/repo', token: 'token' });
-      // getBranchCommit
-      get.mockImplementationOnce(() => ({
-        body: {
-          object: {
-            sha: '1235',
-          },
-        },
-      }));
-      const pr = {
-        number: 1234,
-        head: {
-          ref: 'someref',
-        },
-      };
-      expect(await github.mergePr(pr)).toBe(true);
-      expect(get.put).toHaveBeenCalledTimes(1);
-      expect(get).toHaveBeenCalledTimes(1);
-    });
-    it('should handle merge error', async () => {
-      await initRepo({ repository: 'some/repo', token: 'token' });
-      const pr = {
-        number: 1234,
-        head: {
-          ref: 'someref',
-        },
-      };
-      get.put.mockImplementationOnce(() => {
-        throw new Error('merge error');
-      });
-      expect(await github.mergePr(pr)).toBe(false);
-      expect(get.put).toHaveBeenCalledTimes(1);
-      expect(get).toHaveBeenCalledTimes(1);
-    });
-  });
-  describe('getPrBody(input)', () => {
-    it('returns updated pr body', () => {
-      const input =
-        'https://github.com/foo/bar/issues/5 plus also [a link](https://github.com/foo/bar/issues/5)';
-      expect(github.getPrBody(input)).toMatchSnapshot();
-    });
-    it('returns not-updated pr body for GHE', async () => {
-      get.mockImplementationOnce(() => ({
-        body: {
-          login: 'renovate-bot',
-        },
-      }));
-      await github.initPlatform({
-        endpoint: 'https://github.company.com',
-        token: 'abc123',
-      });
-      hostRules.find.mockReturnValue({
-        token: 'abc123',
-      });
-      await initRepo({
-        repository: 'some/repo',
-      });
-      const input =
-        'https://github.com/foo/bar/issues/5 plus also [a link](https://github.com/foo/bar/issues/5)';
-      expect(github.getPrBody(input)).toEqual(input);
-    });
-  });
-  describe('mergePr(prNo) - autodetection', () => {
-    beforeEach(async () => {
-      function guessInitRepo(...args) {
-        // repo info
-        get.mockImplementationOnce(() => ({
-          body: {
-            owner: {
-              login: 'theowner',
-            },
-            default_branch: 'master',
-          },
-        }));
-        // getBranchCommit
-        get.mockImplementationOnce(() => ({
-          body: {
-            object: {
-              sha: '1234',
-            },
-          },
-        }));
-        // getBranchCommit
-        get.mockImplementationOnce(() => ({
-          body: {
-            object: {
-              sha: '1235',
-            },
-          },
-        }));
-        // getBranchCommit
-        get.mockImplementationOnce(() => ({
-          body: {
-            object: {
-              sha: '1235',
-            },
-          },
-        }));
-        return github.initRepo(...args);
-      }
-      await guessInitRepo({ repository: 'some/repo', token: 'token' });
-      get.put = jest.fn();
-    });
-    it('should try rebase first', async () => {
-      const pr = {
-        number: 1235,
-        head: {
-          ref: 'someref',
-        },
-      };
-      expect(await github.mergePr(pr)).toBe(true);
-      expect(get.put).toHaveBeenCalledTimes(1);
-    });
-    it('should try squash after rebase', async () => {
-      const pr = {
-        number: 1236,
-        head: {
-          ref: 'someref',
-        },
-      };
-      get.put.mockImplementationOnce(() => {
-        throw new Error('no rebasing allowed');
-      });
-      await github.mergePr(pr);
-      expect(get.put).toHaveBeenCalledTimes(2);
-    });
-    it('should try merge after squash', async () => {
-      const pr = {
-        number: 1237,
-        head: {
-          ref: 'someref',
-        },
-      };
-      get.put.mockImplementationOnce(() => {
-        throw new Error('no rebasing allowed');
-      });
-      get.put.mockImplementationOnce(() => {
-        throw new Error('no squashing allowed');
-      });
-      expect(await github.mergePr(pr)).toBe(true);
-      expect(get.put).toHaveBeenCalledTimes(3);
-    });
-    it('should give up', async () => {
-      const pr = {
-        number: 1237,
-        head: {
-          ref: 'someref',
-        },
-      };
-      get.put.mockImplementationOnce(() => {
-        throw new Error('no rebasing allowed');
-      });
-      get.put.mockImplementationOnce(() => {
-        throw new Error('no squashing allowed');
-      });
-      get.put.mockImplementationOnce(() => {
-        throw new Error('no merging allowed');
-      });
-      expect(await github.mergePr(pr)).toBe(false);
-      expect(get.put).toHaveBeenCalledTimes(3);
-    });
-  });
-  describe('getVulnerabilityAlerts()', () => {
-    it('returns empty if error', async () => {
-      get.mockReturnValueOnce({
-        body: {},
-      });
-      const res = await github.getVulnerabilityAlerts();
-      expect(res).toHaveLength(0);
-    });
-    it('returns array if found', async () => {
-      // prettier-ignore
-      const body = "{\"data\":{\"repository\":{\"vulnerabilityAlerts\":{\"edges\":[{\"node\":{\"externalIdentifier\":\"CVE-2018-1000136\",\"externalReference\":\"https://nvd.nist.gov/vuln/detail/CVE-2018-1000136\",\"affectedRange\":\">= 1.8, < 1.8.3\",\"fixedIn\":\"1.8.3\",\"id\":\"MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQ1MzE3NDk4MQ==\",\"packageName\":\"electron\"}}]}}}}";
-      get.post.mockReturnValueOnce({
-        body,
-      });
-      const res = await github.getVulnerabilityAlerts();
-      expect(res).toHaveLength(1);
-    });
-    it('returns empty if disabled', async () => {
-      // prettier-ignore
-      const body = "{\"data\":{\"repository\":{}}}";
-      get.post.mockReturnValueOnce({
-        body,
-      });
-      const res = await github.getVulnerabilityAlerts();
-      expect(res).toHaveLength(0);
-    });
-  });
-});
diff --git a/test/platform/github/index.spec.ts b/test/platform/github/index.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8bb25bce8583e15a26b3f6136a98923cfac143c3
--- /dev/null
+++ b/test/platform/github/index.spec.ts
@@ -0,0 +1,1977 @@
+import fs from 'fs-extra';
+import { GotApi } from '../../../lib/platform/common';
+
+describe('platform/github', () => {
+  let github: typeof import('../../../lib/platform/github');
+  let api: jest.Mocked<GotApi>;
+  let hostRules: jest.Mocked<typeof import('../../../lib/util/host-rules')>;
+  let GitStorage: jest.Mock<typeof import('../../../lib/platform/git/storage')>;
+  beforeEach(async () => {
+    // reset module
+    jest.resetModules();
+    jest.mock('delay');
+    jest.mock('../../../lib/platform/github/gh-got-wrapper');
+    jest.mock('../../../lib/util/host-rules');
+    jest.mock('../../../lib/util/got');
+    api = (await import('../../../lib/platform/github/gh-got-wrapper'))
+      .api as any;
+    github = await import('../../../lib/platform/github');
+    hostRules = (await import('../../../lib/util/host-rules')) as any;
+    jest.mock('../../../lib/platform/git/storage');
+    GitStorage = (await import('../../../lib/platform/git/storage'))
+      .Storage as any;
+    GitStorage.mockImplementation(
+      () =>
+        ({
+          initRepo: jest.fn(),
+          cleanRepo: jest.fn(),
+          getFileList: jest.fn(),
+          branchExists: jest.fn(() => true),
+          isBranchStale: jest.fn(() => false),
+          setBaseBranch: jest.fn(),
+          getBranchLastCommitTime: jest.fn(),
+          getAllRenovateBranches: jest.fn(),
+          getCommitMessages: jest.fn(),
+          getFile: jest.fn(),
+          commitFilesToBranch: jest.fn(),
+          mergeBranch: jest.fn(),
+          deleteBranch: jest.fn(),
+          getRepoStatus: jest.fn(),
+          getBranchCommit: jest.fn(
+            () => '0d9c7726c3d628b7e28af234595cfd20febdbf8e'
+          ),
+        } as any)
+    );
+    delete global.gitAuthor;
+    hostRules.find.mockReturnValue({
+      token: 'abc123',
+    });
+  });
+
+  const graphqlOpenPullRequests = fs.readFileSync(
+    'test/platform/github/_fixtures/graphql/pullrequest-1.json',
+    'utf8'
+  );
+  const graphqlClosedPullrequests = fs.readFileSync(
+    'test/platform/github/_fixtures/graphql/pullrequests-closed.json',
+    'utf8'
+  );
+
+  function getRepos() {
+    // repo info
+    api.get.mockImplementationOnce(
+      () =>
+        ({
+          body: [
+            {
+              full_name: 'a/b',
+            },
+            {
+              full_name: 'c/d',
+            },
+          ],
+        } as any)
+    );
+    return github.getRepos();
+  }
+
+  describe('initPlatform()', () => {
+    it('should throw if no token', async () => {
+      await expect(github.initPlatform({} as any)).rejects.toThrow();
+    });
+    it('should throw if user failure', async () => {
+      api.get.mockImplementationOnce(() => ({} as any));
+      await expect(
+        github.initPlatform({ token: 'abc123' } as any)
+      ).rejects.toThrow();
+    });
+    it('should support default endpoint no email access', async () => {
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              login: 'renovate-bot',
+            },
+          } as any)
+      );
+      expect(
+        await github.initPlatform({ token: 'abc123' } as any)
+      ).toMatchSnapshot();
+    });
+    it('should support default endpoint no email result', async () => {
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              login: 'renovate-bot',
+            },
+          } as any)
+      );
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [{}],
+          } as any)
+      );
+      expect(
+        await github.initPlatform({ token: 'abc123' } as any)
+      ).toMatchSnapshot();
+    });
+    it('should support default endpoint with email', async () => {
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              login: 'renovate-bot',
+            },
+          } as any)
+      );
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [
+              {
+                email: 'user@domain.com',
+              },
+            ],
+          } as any)
+      );
+      expect(
+        await github.initPlatform({ token: 'abc123' } as any)
+      ).toMatchSnapshot();
+    });
+    it('should support custom endpoint', async () => {
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              login: 'renovate-bot',
+            },
+          } as any)
+      );
+      expect(
+        await github.initPlatform({
+          endpoint: 'https://ghe.renovatebot.com',
+          token: 'abc123',
+        })
+      ).toMatchSnapshot();
+    });
+  });
+
+  describe('getRepos', () => {
+    it('should return an array of repos', async () => {
+      const repos = await getRepos();
+      expect(api.get.mock.calls).toMatchSnapshot();
+      expect(repos).toMatchSnapshot();
+    });
+  });
+
+  function initRepo(args: { repository: string; token?: string }) {
+    // repo info
+    api.get.mockImplementationOnce(
+      () =>
+        ({
+          body: {
+            owner: {
+              login: 'theowner',
+            },
+            default_branch: 'master',
+            allow_rebase_merge: true,
+            allow_squash_merge: true,
+            allow_merge_commit: true,
+          },
+        } as any)
+    );
+    if (args) {
+      return github.initRepo(args as any);
+    }
+    return github.initRepo({
+      endpoint: 'https://github.com',
+      repository: 'some/repo',
+    } as any);
+  }
+
+  describe('initRepo', () => {
+    it('should rebase', async () => {
+      function squashInitRepo(args: any) {
+        // repo info
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                owner: {
+                  login: 'theowner',
+                },
+                default_branch: 'master',
+                allow_rebase_merge: true,
+                allow_squash_merge: true,
+                allow_merge_commit: true,
+              },
+            } as any)
+        );
+        return github.initRepo(args);
+      }
+      const config = await squashInitRepo({
+        repository: 'some/repo',
+      });
+      expect(config).toMatchSnapshot();
+    });
+    it('should forks when forkMode', async () => {
+      function forkInitRepo(args: any) {
+        // repo info
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                owner: {
+                  login: 'theowner',
+                },
+                default_branch: 'master',
+                allow_rebase_merge: true,
+                allow_squash_merge: true,
+                allow_merge_commit: true,
+              },
+            } as any)
+        );
+        // api.getBranchCommit
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                object: {
+                  sha: '1234',
+                },
+              },
+            } as any)
+        );
+        // api.getRepos
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: [],
+            } as any)
+        );
+        // api.getBranchCommit
+        api.post.mockImplementationOnce(
+          () =>
+            ({
+              body: {},
+            } as any)
+        );
+        return github.initRepo(args);
+      }
+      const config = await forkInitRepo({
+        repository: 'some/repo',
+        forkMode: true,
+      });
+      expect(config).toMatchSnapshot();
+    });
+    it('should update fork when forkMode', async () => {
+      function forkInitRepo(args: any) {
+        // repo info
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                owner: {
+                  login: 'theowner',
+                },
+                default_branch: 'master',
+                allow_rebase_merge: true,
+                allow_squash_merge: true,
+                allow_merge_commit: true,
+              },
+            } as any)
+        );
+        // api.getBranchCommit
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                object: {
+                  sha: '1234',
+                },
+              },
+            } as any)
+        );
+        // api.getRepos
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: [
+                {
+                  full_name: 'forked_repo',
+                },
+              ],
+            } as any)
+        );
+        // fork
+        api.post.mockImplementationOnce(
+          () =>
+            ({
+              body: { full_name: 'forked_repo' },
+            } as any)
+        );
+        return github.initRepo(args);
+      }
+      const config = await forkInitRepo({
+        repository: 'some/repo',
+        forkMode: true,
+      });
+      expect(config).toMatchSnapshot();
+    });
+    it('should squash', async () => {
+      function mergeInitRepo(args: any) {
+        // repo info
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                owner: {
+                  login: 'theowner',
+                },
+                default_branch: 'master',
+                allow_rebase_merge: false,
+                allow_squash_merge: true,
+                allow_merge_commit: true,
+              },
+            } as any)
+        );
+        return github.initRepo(args);
+      }
+      const config = await mergeInitRepo({
+        repository: 'some/repo',
+      });
+      expect(config).toMatchSnapshot();
+    });
+    it('should merge', async () => {
+      function mergeInitRepo(args: any) {
+        // repo info
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                owner: {
+                  login: 'theowner',
+                },
+                default_branch: 'master',
+                allow_rebase_merge: false,
+                allow_squash_merge: false,
+                allow_merge_commit: true,
+              },
+            } as any)
+        );
+        return github.initRepo(args);
+      }
+      const config = await mergeInitRepo({
+        repository: 'some/repo',
+      });
+      expect(config).toMatchSnapshot();
+    });
+    it('should not guess at merge', async () => {
+      function mergeInitRepo(args: any) {
+        // repo info
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                owner: {
+                  login: 'theowner',
+                },
+                default_branch: 'master',
+              },
+            } as any)
+        );
+        return github.initRepo(args);
+      }
+      const config = await mergeInitRepo({
+        repository: 'some/repo',
+      });
+      expect(config).toMatchSnapshot();
+    });
+    it('should throw error if archived', async () => {
+      api.get.mockReturnValueOnce({
+        body: {
+          archived: true,
+          owner: {},
+        },
+      } as any);
+      await expect(
+        github.initRepo({
+          repository: 'some/repo',
+        } as any)
+      ).rejects.toThrow();
+    });
+    it('throws not-found', async () => {
+      api.get.mockImplementationOnce(
+        () =>
+          Promise.reject({
+            statusCode: 404,
+          }) as any
+      );
+      await expect(
+        github.initRepo({
+          repository: 'some/repo',
+        } as any)
+      ).rejects.toThrow('not-found');
+    });
+    it('should throw error if renamed', async () => {
+      api.get.mockReturnValueOnce({
+        body: {
+          fork: true,
+          full_name: 'some/other',
+          owner: {},
+        },
+      } as any);
+      await expect(
+        github.initRepo({
+          includeForks: true,
+          repository: 'some/repo',
+        } as any)
+      ).rejects.toThrow('renamed');
+    });
+  });
+  describe('getRepoForceRebase', () => {
+    it('should detect repoForceRebase', async () => {
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              required_pull_request_reviews: {
+                dismiss_stale_reviews: false,
+                require_code_owner_reviews: false,
+              },
+              required_status_checks: {
+                strict: true,
+                contexts: [],
+              },
+              restrictions: {
+                users: [
+                  {
+                    login: 'rarkins',
+                    id: 6311784,
+                    type: 'User',
+                    site_admin: false,
+                  },
+                ],
+                teams: [],
+              },
+            },
+          } as any)
+      );
+      const res = await github.getRepoForceRebase();
+      expect(res).toBe(true);
+    });
+    it('should handle 404', async () => {
+      api.get.mockImplementationOnce(
+        () =>
+          Promise.reject({
+            statusCode: 404,
+          }) as any
+      );
+      const res = await github.getRepoForceRebase();
+      expect(res).toBe(false);
+    });
+    it('should handle 403', async () => {
+      api.get.mockImplementationOnce(
+        () =>
+          Promise.reject({
+            statusCode: 403,
+          }) as any
+      );
+      const res = await github.getRepoForceRebase();
+      expect(res).toBe(false);
+    });
+    it('should throw 401', async () => {
+      api.get.mockImplementationOnce(
+        () =>
+          Promise.reject({
+            statusCode: 401,
+          }) as any
+      );
+      await expect(github.getRepoForceRebase()).rejects.toEqual({
+        statusCode: 401,
+      });
+    });
+  });
+  describe('getBranchPr(branchName)', () => {
+    it('should return null if no PR exists', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [],
+          } as any)
+      );
+      const pr = await github.getBranchPr('somebranch');
+      expect(pr).toBeNull();
+    });
+    it('should return the PR object', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [{ number: 91, head: { ref: 'somebranch' }, state: 'open' }],
+          } as any)
+      );
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              number: 91,
+              additions: 1,
+              deletions: 1,
+              commits: 1,
+              base: {
+                sha: '1234',
+              },
+              head: { ref: 'somebranch' },
+              state: 'open',
+            },
+          } as any)
+      );
+      api.get.mockResolvedValue({ body: { object: { sha: '12345' } } } as any);
+      const pr = await github.getBranchPr('somebranch');
+      expect(api.get.mock.calls).toMatchSnapshot();
+      expect(pr).toMatchSnapshot();
+    });
+  });
+  describe('getBranchStatus()', () => {
+    it('returns success if requiredStatusChecks null', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      const res = await github.getBranchStatus('somebranch', null);
+      expect(res).toEqual('success');
+    });
+    it('return failed if unsupported requiredStatusChecks', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      const res = await github.getBranchStatus('somebranch', ['foo']);
+      expect(res).toEqual('failed');
+    });
+    it('should pass through success', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              state: 'success',
+            },
+          } as any)
+      );
+      const res = await github.getBranchStatus('somebranch', []);
+      expect(res).toEqual('success');
+    });
+    it('should pass through failed', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              state: 'failed',
+            },
+          } as any)
+      );
+      const res = await github.getBranchStatus('somebranch', []);
+      expect(res).toEqual('failed');
+    });
+    it('should fail if a check run has failed', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              state: 'pending',
+              statuses: [],
+            },
+          } as any)
+      );
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              total_count: 2,
+              check_runs: [
+                {
+                  id: 23950198,
+                  status: 'completed',
+                  conclusion: 'success',
+                  name: 'Travis CI - Pull Request',
+                },
+                {
+                  id: 23950195,
+                  status: 'completed',
+                  conclusion: 'failed',
+                  name: 'Travis CI - Branch',
+                },
+              ],
+            },
+          } as any)
+      );
+      const res = await github.getBranchStatus('somebranch', []);
+      expect(res).toEqual('failed');
+    });
+    it('should suceed if no status and all passed check runs', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              state: 'pending',
+              statuses: [],
+            },
+          } as any)
+      );
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              total_count: 2,
+              check_runs: [
+                {
+                  id: 23950198,
+                  status: 'completed',
+                  conclusion: 'success',
+                  name: 'Travis CI - Pull Request',
+                },
+                {
+                  id: 23950195,
+                  status: 'completed',
+                  conclusion: 'success',
+                  name: 'Travis CI - Branch',
+                },
+              ],
+            },
+          } as any)
+      );
+      const res = await github.getBranchStatus('somebranch', []);
+      expect(res).toEqual('success');
+    });
+    it('should fail if a check run has failed', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              state: 'pending',
+              statuses: [],
+            },
+          } as any)
+      );
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              total_count: 2,
+              check_runs: [
+                {
+                  id: 23950198,
+                  status: 'completed',
+                  conclusion: 'success',
+                  name: 'Travis CI - Pull Request',
+                },
+                {
+                  id: 23950195,
+                  status: 'pending',
+                  name: 'Travis CI - Branch',
+                },
+              ],
+            },
+          } as any)
+      );
+      const res = await github.getBranchStatus('somebranch', []);
+      expect(res).toEqual('pending');
+    });
+  });
+  describe('getBranchStatusCheck', () => {
+    it('returns state if found', async () => {
+      await initRepo({
+        repository: 'some/repo',
+        token: 'token',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [
+              {
+                context: 'context-1',
+                state: 'state-1',
+              },
+              {
+                context: 'context-2',
+                state: 'state-2',
+              },
+              {
+                context: 'context-3',
+                state: 'state-3',
+              },
+            ],
+          } as any)
+      );
+      const res = await github.getBranchStatusCheck(
+        'renovate/future_branch',
+        'context-2'
+      );
+      expect(res).toEqual('state-2');
+    });
+    it('returns null', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [
+              {
+                context: 'context-1',
+                state: 'state-1',
+              },
+              {
+                context: 'context-2',
+                state: 'state-2',
+              },
+              {
+                context: 'context-3',
+                state: 'state-3',
+              },
+            ],
+          } as any)
+      );
+      const res = await github.getBranchStatusCheck('somebranch', 'context-4');
+      expect(res).toBeNull();
+    });
+  });
+  describe('setBranchStatus', () => {
+    it('returns if already set', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [
+              {
+                context: 'some-context',
+                state: 'some-state',
+              },
+            ],
+          } as any)
+      );
+      await github.setBranchStatus(
+        'some-branch',
+        'some-context',
+        'some-description',
+        'some-state',
+        'some-url'
+      );
+      expect(api.post).toHaveBeenCalledTimes(0);
+    });
+    it('sets branch status', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [
+              {
+                context: 'context-1',
+                state: 'state-1',
+              },
+              {
+                context: 'context-2',
+                state: 'state-2',
+              },
+              {
+                context: 'context-3',
+                state: 'state-3',
+              },
+            ],
+          } as any)
+      );
+      // api.getBranchCommit
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              object: {
+                sha: '1235',
+              },
+            },
+          } as any)
+      );
+      await github.setBranchStatus(
+        'some-branch',
+        'some-context',
+        'some-description',
+        'some-state',
+        'some-url'
+      );
+      expect(api.post).toHaveBeenCalledTimes(1);
+    });
+  });
+  describe('findIssue()', () => {
+    beforeEach(() => {
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: JSON.stringify({
+              data: {
+                repository: {
+                  issues: {
+                    pageInfo: {
+                      startCursor: null,
+                      hasNextPage: false,
+                      endCursor: null,
+                    },
+                    nodes: [
+                      {
+                        number: 2,
+                        state: 'open',
+                        title: 'title-2',
+                      },
+                      {
+                        number: 1,
+                        state: 'open',
+                        title: 'title-1',
+                      },
+                    ],
+                  },
+                },
+              },
+            }),
+          } as any)
+      );
+    });
+    it('returns null if no issue', async () => {
+      const res = await github.findIssue('title-3');
+      expect(res).toBeNull();
+    });
+    it('finds issue', async () => {
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: JSON.stringify({
+              data: {
+                repository: {
+                  issues: {
+                    pageInfo: {
+                      startCursor: null,
+                      hasNextPage: false,
+                      endCursor: null,
+                    },
+                    nodes: [
+                      {
+                        number: 2,
+                        state: 'open',
+                        title: 'title-2',
+                      },
+                      {
+                        number: 1,
+                        state: 'open',
+                        title: 'title-1',
+                      },
+                    ],
+                  },
+                },
+              },
+            }),
+          } as any)
+      );
+      api.get.mockReturnValueOnce({ body: { body: 'new-content' } } as any);
+      const res = await github.findIssue('title-2');
+      expect(res).not.toBeNull();
+    });
+  });
+  describe('ensureIssue()', () => {
+    it('creates issue', async () => {
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: JSON.stringify({
+              data: {
+                repository: {
+                  issues: {
+                    pageInfo: {
+                      startCursor: null,
+                      hasNextPage: false,
+                      endCursor: null,
+                    },
+                    nodes: [
+                      {
+                        number: 2,
+                        state: 'open',
+                        title: 'title-2',
+                      },
+                      {
+                        number: 1,
+                        state: 'open',
+                        title: 'title-1',
+                      },
+                    ],
+                  },
+                },
+              },
+            }),
+          } as any)
+      );
+      const res = await github.ensureIssue('new-title', 'new-content');
+      expect(res).toEqual('created');
+    });
+    it('creates issue if not ensuring only once', async () => {
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: JSON.stringify({
+              data: {
+                repository: {
+                  issues: {
+                    pageInfo: {
+                      startCursor: null,
+                      hasNextPage: false,
+                      endCursor: null,
+                    },
+                    nodes: [
+                      {
+                        number: 2,
+                        state: 'open',
+                        title: 'title-2',
+                      },
+                      {
+                        number: 1,
+                        state: 'closed',
+                        title: 'title-1',
+                      },
+                    ],
+                  },
+                },
+              },
+            }),
+          } as any)
+      );
+      const res = await github.ensureIssue('title-1', 'new-content');
+      expect(res).toBeNull();
+    });
+    it('does not create issue if ensuring only once', async () => {
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: JSON.stringify({
+              data: {
+                repository: {
+                  issues: {
+                    pageInfo: {
+                      startCursor: null,
+                      hasNextPage: false,
+                      endCursor: null,
+                    },
+                    nodes: [
+                      {
+                        number: 2,
+                        state: 'open',
+                        title: 'title-2',
+                      },
+                      {
+                        number: 1,
+                        state: 'closed',
+                        title: 'title-1',
+                      },
+                    ],
+                  },
+                },
+              },
+            }),
+          } as any)
+      );
+      const once = true;
+      const res = await github.ensureIssue('title-1', 'new-content', once);
+      expect(res).toBeNull();
+    });
+    it('closes others if ensuring only once', async () => {
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: JSON.stringify({
+              data: {
+                repository: {
+                  issues: {
+                    pageInfo: {
+                      startCursor: null,
+                      hasNextPage: false,
+                      endCursor: null,
+                    },
+                    nodes: [
+                      {
+                        number: 3,
+                        state: 'open',
+                        title: 'title-1',
+                      },
+                      {
+                        number: 2,
+                        state: 'open',
+                        title: 'title-2',
+                      },
+                      {
+                        number: 1,
+                        state: 'closed',
+                        title: 'title-1',
+                      },
+                    ],
+                  },
+                },
+              },
+            }),
+          } as any)
+      );
+      const once = true;
+      const res = await github.ensureIssue('title-1', 'new-content', once);
+      expect(res).toBeNull();
+    });
+    it('updates issue', async () => {
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: JSON.stringify({
+              data: {
+                repository: {
+                  issues: {
+                    pageInfo: {
+                      startCursor: null,
+                      hasNextPage: false,
+                      endCursor: null,
+                    },
+                    nodes: [
+                      {
+                        number: 2,
+                        state: 'open',
+                        title: 'title-2',
+                      },
+                      {
+                        number: 1,
+                        state: 'open',
+                        title: 'title-1',
+                      },
+                    ],
+                  },
+                },
+              },
+            }),
+          } as any)
+      );
+      api.get.mockReturnValueOnce({ body: { body: 'new-content' } } as any);
+      const res = await github.ensureIssue('title-2', 'newer-content');
+      expect(res).toEqual('updated');
+    });
+    it('skips update if unchanged', async () => {
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: JSON.stringify({
+              data: {
+                repository: {
+                  issues: {
+                    pageInfo: {
+                      startCursor: null,
+                      hasNextPage: false,
+                      endCursor: null,
+                    },
+                    nodes: [
+                      {
+                        number: 2,
+                        state: 'open',
+                        title: 'title-2',
+                      },
+                      {
+                        number: 1,
+                        state: 'open',
+                        title: 'title-1',
+                      },
+                    ],
+                  },
+                },
+              },
+            }),
+          } as any)
+      );
+      api.get.mockReturnValueOnce({ body: { body: 'newer-content' } } as any);
+      const res = await github.ensureIssue('title-2', 'newer-content');
+      expect(res).toBeNull();
+    });
+    it('deletes if duplicate', async () => {
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: JSON.stringify({
+              data: {
+                repository: {
+                  issues: {
+                    pageInfo: {
+                      startCursor: null,
+                      hasNextPage: false,
+                      endCursor: null,
+                    },
+                    nodes: [
+                      {
+                        number: 2,
+                        state: 'open',
+                        title: 'title-1',
+                      },
+                      {
+                        number: 1,
+                        state: 'open',
+                        title: 'title-1',
+                      },
+                    ],
+                  },
+                },
+              },
+            }),
+          } as any)
+      );
+      api.get.mockReturnValueOnce({ body: { body: 'newer-content' } } as any);
+      const res = await github.ensureIssue('title-1', 'newer-content');
+      expect(res).toBeNull();
+    });
+  });
+  describe('ensureIssueClosing()', () => {
+    it('closes issue', async () => {
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: JSON.stringify({
+              data: {
+                repository: {
+                  issues: {
+                    pageInfo: {
+                      startCursor: null,
+                      hasNextPage: false,
+                      endCursor: null,
+                    },
+                    nodes: [
+                      {
+                        number: 2,
+                        state: 'open',
+                        title: 'title-2',
+                      },
+                      {
+                        number: 1,
+                        state: 'open',
+                        title: 'title-1',
+                      },
+                    ],
+                  },
+                },
+              },
+            }),
+          } as any)
+      );
+      await github.ensureIssueClosing('title-2');
+    });
+  });
+  describe('deleteLabel(issueNo, label)', () => {
+    it('should delete the label', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      await github.deleteLabel(42, 'rebase');
+      expect(api.delete.mock.calls).toMatchSnapshot();
+    });
+  });
+  describe('addAssignees(issueNo, assignees)', () => {
+    it('should add the given assignees to the issue', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      await github.addAssignees(42, ['someuser', 'someotheruser']);
+      expect(api.post.mock.calls).toMatchSnapshot();
+    });
+  });
+  describe('addReviewers(issueNo, reviewers)', () => {
+    it('should add the given reviewers to the PR', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.post.mockReturnValueOnce({} as any);
+      await github.addReviewers(42, [
+        'someuser',
+        'someotheruser',
+        'team:someteam',
+      ]);
+      expect(api.post.mock.calls).toMatchSnapshot();
+    });
+  });
+  describe('ensureComment', () => {
+    it('add comment if not found', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockReturnValueOnce({ body: [] } as any);
+      await github.ensureComment(42, 'some-subject', 'some\ncontent');
+      expect(api.post).toHaveBeenCalledTimes(2);
+      expect(api.post.mock.calls[1]).toMatchSnapshot();
+    });
+    it('adds comment if found in closed PR list', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: graphqlClosedPullrequests,
+          } as any)
+      );
+      await github.ensureComment(2499, 'some-subject', 'some\ncontent');
+      expect(api.post).toHaveBeenCalledTimes(2);
+      expect(api.patch).toHaveBeenCalledTimes(0);
+    });
+    it('add updates comment if necessary', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockReturnValueOnce({
+        body: [{ id: 1234, body: '### some-subject\n\nblablabla' }],
+      } as any);
+      await github.ensureComment(42, 'some-subject', 'some\ncontent');
+      expect(api.post).toHaveBeenCalledTimes(1);
+      expect(api.patch).toHaveBeenCalledTimes(1);
+      expect(api.patch.mock.calls).toMatchSnapshot();
+    });
+    it('skips comment', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockReturnValueOnce({
+        body: [{ id: 1234, body: '### some-subject\n\nsome\ncontent' }],
+      } as any);
+      await github.ensureComment(42, 'some-subject', 'some\ncontent');
+      expect(api.post).toHaveBeenCalledTimes(1);
+      expect(api.patch).toHaveBeenCalledTimes(0);
+    });
+    it('handles comment with no description', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockReturnValueOnce({
+        body: [{ id: 1234, body: '!merge' }],
+      } as any);
+      await github.ensureComment(42, null, '!merge');
+      expect(api.post).toHaveBeenCalledTimes(1);
+      expect(api.patch).toHaveBeenCalledTimes(0);
+    });
+  });
+  describe('ensureCommentRemoval', () => {
+    it('deletes comment if found', async () => {
+      await initRepo({ repository: 'some/repo', token: 'token' });
+      api.get.mockReturnValueOnce({
+        body: [{ id: 1234, body: '### some-subject\n\nblablabla' }],
+      } as any);
+      await github.ensureCommentRemoval(42, 'some-subject');
+      expect(api.delete).toHaveBeenCalledTimes(1);
+    });
+  });
+  describe('findPr(branchName, prTitle, state)', () => {
+    it('returns true if no title and all state', async () => {
+      api.get.mockReturnValueOnce({
+        body: [
+          {
+            number: 1,
+            head: { ref: 'branch-a' },
+            title: 'branch a pr',
+            state: 'open',
+          },
+        ],
+      } as any);
+      const res = await github.findPr('branch-a', null);
+      expect(res).toBeDefined();
+    });
+    it('returns true if not open', async () => {
+      api.get.mockReturnValueOnce({
+        body: [
+          {
+            number: 1,
+            head: { ref: 'branch-a' },
+            title: 'branch a pr',
+            state: 'closed',
+          },
+        ],
+      } as any);
+      const res = await github.findPr('branch-a', null, '!open');
+      expect(res).toBeDefined();
+    });
+    it('caches pr list', async () => {
+      api.get.mockReturnValueOnce({
+        body: [
+          {
+            number: 1,
+            head: { ref: 'branch-a' },
+            title: 'branch a pr',
+            state: 'open',
+          },
+        ],
+      } as any);
+      let res = await github.findPr('branch-a', null);
+      expect(res).toBeDefined();
+      res = await github.findPr('branch-a', 'branch a pr');
+      expect(res).toBeDefined();
+      res = await github.findPr('branch-a', 'branch a pr', 'open');
+      expect(res).toBeDefined();
+      res = await github.findPr('branch-b');
+      expect(res).not.toBeDefined();
+    });
+  });
+  describe('createPr()', () => {
+    it('should create and return a PR object', async () => {
+      await initRepo({ repository: 'some/repo', token: 'token' });
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              number: 123,
+            },
+          } as any)
+      );
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [],
+          } as any)
+      );
+      // res.body.object.sha
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              object: { sha: 'some-sha' },
+            },
+          } as any)
+      );
+      const pr = await github.createPr(
+        'some-branch',
+        'The Title',
+        'Hello world',
+        ['deps', 'renovate'],
+        false,
+        { statusCheckVerify: true }
+      );
+      expect(pr).toMatchSnapshot();
+      expect(api.post.mock.calls).toMatchSnapshot();
+    });
+    it('should use defaultBranch', async () => {
+      await initRepo({ repository: 'some/repo', token: 'token' });
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              number: 123,
+            },
+          } as any)
+      );
+      const pr = await github.createPr(
+        'some-branch',
+        'The Title',
+        'Hello world',
+        null,
+        true
+      );
+      expect(pr).toMatchSnapshot();
+      expect(api.post.mock.calls).toMatchSnapshot();
+    });
+  });
+  describe('getPr(prNo)', () => {
+    it('should return null if no prNo is passed', async () => {
+      const pr = await github.getPr(0);
+      expect(pr).toBeNull();
+    });
+    it('should return PR from graphql result', async () => {
+      global.gitAuthor = {
+        name: 'Renovate Bot',
+        email: 'bot@renovateapp.com',
+      };
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: graphqlOpenPullRequests,
+          } as any)
+      );
+      // api.getBranchCommit
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              object: {
+                sha: '1234123412341234123412341234123412341234',
+              },
+            },
+          } as any)
+      );
+      const pr = await github.getPr(2500);
+      expect(pr).toBeDefined();
+      expect(pr).toMatchSnapshot();
+    });
+    it('should return PR from closed graphql result', async () => {
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: graphqlOpenPullRequests,
+          } as any)
+      );
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: graphqlClosedPullrequests,
+          } as any)
+      );
+      const pr = await github.getPr(2499);
+      expect(pr).toBeDefined();
+      expect(pr).toMatchSnapshot();
+    });
+    it('should return null if no PR is returned from GitHub', async () => {
+      await initRepo({ repository: 'some/repo', token: 'token' });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: null,
+          } as any)
+      );
+      const pr = await github.getPr(1234);
+      expect(pr).toBeNull();
+    });
+    [
+      {
+        number: 1,
+        state: 'closed',
+        base: { sha: '1234' },
+        mergeable: true,
+        merged_at: 'sometime',
+      },
+      {
+        number: 1,
+        state: 'open',
+        mergeable_state: 'dirty',
+        base: { sha: '1234' },
+        commits: 1,
+      },
+      {
+        number: 1,
+        state: 'open',
+        base: { sha: '5678' },
+        commits: 1,
+        mergeable: true,
+      },
+    ].forEach((body, i) => {
+      it(`should return a PR object - ${i}`, async () => {
+        await initRepo({ repository: 'some/repo', token: 'token' });
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body,
+            } as any)
+        );
+        // api.getBranchCommit
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                object: {
+                  sha: '1234',
+                },
+              },
+            } as any)
+        );
+        const pr = await github.getPr(1234);
+        expect(pr).toMatchSnapshot();
+      });
+    });
+    it('should return a rebaseable PR despite multiple commits', async () => {
+      await initRepo({ repository: 'some/repo', token: 'token' });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              number: 1,
+              state: 'open',
+              mergeable_state: 'dirty',
+              base: { sha: '1234' },
+              commits: 2,
+            },
+          } as any)
+      );
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [
+              {
+                author: {
+                  login: 'foo',
+                },
+              },
+            ],
+          } as any)
+      );
+      // api.getBranchCommit
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              object: {
+                sha: '1234',
+              },
+            },
+          } as any)
+      );
+      const pr = await github.getPr(1234);
+      expect(pr).toMatchSnapshot();
+    });
+    it('should return an unrebaseable PR if multiple authors', async () => {
+      await initRepo({ repository: 'some/repo', token: 'token' });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              number: 1,
+              state: 'open',
+              mergeable_state: 'dirty',
+              base: { sha: '1234' },
+              commits: 2,
+            },
+          } as any)
+      );
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [
+              {
+                commit: {
+                  author: {
+                    email: 'bar',
+                  },
+                },
+              },
+              {
+                committer: {
+                  login: 'web-flow',
+                },
+              },
+              {},
+            ],
+          } as any)
+      );
+      // api.getBranchCommit
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              object: {
+                sha: '1234',
+              },
+            },
+          } as any)
+      );
+      const pr = await github.getPr(1234);
+      expect(pr).toMatchSnapshot();
+    });
+    it('should return a rebaseable PR if web-flow is second author', async () => {
+      await initRepo({ repository: 'some/repo', token: 'token' });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              number: 1,
+              state: 'open',
+              mergeable_state: 'dirty',
+              base: { sha: '1234' },
+              commits: 2,
+            },
+          } as any)
+      );
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [
+              {
+                author: {
+                  login: 'foo',
+                },
+              },
+              {
+                committer: {
+                  login: 'web-flow',
+                },
+                commit: {
+                  message: "Merge branch 'master' into renovate/foo",
+                },
+                parents: [1, 2],
+              },
+            ],
+          } as any)
+      );
+      // api.getBranchCommit
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              object: {
+                sha: '1234',
+              },
+            },
+          } as any)
+      );
+      const pr = await github.getPr(1234);
+      expect(pr.canRebase).toBe(true);
+      expect(pr).toMatchSnapshot();
+    });
+    it('should return a rebaseable PR if gitAuthor matches 1 commit', async () => {
+      global.gitAuthor = {
+        name: 'Renovate Bot',
+        email: 'bot@renovateapp.com',
+      };
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              number: 1,
+              state: 'open',
+              mergeable_state: 'dirty',
+              base: { sha: '1234' },
+              commits: 1,
+            },
+          } as any)
+      );
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [
+              {
+                commit: {
+                  author: {
+                    email: 'bot@renovateapp.com',
+                  },
+                },
+              },
+            ],
+          } as any)
+      );
+      // api.getBranchCommit
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              object: {
+                sha: '1234',
+              },
+            },
+          } as any)
+      );
+      const pr = await github.getPr(1234);
+      expect(pr.canRebase).toBe(true);
+      expect(pr).toMatchSnapshot();
+    });
+    it('should return a not rebaseable PR if gitAuthor does not match 1 commit', async () => {
+      global.gitAuthor = {
+        name: 'Renovate Bot',
+        email: 'bot@renovateapp.com',
+      };
+      await initRepo({
+        repository: 'some/repo',
+      });
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              number: 1,
+              state: 'open',
+              mergeable_state: 'dirty',
+              base: { sha: '1234' },
+              commits: 1,
+            },
+          } as any)
+      );
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: [
+              {
+                commit: {
+                  author: {
+                    email: 'foo@bar.com',
+                  },
+                },
+              },
+            ],
+          } as any)
+      );
+      // api.getBranchCommit
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              object: {
+                sha: '1234',
+              },
+            },
+          } as any)
+      );
+      const pr = await github.getPr(1234);
+      expect(pr.canRebase).toBe(false);
+      expect(pr).toMatchSnapshot();
+    });
+  });
+  describe('getPrFiles()', () => {
+    it('should return empty if no prNo is passed', async () => {
+      const prFiles = await github.getPrFiles(0);
+      expect(prFiles).toEqual([]);
+    });
+    it('returns files', async () => {
+      api.get.mockReturnValueOnce({
+        body: [
+          { filename: 'renovate.json' },
+          { filename: 'not renovate.json' },
+        ],
+      } as any);
+      const prFiles = await github.getPrFiles(123);
+      expect(prFiles).toMatchSnapshot();
+      expect(prFiles).toHaveLength(2);
+    });
+  });
+  describe('updatePr(prNo, title, body)', () => {
+    it('should update the PR', async () => {
+      await initRepo({ repository: 'some/repo', token: 'token' });
+      await github.updatePr(1234, 'The New Title', 'Hello world again');
+      expect(api.patch.mock.calls).toMatchSnapshot();
+    });
+  });
+  describe('mergePr(prNo)', () => {
+    it('should merge the PR', async () => {
+      await initRepo({ repository: 'some/repo', token: 'token' });
+      // api.getBranchCommit
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              object: {
+                sha: '1235',
+              },
+            },
+          } as any)
+      );
+      const pr = {
+        number: 1234,
+        head: {
+          ref: 'someref',
+        },
+      };
+      expect(await github.mergePr(pr.number, '')).toBe(true);
+      expect(api.put).toHaveBeenCalledTimes(1);
+      expect(api.get).toHaveBeenCalledTimes(1);
+    });
+    it('should handle merge error', async () => {
+      await initRepo({ repository: 'some/repo', token: 'token' });
+      const pr = {
+        number: 1234,
+        head: {
+          ref: 'someref',
+        },
+      };
+      api.put.mockImplementationOnce(() => {
+        throw new Error('merge error');
+      });
+      expect(await github.mergePr(pr.number, '')).toBe(false);
+      expect(api.put).toHaveBeenCalledTimes(1);
+      expect(api.get).toHaveBeenCalledTimes(1);
+    });
+  });
+  describe('getPrBody(input)', () => {
+    it('returns updated pr body', () => {
+      const input =
+        'https://github.com/foo/bar/issues/5 plus also [a link](https://github.com/foo/bar/issues/5)';
+      expect(github.getPrBody(input)).toMatchSnapshot();
+    });
+    it('returns not-updated pr body for GHE', async () => {
+      api.get.mockImplementationOnce(
+        () =>
+          ({
+            body: {
+              login: 'renovate-bot',
+            },
+          } as any)
+      );
+      await github.initPlatform({
+        endpoint: 'https://github.company.com',
+        token: 'abc123',
+      });
+      hostRules.find.mockReturnValue({
+        token: 'abc123',
+      });
+      await initRepo({
+        repository: 'some/repo',
+      });
+      const input =
+        'https://github.com/foo/bar/issues/5 plus also [a link](https://github.com/foo/bar/issues/5)';
+      expect(github.getPrBody(input)).toEqual(input);
+    });
+  });
+  describe('mergePr(prNo) - autodetection', () => {
+    beforeEach(async () => {
+      function guessInitRepo(args: any) {
+        // repo info
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                owner: {
+                  login: 'theowner',
+                },
+                default_branch: 'master',
+              },
+            } as any)
+        );
+        // api.getBranchCommit
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                object: {
+                  sha: '1234',
+                },
+              },
+            } as any)
+        );
+        // api.getBranchCommit
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                object: {
+                  sha: '1235',
+                },
+              },
+            } as any)
+        );
+        // api.getBranchCommit
+        api.get.mockImplementationOnce(
+          () =>
+            ({
+              body: {
+                object: {
+                  sha: '1235',
+                },
+              },
+            } as any)
+        );
+        return github.initRepo(args);
+      }
+      await guessInitRepo({ repository: 'some/repo', token: 'token' });
+      api.put = jest.fn();
+    });
+    it('should try rebase first', async () => {
+      const pr = {
+        number: 1235,
+        head: {
+          ref: 'someref',
+        },
+      };
+      expect(await github.mergePr(pr.number, '')).toBe(true);
+      expect(api.put).toHaveBeenCalledTimes(1);
+    });
+    it('should try squash after rebase', async () => {
+      const pr = {
+        number: 1236,
+        head: {
+          ref: 'someref',
+        },
+      };
+      api.put.mockImplementationOnce(() => {
+        throw new Error('no rebasing allowed');
+      });
+      await github.mergePr(pr.number, '');
+      expect(api.put).toHaveBeenCalledTimes(2);
+    });
+    it('should try merge after squash', async () => {
+      const pr = {
+        number: 1237,
+        head: {
+          ref: 'someref',
+        },
+      };
+      api.put.mockImplementationOnce(() => {
+        throw new Error('no rebasing allowed');
+      });
+      api.put.mockImplementationOnce(() => {
+        throw new Error('no squashing allowed');
+      });
+      expect(await github.mergePr(pr.number, '')).toBe(true);
+      expect(api.put).toHaveBeenCalledTimes(3);
+    });
+    it('should give up', async () => {
+      const pr = {
+        number: 1237,
+        head: {
+          ref: 'someref',
+        },
+      };
+      api.put.mockImplementationOnce(() => {
+        throw new Error('no rebasing allowed');
+      });
+      api.put.mockImplementationOnce(() => {
+        throw new Error('no squashing allowed');
+      });
+      api.put.mockImplementationOnce(() => {
+        throw new Error('no merging allowed');
+      });
+      expect(await github.mergePr(pr.number, '')).toBe(false);
+      expect(api.put).toHaveBeenCalledTimes(3);
+    });
+  });
+  describe('getVulnerabilityAlerts()', () => {
+    it('returns empty if error', async () => {
+      api.get.mockReturnValueOnce({
+        body: {},
+      } as any);
+      const res = await github.getVulnerabilityAlerts();
+      expect(res).toHaveLength(0);
+    });
+    it('returns array if found', async () => {
+      // prettier-ignore
+      const body = "{\"data\":{\"repository\":{\"vulnerabilityAlerts\":{\"edges\":[{\"node\":{\"externalIdentifier\":\"CVE-2018-1000136\",\"externalReference\":\"https://nvd.nist.gov/vuln/detail/CVE-2018-1000136\",\"affectedRange\":\">= 1.8, < 1.8.3\",\"fixedIn\":\"1.8.3\",\"id\":\"MDI4OlJlcG9zaXRvcnlWdWxuZXJhYmlsaXR5QWxlcnQ1MzE3NDk4MQ==\",\"packageName\":\"electron\"}}]}}}}";
+      api.post.mockReturnValueOnce({
+        body,
+      } as any);
+      const res = await github.getVulnerabilityAlerts();
+      expect(res).toHaveLength(1);
+    });
+    it('returns empty if disabled', async () => {
+      // prettier-ignore
+      const body = "{\"data\":{\"repository\":{}}}";
+      api.post.mockReturnValueOnce({
+        body,
+      } as any);
+      const res = await github.getVulnerabilityAlerts();
+      expect(res).toHaveLength(0);
+    });
+  });
+});
diff --git a/test/platform/index.spec.js b/test/platform/index.spec.js
index 2d3b3e52cc59ead3a7888bcdf4a2aff953910bc1..8aad6447555547dad431a1045fc0ae343123c8c6 100644
--- a/test/platform/index.spec.js
+++ b/test/platform/index.spec.js
@@ -21,7 +21,7 @@ describe('platform', () => {
     expect(await platform.initPlatform(config)).toMatchSnapshot();
   });
   it('has a list of supported methods for github', () => {
-    const githubMethods = Object.keys(github);
+    const githubMethods = Object.keys(github).sort();
     expect(githubMethods).toMatchSnapshot();
   });
 
diff --git a/test/workers/pr/changelog/index.spec.js b/test/workers/pr/changelog/index.spec.js
index 5dbca1c35bb17060277bd60b8279180e03d4081c..16989a7b331b17491a29a6baf5c0d548b023fc12 100644
--- a/test/workers/pr/changelog/index.spec.js
+++ b/test/workers/pr/changelog/index.spec.js
@@ -1,9 +1,9 @@
+import ghGot from '../../../../lib/platform/github/gh-got-wrapper';
+
 jest.mock('../../../../lib/platform/github/gh-got-wrapper');
 jest.mock('../../../../lib/datasource/npm');
-jest.mock('../../../../lib/platform/github/gh-got-wrapper');
 
 const hostRules = require('../../../../lib/util/host-rules');
-const ghGot = require('../../../../lib/platform/github/gh-got-wrapper');
 
 const { getChangeLogJSON } = require('../../../../lib/workers/pr/changelog');
 const releaseNotes = require('../../../../lib/workers/pr/changelog/release-notes');
diff --git a/yarn.lock b/yarn.lock
index 5f1cdfd61d7e0e7ecc13f6f3de9e53ac71ff24b3..16d120f4b6f018960e8d57b846676593506ebf60 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -344,6 +344,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
+"@babel/plugin-syntax-dynamic-import@7.2.0":
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612"
+  integrity sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+
 "@babel/plugin-syntax-json-strings@^7.2.0":
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470"
@@ -1235,6 +1242,11 @@
   resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
   integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
 
+"@types/semver@6.0.1":
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.0.1.tgz#a984b405c702fa5a7ec6abc56b37f2ba35ef5af6"
+  integrity sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==
+
 "@types/stack-utils@^1.0.1":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
@@ -1677,7 +1689,7 @@ babel-jest@24.8.0, babel-jest@^24.8.0:
     chalk "^2.4.2"
     slash "^2.0.0"
 
-babel-plugin-dynamic-import-node@^2.3.0:
+babel-plugin-dynamic-import-node@2.3.0, babel-plugin-dynamic-import-node@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f"
   integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==