diff --git a/src/config.js b/src/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..d88a4dcb7c6c96f01218cf5e254db8f7b8de524f
--- /dev/null
+++ b/src/config.js
@@ -0,0 +1,21 @@
+module.exports = {
+  verbose: false,
+  baseBranch: 'master',
+  templates: {
+    branchName: (params) => {
+      return `renovate/${params.depName}-${params.nextVersionMajor}.x`;
+    },
+    commitMessage: (params) => {
+      return `Upgrade dependency ${params.depName} to version ${params.nextVersion}`;
+    },
+    prBody: (params) => {
+      return `This Pull Request updates dependency ${params.depName} from version ${params.currentVersion} to ${params.nextVersion}.`;
+    },
+    prTitleMajor: (params) => {
+      return `Upgrade dependency ${params.depName} to version ${params.nextVersionMajor}.x`;
+    },
+    prTitleMinor: (params) => {
+      return `Upgrade dependency ${params.depName} to version ${params.nextVersion}`;
+    },
+  }
+};
diff --git a/src/github.js b/src/github.js
new file mode 100644
index 0000000000000000000000000000000000000000..109c9858d3859e47c9001606803433e7e23a12c0
--- /dev/null
+++ b/src/github.js
@@ -0,0 +1,119 @@
+const ghGot = require('gh-got');
+
+var config = {};
+
+module.exports = {
+  // Initialize GitHub by getting base branch SHA
+  init: function(token, repoName, baseBranch, verbose = false) {
+    config.token = token;
+    config.repoName = repoName;
+    config.baseBranch = baseBranch;
+    config.verbose = verbose;
+
+    config.userName = repoName.split('/')[0];
+
+    return ghGot(`repos/${config.repoName}/git/refs/head`, {token: config.token}).then(res => {
+      // First, get the SHA for base branch
+      res.body.forEach(function(branch) {
+        // Loop through all branches because the base branch may not be the first
+        if (branch.ref === `refs/heads/${config.baseBranch}`) {
+          // This is the SHA we will create new branches from
+          config.baseSHA = branch.object.sha;
+        }
+      });
+    }).catch(function(err) {
+      console.log('init error: ' + err);
+    });
+  },
+  createBranch: function(branchName) {
+    return ghGot.post(`repos/${config.repoName}/git/refs`, {
+      token: config.token,
+      body: {
+        ref: `refs/heads/${branchName}`,
+        sha: config.baseSHA,
+      },
+    });
+  },
+  createPr: function(branchName, title, body) {
+    return ghGot.post(`repos/${config.repoName}/pulls`, {
+      token: config.token,
+      body: {
+        title: title,
+        head: branchName,
+        base: config.baseBranch,
+        body: body,
+      }
+    });
+  },
+  checkPrExists(branchName, prTitle) {
+    if (config.verbose) {
+      console.log(`Checking if branch/PR exists: ${branchName} / ${prTitle}`);
+    }
+    return ghGot(`repos/${config.repoName}/pulls?state=closed&head=${config.userName}:${branchName}`, { token: config.token })
+      .then(res => {
+        if (config.verbose) {
+          console.log(`Got ${res.body.length} results for ${branchName}`);
+        }
+        let prAlreadyExists = false;
+        res.body.forEach(function(result) {
+          if (result.title === prTitle && result.head.label === `${config.userName}:${branchName}`) {
+            prAlreadyExists = true;
+          }
+        });
+        if (config.verbose) {
+          if (prAlreadyExists) {
+            console.log(`PR already exists for ${branchName}`);
+          } else {
+            console.log(`PR doesn't exist for ${branchName}`);
+          }
+        }
+        return prAlreadyExists;
+      }).catch((err) => {
+        console.error('Error checking if PR already existed');
+      });
+  },
+  getFile: function(filePath, branchName) {
+    branchName = branchName || config.baseBranch;
+    return ghGot(`repos/${config.repoName}/contents/${filePath}?ref=${branchName}`, {
+      token: config.token,
+    });
+  },
+  getFileContents: function(filePath, branchName) {
+    return this.getFile(filePath, branchName).then(res => {
+      return JSON.parse(new Buffer(res.body.content, 'base64').toString());
+    });
+  },
+  getPrNo: function(branchName) {
+    return ghGot(`repos/${config.repoName}/pulls?base=${config.baseBranch}&head=${config.userName}:${branchName}`, {
+      token: config.token,
+    }).then(res => {
+      let prNo = 0;
+      res.body.forEach(function(result) {
+        if (result.state === 'open' && result.head.label === `${config.userName}:${branchName}`) {
+          prNo = result.number;
+        }
+      });
+      return prNo;
+    });
+  },
+  writeFile: function(branchName, oldFileSHA, filePath, fileContents, message) {
+    return ghGot.put(`repos/${config.repoName}/contents/${filePath}`, {
+      token: config.token,
+      body: {
+        branch: branchName,
+        sha: oldFileSHA,
+        message: message,
+        content: new Buffer(fileContents).toString('base64')
+      }
+    });
+  },
+  updatePr: function(prNo, title, body) {
+    return ghGot.patch(`repos/${config.repoName}/pulls/${prNo}`, {
+      token: config.token,
+      body: {
+        title: title,
+        body: body,
+      },
+    });
+  },
+};
diff --git a/src/index.js b/src/index.js
index 374ca1f9c217de6bc7856146b34f9294489cd5d0..1b975ea0562a43e2819ee4d7fae949366542387a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,138 +1,127 @@
-const ghGot = require('gh-got');
-const got = require('got');
 const semver = require('semver');
 const stable = require('semver-stable');
 
+const config = require('./config');
+const github = require('./github');
+const npm = require('./npm');
+
 const token = process.env.RENOVATE_TOKEN;
+// token must be defined
+if (typeof token === 'undefined') {
+  console.error('Error: Environment variable RENOVATE_TOKEN must be defined');
+  process.exit(1);
+}
+
+if (process.argv.length < 3 || process.argv.length > 4) {
+  console.error('Error: You must specify the GitHub repository and optionally path.');
+  console.log('Example: node src singapore/renovate');
+  console.log('Example: node src foo/bar baz/package.json');
+  process.exit(1);
+}
+
+// Process command line arguments
 const repoName = process.argv[2];
 const userName = repoName.split('/')[0];
 const packageFile = process.argv[3] || 'package.json';
 
-let masterSHA;
-let masterPackageJson;
+npm.init(config.verbose);
 
-ghGot(`repos/${repoName}/git/refs/head`, {token: token}).then(res => {
-  // First, get the SHA for master branch
-  res.body.forEach(function(branch) {
-    // Loop through all branches because master may not be the first
-    if (branch.ref === 'refs/heads/master') {
-      // This is the SHA we will create new branches from
-      masterSHA = branch.object.sha;
-    }
-  });
-  // Now, retrieve the master package.json
-  ghGot(`repos/${repoName}/contents/${packageFile}`, {token: token}).then(res => {
-    masterPackageJson = JSON.parse(new Buffer(res.body.content, 'base64').toString());
-    // Iterate through dependencies and then devDependencies
-    return iterateDependencies('dependencies')
-      .then(() => iterateDependencies('devDependencies'));
-  }).catch(err => {
-    console.log('Error reading master package.json');
-  });
+let basePackageJson;
+
+github.init(token, repoName, config.baseBranch, config.verbose).then(() => {
+  return github.getFileContents(packageFile);
+}).then((packageContents) => {
+  basePackageJson = packageContents;
+  return iterateDependencies('dependencies');
+}).then(() => {
+  iterateDependencies('devDependencies');
+}).catch(err => {
+  console.log('Error: ' + err);
 });
 
 function iterateDependencies(depType) {
-  const deps = masterPackageJson[depType];
+  const deps = basePackageJson[depType];
   if (!deps) {
     return;
   }
+  console.log(`Checking ${Object.keys(deps).length} ${depType}`);
   return Object.keys(deps).reduce((total, depName) => {
     return total.then(() => {
+      if (config.verbose) {
+        console.log(' * ' + depName);
+      }
       const currentVersion = deps[depName].replace(/[^\d.]/g, '');
-
       if (!semver.valid(currentVersion)) {
-        console.log('Invalid current version');
+        console.log(`${depName}: Invalid current version`);
         return;
       }
 
-      // supports scoped packages, e.g. @user/package
-      return got(`https://registry.npmjs.org/${depName.replace('/', '%2F')}`, { json: true })
-        .then(res => {
-          let allUpgrades = {};
-          Object.keys(res.body['versions']).forEach(function(version) {
-            if (stable.is(currentVersion) && !stable.is(version)) {
-              return;
-            }
-            if (semver.gt(version, currentVersion)) {
-              var thisMajor = semver.major(version);
-              if (!allUpgrades[thisMajor] || semver.gt(version, allUpgrades[thisMajor])) {
-                allUpgrades[thisMajor] = version;
-              }
-            }
-          });
-
-          let upgradePromises = [];
-
-          Object.keys(allUpgrades).forEach(function(upgrade) {
-            const nextVersion = allUpgrades[upgrade];
-            upgradePromises.push(updateDependency(depType, depName, currentVersion, nextVersion));
+      return npm.getDependencyUpgrades(depName, currentVersion)
+      .then(allUpgrades => {
+        if (config.verbose) {
+          console.log(`All upgrades for ${depName}: ${JSON.stringify(allUpgrades)}`);
+        }
+        return Object.keys(allUpgrades).reduce((promiseChain, upgrade) => {
+          return promiseChain.then(() => {
+            return updateDependency(depType, depName, currentVersion, allUpgrades[upgrade]);
           });
-
-          return Promise.all(upgradePromises);
-        });
+        }, Promise.resolve());
+      });
     });
   }, Promise.resolve());
 }
 
 function updateDependency(depType, depName, currentVersion, nextVersion) {
   const nextVersionMajor = semver.major(nextVersion);
-  const branchName = `upgrade/${depName}-${nextVersionMajor}.x`;
-  let prName = '';
+  const branchName = config.templates.branchName({depType, depName, currentVersion, nextVersion, nextVersionMajor});
+  let prTitle = '';
   if (nextVersionMajor > semver.major(currentVersion)) {
-    prName = `Upgrade dependency ${depName} to version ${nextVersionMajor}.x`;
-    // Check if PR was already closed previously
-    ghGot(`repos/${repoName}/pulls?state=closed&head=${userName}:${branchName}`, { token: token })
-      .then(res => {
-        if (res.body.length > 0) {
-          console.log(`Dependency ${depName} upgrade to ${nextVersionMajor}.x PR already existed, so skipping`);
-        } else {
-          writeUpdates(depType, depName, branchName, prName, currentVersion, nextVersion);
-        }
-      });
+    prTitle = config.templates.prTitleMajor({ depType, depName, currentVersion, nextVersion, nextVersionMajor });
   } else {
-    prName = `Upgrade dependency ${depName} to version ${nextVersion}`;
-    writeUpdates(depType, depName, branchName, prName, currentVersion, nextVersion);
+    prTitle = config.templates.prTitleMinor({ depType, depName, currentVersion, nextVersion, nextVersionMajor });
   }
+  // Check if same PR already exists or existed
+  return github.checkPrExists(branchName, prTitle).then((prExisted) => {
+    if (!prExisted) {
+      return writeUpdates(depType, depName, branchName, prTitle, currentVersion, nextVersion);
+    } else {
+      console.log(`${depName}: Skipping due to existing PR found.`);
+    }
+  });
 }
 
-function writeUpdates(depType, depName, branchName, prName, currentVersion, nextVersion) {
-  const commitMessage = `Upgrade dependency ${depName} to version ${nextVersion}`;
-  const prBody = `This Pull Request updates dependency ${depName} from version ${currentVersion} to ${nextVersion}.`;
-  // Try to create branch
-  const body = {
-    ref: `refs/heads/${branchName}`,
-    sha: masterSHA
-  };
-  ghGot.post(`repos/${repoName}/git/refs`, {
-    token: token,
-    body: body
-  }).catch(error => {
+function writeUpdates(depType, depName, branchName, prTitle, currentVersion, nextVersion) {
+  const prBody = config.templates.prBody({ depName, currentVersion, nextVersion });
+  return github.createBranch(branchName).catch(error => {
     if (error.response.body.message !== 'Reference already exists') {
-      console.log('Error creating branch' + branchName);
+      console.log('Error creating branch: ' + branchName);
       console.log(error.response.body);
     }
   }).then(res => {
-    ghGot(`repos/${repoName}/contents/${packageFile}?ref=${branchName}`, { token: token })
-    .then(res => {
+    if (config.verbose) {
+      console.log(`Branch exists (${branchName}), now writing file`);
+    }
+    return github.getFile(packageFile, branchName).then(res => {
       const oldFileSHA = res.body.sha;
-      let branchPackageJson = JSON.parse(new Buffer(res.body.content, 'base64').toString());
-      if (branchPackageJson[depType][depName] !== nextVersion) {
+      let currentFileContent = JSON.parse(new Buffer(res.body.content, 'base64').toString());
+      if (currentFileContent[depType][depName] !== nextVersion) {
         // Branch is new, or needs version updated
-        console.log(`Dependency ${depName} needs upgrading to ${nextVersion}`);
-        branchPackageJson[depType][depName] = nextVersion;
-        branchPackageString = JSON.stringify(branchPackageJson, null, 2) + '\n';
+        currentFileContent[depType][depName] = nextVersion;
+        const newPackageString = JSON.stringify(currentFileContent, null, 2) + '\n';
 
-        ghGot.put(`repos/${repoName}/contents/${packageFile}`, {
-          token: token,
-          body: {
-            branch: branchName,
-            sha: oldFileSHA,
-            message: commitMessage,
-            content: new Buffer(branchPackageString).toString('base64')
-          }
-        }).then(res => {
-          return createOrUpdatePullRequest(branchName, prName, prBody);
+        var commitMessage = config.templates.commitMessage({ depName, currentVersion, nextVersion });
+
+        return github.writeFile(branchName, oldFileSHA, packageFile, newPackageString, commitMessage)
+        .then(() => {
+          return createOrUpdatePullRequest(branchName, prTitle, prBody);
+        })
+        .catch(err => {
+          console.error('Error writing new package file for ' + depName);
+          console.log(err);
         });
+      } else {
+        // File was up to date. Ensure PR
+        return createOrUpdatePullRequest(branchName, prTitle, prBody);
       }
     });
   })
@@ -141,44 +130,22 @@ function writeUpdates(depType, depName, branchName, prName, currentVersion, next
   });
 }
 
-function createOrUpdatePullRequest(branchName, title, body) {
-  return ghGot.post(`repos/${repoName}/pulls`, {
-    token: token,
-    body: {
-      title: title,
-      head: branchName,
-      base: 'master',
-      body: body,
-    }
-  }).then(res => {
-    console.log('Created Pull Request: ' + title);
-  }).catch(error => {
-    if (error.response.body.errors[0].message.indexOf('A pull request already exists') === 0) {
-      // Pull Request already exists
-      // Now we need to find the Pull Request number
-      return ghGot(`repos/${repoName}/pulls?base=master&head=${userName}:${branchName}`, {
-        token: token,
-      }).then(res => {
-        // TODO iterate through list and confirm branch
-        if (res.body.length !== 1) {
-          console.error('Could not find matching PR');
-          return;
-        }
-        const existingPrNo = res.body[0].number;
-        return ghGot.patch(`repos/${repoName}/pulls/${existingPrNo}`, {
-          token: token,
-          body: {
-            title: title,
-            body: body,
-          }
-        }).then(res => {
-          console.log('Updated Pull Request: ' + title);
-        });
+function createOrUpdatePullRequest(branchName, prTitle, prBody) {
+  return github.getPrNo(branchName).then(prNo => {
+    if (prNo) {
+      // PR already exists - update it
+      // Note: PR might be unchanged, so no log message
+      return github.updatePr(prNo, prTitle, prBody)
+      .catch(err => {
+        console.error('Error: Failed to update Pull Request: ' + prTitle);
+        console.log(err);
       });
-    } else {
-      console.log('Error creating Pull Request:');
-      console.log(error.response.body);
-      Promise.reject();
     }
+    return github.createPr(branchName, prTitle, prBody).then(res => {
+      console.log('Created Pull Request: ' + prTitle);
+    }).catch(err => {
+      console.error('Error: Failed to create Pull Request: ' + prTitle);
+      console.log(err);
+    });
   });
 }
diff --git a/src/npm.js b/src/npm.js
new file mode 100644
index 0000000000000000000000000000000000000000..d14aaf45716df1d577218e39671c17c91d1280bb
--- /dev/null
+++ b/src/npm.js
@@ -0,0 +1,37 @@
+const got = require('got');
+const semver = require('semver');
+const stable = require('semver-stable');
+
+var config = {};
+
+module.exports = {
+  init: function(verbose = false) {
+    config.verbose = verbose;
+  },
+  getDependency(depName) {
+    if (config.verbose) {
+      console.log(`Looking up npm for ${depName}`);
+    }
+    // supports scoped packages, e.g. @user/package
+    return got(`https://registry.npmjs.org/${depName.replace('/', '%2F')}`, { json: true });
+  },
+  getDependencyUpgrades(depName, currentVersion) {
+    return this.getDependency(depName).then(res => {
+      let allUpgrades = {};
+      Object.keys(res.body['versions']).forEach(function(version) {
+        if (stable.is(currentVersion) && !stable.is(version)) {
+          // Ignore unstable versions, unless the current version is unstable
+          return;
+        }
+        if (semver.gt(version, currentVersion)) {
+          // Group by major versions
+          var thisMajor = semver.major(version);
+          if (!allUpgrades[thisMajor] || semver.gt(version, allUpgrades[thisMajor])) {
+            allUpgrades[thisMajor] = version;
+          }
+        }
+      });
+      return allUpgrades;
+    });
+  },
+};