diff --git a/lib/platform/git/storage.js b/lib/platform/git/storage.ts
similarity index 85%
rename from lib/platform/git/storage.js
rename to lib/platform/git/storage.ts
index 2e418d735ec170babdf8251c0cc063d58255f0ab..a01974d8b56fb50bc0fbbc590b04e169ce718bc6 100644
--- a/lib/platform/git/storage.js
+++ b/lib/platform/git/storage.ts
@@ -1,16 +1,31 @@
-const fs = require('fs-extra');
-const { join } = require('path');
-const path = require('path');
-const URL = require('url');
-const Git = require('simple-git/promise');
-const convertHrtime = require('convert-hrtime');
+import convertHrtime from 'convert-hrtime';
+import fs from 'fs-extra';
+import { join } from 'path';
+import Git from 'simple-git/promise';
+import URL from 'url';
+
+declare module 'fs-extra' {
+  export function exists(pathLike: string): Promise<boolean>;
+}
+
+interface IStorageConfig {
+  localDir: string;
+  baseBranch?: string;
+  url: string;
+  gitPrivateKey?: string;
+}
+
+interface ILocalConfig extends IStorageConfig {
+  baseBranch: string;
+  baseBranchSha: string;
+  branchExists: { [branch: string]: boolean };
+}
 
 class Storage {
   constructor() {
-    let config = {};
-    /** @type {Git.SimpleGit} */
-    let git = null;
-    let cwd = null;
+    let config: ILocalConfig = {} as any;
+    let git: Git.SimpleGit;
+    let cwd: string;
 
     Object.assign(this, {
       initRepo,
@@ -32,7 +47,7 @@ class Storage {
     });
 
     // istanbul ignore next
-    async function resetToBranch(branchName) {
+    async function resetToBranch(branchName: string) {
       logger.debug(`resetToBranch(${branchName})`);
       await git.raw(['reset', '--hard']);
       await git.checkout(branchName);
@@ -53,13 +68,13 @@ class Storage {
       }
     }
 
-    async function initRepo(args) {
+    async function initRepo(args: IStorageConfig) {
       cleanRepo();
-      config = { ...args };
+      config = { ...args } as any;
       cwd = config.localDir;
       config.branchExists = {};
       logger.info('Initialising git repository into ' + cwd);
-      const gitHead = path.join(cwd, '.git/HEAD');
+      const gitHead = join(cwd, '.git/HEAD');
       let clone = true;
       async function determineBaseBranch() {
         // see https://stackoverflow.com/a/44750379/1438522
@@ -153,7 +168,7 @@ class Storage {
       return git.status();
     }
 
-    async function createBranch(branchName, sha) {
+    async function createBranch(branchName: string, sha: string) {
       logger.debug(`createBranch(${branchName})`);
       await git.reset('hard');
       await git.raw(['clean', '-fd']);
@@ -163,7 +178,7 @@ class Storage {
     }
 
     // Return the commit SHA for a branch
-    async function getBranchCommit(branchName) {
+    async function getBranchCommit(branchName: string) {
       const res = await git.revparse(['origin/' + branchName]);
       return res.trim();
     }
@@ -177,7 +192,7 @@ class Storage {
       return res.all.map(commit => commit.message);
     }
 
-    async function setBaseBranch(branchName) {
+    async function setBaseBranch(branchName: string) {
       if (branchName) {
         logger.debug(`Setting baseBranch to ${branchName}`);
         config.baseBranch = branchName;
@@ -192,7 +207,7 @@ class Storage {
       }
     }
 
-    async function getFileList(branchName) {
+    async function getFileList(branchName?: string) {
       const branch = branchName || config.baseBranch;
       const exists = await branchExists(branch);
       if (!exists) {
@@ -211,7 +226,7 @@ class Storage {
       return files.split('\n').filter(Boolean);
     }
 
-    async function branchExists(branchName) {
+    async function branchExists(branchName: string) {
       // First check cache
       if (config.branchExists[branchName] !== undefined) {
         return config.branchExists[branchName];
@@ -226,14 +241,14 @@ class Storage {
       }
     }
 
-    async function getAllRenovateBranches(branchPrefix) {
+    async function getAllRenovateBranches(branchPrefix: string) {
       const branches = await git.branch(['--remotes', '--verbose']);
       return branches.all
         .map(localName)
         .filter(branchName => branchName.startsWith(branchPrefix));
     }
 
-    async function isBranchStale(branchName) {
+    async function isBranchStale(branchName: string) {
       const branches = await git.branch([
         '--remotes',
         '--verbose',
@@ -243,11 +258,11 @@ class Storage {
       return !branches.all.map(localName).includes(branchName);
     }
 
-    async function deleteLocalBranch(branchName) {
+    async function deleteLocalBranch(branchName: string) {
       await git.branch(['-D', branchName]);
     }
 
-    async function deleteBranch(branchName) {
+    async function deleteBranch(branchName: string) {
       try {
         await git.raw(['push', '--delete', 'origin', branchName]);
         logger.debug({ branchName }, 'Deleted remote branch');
@@ -271,7 +286,7 @@ class Storage {
       config.branchExists[branchName] = false;
     }
 
-    async function mergeBranch(branchName) {
+    async function mergeBranch(branchName: string) {
       await git.reset('hard');
       await git.checkout(['-B', branchName, 'origin/' + branchName]);
       await git.checkout(config.baseBranch);
@@ -279,7 +294,7 @@ class Storage {
       await git.push('origin', config.baseBranch);
     }
 
-    async function getBranchLastCommitTime(branchName) {
+    async function getBranchLastCommitTime(branchName: string) {
       try {
         const time = await git.show([
           '-s',
@@ -292,7 +307,7 @@ class Storage {
       }
     }
 
-    async function getFile(filePath, branchName) {
+    async function getFile(filePath: string, branchName?: string) {
       if (branchName) {
         const exists = await branchExists(branchName);
         if (!exists) {
@@ -311,9 +326,9 @@ class Storage {
     }
 
     async function commitFilesToBranch(
-      branchName,
-      files,
-      message,
+      branchName: string,
+      files: any[],
+      message: string,
       parentBranch = config.baseBranch
     ) {
       logger.debug(`Committing files to branch ${branchName}`);
@@ -371,7 +386,19 @@ class Storage {
     function cleanRepo() {}
   }
 
-  static getUrl({ gitFs, auth, hostname, host, repository }) {
+  static getUrl({
+    gitFs,
+    auth,
+    hostname,
+    host,
+    repository,
+  }: {
+    gitFs: 'ssh' | 'http' | 'https';
+    auth: string;
+    hostname: string;
+    host: string;
+    repository: string;
+  }) {
     let protocol = gitFs || 'https';
     // istanbul ignore if
     if (protocol.toString() === 'true') {
@@ -390,8 +417,8 @@ class Storage {
   }
 }
 
-function localName(branchName) {
+function localName(branchName: string) {
   return branchName.replace(/^origin\//, '');
 }
 
-module.exports = Storage;
+export = Storage;
diff --git a/lib/types.d.ts b/lib/types.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..331a2b54c8b16ea0826c8ea7122f41c406d2d7f7
--- /dev/null
+++ b/lib/types.d.ts
@@ -0,0 +1,23 @@
+declare namespace Renovate {
+  // TODO: refactor logger
+  interface ILogger {
+    trace(...args: any[]): void;
+    debug(...args: any[]): void;
+    info(...args: any[]): void;
+    warn(...args: any[]): void;
+    error(...args: any[]): void;
+    fatal(...args: any[]): void;
+    child(...args: any[]): void;
+
+    setMeta(obj: any): void;
+  }
+}
+
+declare var logger: Renovate.ILogger;
+
+declare namespace NodeJS {
+  interface Global {
+    gitAuthor?: { name: string; email: string };
+    logger: Renovate.ILogger;
+  }
+}
diff --git a/package.json b/package.json
index a37e38a7a94f41c0adc36e5667909f648255d1fe..6c88ce2b223edea5c7536209036d3bce8f81bf4c 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
     "renovate-config-validator": "bin/config-validator.js"
   },
   "scripts": {
-    "build": "yarn clean && tsc && yarn copy-static-files",
+    "build": "yarn clean && tsc -p tsconfig.app.json && yarn copy-static-files",
     "clean": "rimraf dist",
     "clean-cache": "node bin/clean-cache.js",
     "copy-static-files": "copyfiles -u 1 lib/**/*.json lib/**/*.py dist/",
@@ -152,6 +152,8 @@
   },
   "devDependencies": {
     "@types/bunyan": "1.8.6",
+    "@types/convert-hrtime": "2.0.0",
+    "@types/fs-extra": "5.1.0",
     "@types/jest": "24.0.13",
     "@types/node": "11.13.10",
     "@typescript-eslint/eslint-plugin": "1.7.0",
@@ -196,6 +198,7 @@
     ]
   },
   "jest": {
+    "globals": { "ts-jest": { "isolatedModules": true } },
     "cacheDirectory": ".cache/jest",
     "coverageDirectory": "./coverage",
     "collectCoverage": true,
diff --git a/tsconfig.app.json b/tsconfig.app.json
new file mode 100644
index 0000000000000000000000000000000000000000..10a65a43840c0979df5b247ff0657fc4ac13e7dc
--- /dev/null
+++ b/tsconfig.app.json
@@ -0,0 +1,6 @@
+{
+  "extends": "./tsconfig",
+  "compilerOptions": {
+    "resolveJsonModule": false
+  }
+}
diff --git a/tsconfig.json b/tsconfig.json
index a21aba9e3a2989237d7434843c229e69c1123604..4f4b2cf665e7b0bae085505d7785f594824461b6 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,8 +6,13 @@
     "allowJs": true,
     "checkJs": false,
     "module": "commonjs",
-    "sourceMap": false
+    "sourceMap": false,
+    "allowSyntheticDefaultImports": true,
+    "esModuleInterop": true,
+    "resolveJsonModule": true,
+    "lib": ["es2018"],
+    "types": ["node"]
   },
-  "include": ["./lib/**/*"],
+  "include": ["lib/**/*"],
   "exclude": ["node_modules", "./.cache", "./dist"]
 }
diff --git a/yarn.lock b/yarn.lock
index 2c15690b76cc0b8f2dceec9ede7051e568342cca..6853192730c2cebb92352b827693bff3d471e4c1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -496,11 +496,23 @@
   dependencies:
     "@types/node" "*"
 
+"@types/convert-hrtime@2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@types/convert-hrtime/-/convert-hrtime-2.0.0.tgz#48d8215750e602a8ea439591c741cc927c116bb1"
+  integrity sha512-0+5xvZlHD4Hp2K1yamMvcT8Rq3sQnujafL0PaVg0Q90jWZcK+zF2A+5vJQw0P8F5oOY11l1d5/hF6s4UQEGDdA==
+
 "@types/events@*":
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
   integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
 
+"@types/fs-extra@5.1.0":
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.1.0.tgz#2a325ef97901504a3828718c390d34b8426a10a1"
+  integrity sha512-AInn5+UBFIK9FK5xc9yP5e3TQSPNNgjHByqYcj9g5elVBnDQcQL7PlO1CIRy2gWlbwK7UPYqi7vRvFA44dCmYQ==
+  dependencies:
+    "@types/node" "*"
+
 "@types/glob@^7.1.1":
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"