mirror of https://github.com/renovatebot/renovate
194 lines
6.7 KiB
JavaScript
194 lines
6.7 KiB
JavaScript
const changelog = require('changelog');
|
|
const github = require('./helpers/github');
|
|
const npm = require('./helpers/npm');
|
|
const packageJson = require('./helpers/package-json');
|
|
|
|
let config = null;
|
|
let logger = null;
|
|
|
|
module.exports = function init(setConfig) {
|
|
config = setConfig;
|
|
logger = config.logger;
|
|
|
|
// Initialize helpers
|
|
github.init(config.token, logger);
|
|
npm.setLogger(logger);
|
|
packageJson.setLogger(logger);
|
|
changelog.setGitHubToken(config.token);
|
|
|
|
return processPackageFile;
|
|
};
|
|
|
|
// This function manages the queue per-package file
|
|
function processPackageFile(repoName, packageFile) {
|
|
return github.initRepo(repoName)
|
|
.then(() => github.getPackageFileContents(packageFile))
|
|
.then(npm.extractDependencies)
|
|
.then(npm.findUpgrades)
|
|
.then(processUpgradesSequentially)
|
|
.then(() => { // eslint-disable-line promise/always-return
|
|
logger.info(`${repoName} ${packageFile} done`);
|
|
})
|
|
.catch((error) => {
|
|
logger.error(`renovate caught error: ${error}`);
|
|
});
|
|
}
|
|
|
|
function processUpgradesSequentially(upgrades) {
|
|
if (Object.keys(upgrades).length) {
|
|
logger.verbose('Processing upgrades');
|
|
} else {
|
|
logger.verbose('No upgrades to process');
|
|
}
|
|
logger.verbose(`All upgrades: ${JSON.stringify(upgrades)}`);
|
|
// We are processing each upgrade sequentially for two major reasons:
|
|
// 1. Reduce chances of GitHub API rate limiting
|
|
// 2. Edge case collision of branch name, e.g. dependency also listed as dev dependency
|
|
return upgrades.reduce(
|
|
(promise, upgrade) => promise
|
|
.then(() => getChangelog(upgrade))
|
|
.then(updateDependency), Promise.resolve());
|
|
}
|
|
|
|
function getChangelog(upgrade) {
|
|
if (!upgrade.workingVersion || upgrade.workingVersion === upgrade.newVersion) {
|
|
return Object.assign(upgrade, { changelog: '' });
|
|
}
|
|
const semverString = `>${upgrade.workingVersion} <=${upgrade.newVersion}`;
|
|
let log = '';
|
|
logger.debug(`semverString: ${semverString}`);
|
|
return changelog.generate(upgrade.depName, semverString)
|
|
.then(changelog.markdown)
|
|
.then((res) => {
|
|
log = res;
|
|
return logger.silly(`${upgrade.depName} ${upgrade.newVersion} changelog: ${res}`);
|
|
})
|
|
.catch((error) => {
|
|
logger.verbose(`getChangelog error: ${error}`);
|
|
})
|
|
.then(() => Object.assign(upgrade, { changelog: log }));
|
|
}
|
|
|
|
function updateDependency(upgrade) {
|
|
// Expand upgrade params
|
|
const depType = upgrade.depType;
|
|
const depName = upgrade.depName;
|
|
const newVersion = upgrade.newVersion;
|
|
// Use templates to generate strings
|
|
const branchName = config.templates.branchName(upgrade);
|
|
let commitFunction = null;
|
|
let prFunction = null;
|
|
if (upgrade.upgradeType === 'pin') {
|
|
commitFunction = config.templates.commitMessagePin;
|
|
prFunction = config.templates.prTitlePin;
|
|
} else if (upgrade.upgradeType === 'minor') {
|
|
// Use same title for range or minor
|
|
commitFunction = config.templates.commitMessage;
|
|
prFunction = config.templates.prTitleMinor;
|
|
} else {
|
|
commitFunction = config.templates.commitMessage;
|
|
prFunction = config.templates.prTitleMajor;
|
|
}
|
|
const commitMessage = commitFunction(upgrade);
|
|
const prTitle = prFunction(upgrade);
|
|
const prBody = config.templates.prBody(upgrade);
|
|
|
|
// Check if same PR already existed and skip if so
|
|
// This allows users to close an unwanted upgrade PR and not worry about seeing it raised again
|
|
return github.checkForClosedPr(branchName, prTitle).then((prExisted) => {
|
|
if (prExisted) {
|
|
logger.verbose(`${depName}: Skipping due to existing PR found.`);
|
|
return Promise.resolve();
|
|
}
|
|
return ensureAll();
|
|
});
|
|
function ensureAll() {
|
|
return ensureBranch()
|
|
.then(ensureCommit)
|
|
.then(ensurePr)
|
|
.catch((error) => {
|
|
logger.error(`Error updating dependency ${depName}: ${error}`);
|
|
// Don't throw here - we don't want to stop the other renovations
|
|
});
|
|
}
|
|
function ensureBranch() {
|
|
// Save an API call by attempting to create branch without checking for existence first
|
|
return github.createBranch(branchName).catch((error) => {
|
|
// Check in case it's because the branch already existed
|
|
if (error.response.body.message !== 'Reference already exists') {
|
|
// In this case it means we really do have a problem and can't continue
|
|
logger.error(`Error creating branch: ${branchName}`);
|
|
logger.error(`Response body: ${error.response.body}`);
|
|
throw error;
|
|
}
|
|
// Otherwise we swallow this error and continue
|
|
});
|
|
}
|
|
function ensureCommit() {
|
|
// Retrieve the package.json from this renovate branch
|
|
return github.getPackageFile(branchName).then((res) => {
|
|
const currentSHA = res.body.sha;
|
|
const currentFileContent = new Buffer(res.body.content, 'base64').toString();
|
|
const currentJson = JSON.parse(currentFileContent);
|
|
if (currentJson[depType][depName] === newVersion) {
|
|
logger.verbose(`${depName}: branch ${branchName} is already up-to-date`);
|
|
return Promise.resolve();
|
|
}
|
|
// Branch must need updating
|
|
logger.verbose(`${depName}: Updating to ${newVersion} in branch ${branchName}`);
|
|
const newPackageContents = packageJson.setNewValue(
|
|
currentFileContent,
|
|
depType,
|
|
depName,
|
|
newVersion);
|
|
return github.writePackageFile(
|
|
branchName,
|
|
currentSHA,
|
|
newPackageContents,
|
|
commitMessage);
|
|
})
|
|
.catch((error) => {
|
|
logger.error(`${depName} ensureCommit error: ${error}`);
|
|
throw error;
|
|
});
|
|
}
|
|
|
|
// Ensures that PR exists with matching title/body
|
|
function ensurePr() {
|
|
// Create PR based on current state
|
|
function createPr() {
|
|
return github.createPr(branchName, prTitle, prBody).then((newPr) => {
|
|
logger.info(`${depName}: Created PR #${newPr.number}`);
|
|
return Promise.resolve();
|
|
});
|
|
}
|
|
// Update PR based on current state
|
|
function updatePr(existingPr) {
|
|
return github.updatePr(existingPr.number, prTitle, prBody).then(() => {
|
|
logger.info(`${depName}: Updated PR #${existingPr.number}`);
|
|
return Promise.resolve();
|
|
});
|
|
}
|
|
// Process a returned PR
|
|
function processExistingPr(existingPr) {
|
|
if (!existingPr) {
|
|
// We need to create a new PR
|
|
return createPr();
|
|
}
|
|
// Check if existing PR needs updating
|
|
if (existingPr.title === prTitle && existingPr.body === prBody) {
|
|
logger.verbose(`${depName}: PR #${existingPr.number} already up-to-date`);
|
|
return Promise.resolve();
|
|
}
|
|
// PR must need updating
|
|
return updatePr(existingPr);
|
|
}
|
|
|
|
return github.getPr(branchName)
|
|
.then(processExistingPr)
|
|
.catch((error) => {
|
|
logger.error(`${depName} failed to ensure PR: ${error}`);
|
|
});
|
|
}
|
|
}
|