Move code into github and npm helper libraries

commit 8e84875bd5f7e4584d707d88d6850565bb02c79c
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Sat Jan 7 08:22:21 2017 +0100

    Synchronous

commit 0f24ea192bcf54aae1264e91a4b6eb98fea55448
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Sat Jan 7 07:12:20 2017 +0100

    externalise more npm

commit 458d60975fc967f1373c81cd0fa28a9717dd9b0b
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 15:45:08 2017 +0100

    Externalise npm

commit 5d4f39e72d2977af1fec12d7a0a39d3877e4ad02
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 15:35:16 2017 +0100

    Remove ghGot

commit 06898801c1e591d6db9e6ac1e565233af5e9be7e
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 15:34:43 2017 +0100

    Externalise PR functions

commit 0b0e0f781b3384ad57a1df3df7d1089b2c72079a
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 15:34:25 2017 +0100

    Enable verbose

commit 4cebf1e0a80d7e14b9704c5fd7e5d0b036b9661a
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 14:23:12 2017 +0100

    verbose

commit 5a984b91e099cccb5c9dff857a6be07b3b4dedd5
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 14:22:59 2017 +0100

    Change default branch naming

commit ab9bc952c81d16be9be57227382dff8d05e73f54
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 13:05:08 2017 +0100

    Fix branch matching

commit eeecf17e196245964aed5247cf1703619d42b0d4
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 11:15:16 2017 +0100

    Update message

commit d27b345c5eb51dcb7e32b903beafe0728e24bfdb
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 11:09:39 2017 +0100

    Refactor file write

commit 7f12ef69f456ecd064be5d9851157131222f7700
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 10:59:24 2017 +0100

    Refactor writeFile

commit 8c7cc9e6a6c7e398aa60cb828c16ff51f36f2efa
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 10:39:27 2017 +0100

    Refactor getFile

commit b4338ade6d29b830ead657267248c93216c2f91d
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 10:15:02 2017 +0100

    refactor

commit dc4aeb39dad367844836da7f93e9f167864f6030
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 10:14:34 2017 +0100

    createBranch

commit d6a357f609de55d7b934652f30592219391a9884
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 10:04:04 2017 +0100

    Add createBranch

commit 11ba4e9f6c2153d7b783670944570cb4968ff718
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 07:27:08 2017 +0100

    Rename

commit 7a4be0fde0e070e2149bc4c34397c4903096ac51
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 07:17:31 2017 +0100

    Externalise some github functions

commit e393e92bcc9cb548fac3637644b0330a136f3611
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 07:17:19 2017 +0100

    Fix error message

commit 59fb50656d84491780bc31bab4cb9263a7912c03
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Fri Jan 6 07:16:59 2017 +0100

    Improve error checks

commit bc44b3a0d820ab5756c3b3c746402329e5b52703
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Thu Jan 5 15:34:04 2017 +0100

    Make base branch configurable

commit b9d31776814723d991a226d1ca1b2f39d0d2af85
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Thu Jan 5 15:33:44 2017 +0100

    Reorder early lines

commit b75f9f25cfb86f029b73445aae67b7889ff09b3e
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Thu Jan 5 15:26:47 2017 +0100

    Error if RENOVATE_TOKEN is undefined

    Closes #11

commit 34e13a70326a71b3ee7f18c12ec3de55b78bcaa1
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Thu Jan 5 14:43:42 2017 +0100

    arrow functions

commit 6006db2deae887938bc20a07c93d1a59bd8cd74e
Author: Rhys Arkins <rhys@keylocation.sg>
Date:   Thu Jan 5 14:39:30 2017 +0100

    Refactor templates
This commit is contained in:
Rhys Arkins 2017-01-07 08:22:48 +01:00
Родитель 24d23dfc11
Коммит b4aaa65b5c
4 изменённых файлов: 274 добавлений и 130 удалений

21
src/config.js Normal file
Просмотреть файл

@ -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}`;
},
}
};

119
src/github.js Normal file
Просмотреть файл

@ -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,
},
});
},
};

Просмотреть файл

@ -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;
}
}
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]);
});
let upgradePromises = [];
Object.keys(allUpgrades).forEach(function(upgrade) {
const nextVersion = allUpgrades[upgrade];
upgradePromises.push(updateDependency(depType, depName, currentVersion, nextVersion));
});
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);
});
});
}

37
src/npm.js Normal file
Просмотреть файл

@ -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;
});
},
};