renovate/lib/workers/pr/pr-body.js
2019-04-12 10:23:05 +02:00

214 lines
6.7 KiB
JavaScript

const is = require('@sindresorhus/is');
const handlebars = require('handlebars');
const releaseNotesHbs = require('./changelog/hbs-template');
const { getPrConfigDescription } = require('./pr-body-config');
const versioning = require('../../versioning');
const { appName, appSlug } = require('../../config/app-strings');
handlebars.registerHelper('encodeURIComponent', encodeURIComponent);
module.exports = {
getPrBody,
};
function getTableDefinition(config) {
const res = [];
for (const header of config.prBodyColumns) {
const value = config.prBodyDefinitions[header];
res.push({ header, value });
}
return res;
}
function getNonEmptyColumns(definitions, rows) {
const res = [];
for (const column of definitions) {
const { header } = column;
for (const row of rows) {
if (row[header] && row[header].length) {
if (!res.includes(header)) {
res.push(header);
}
}
}
}
return res;
}
async function getPrBody(config) {
config.upgrades.forEach(upgrade => {
/* eslint-disable no-param-reassign */
const { homepage, sourceUrl, sourceDirectory, changelogUrl } = upgrade;
const references = [];
if (homepage) {
references.push(`[homepage](${homepage})`);
}
if (sourceUrl) {
let fullUrl = sourceUrl;
if (sourceDirectory) {
fullUrl =
sourceUrl.replace(/\/?$/, '/') +
'tree/HEAD/' +
sourceDirectory.replace('^/?/', '');
}
references.push(`[source](${fullUrl})`);
}
if (changelogUrl) {
references.push(`[changelog](${changelogUrl})`);
}
upgrade.references = references.join(', ');
const {
fromVersion,
toVersion,
newValue,
newDigestShort,
updateType,
versionScheme,
} = upgrade;
// istanbul ignore if
if (updateType === 'minor') {
try {
const { getMinor } = versioning.get(versionScheme);
if (getMinor(fromVersion) === getMinor(toVersion)) {
upgrade.updateType = 'patch';
}
} catch (err) {
// do nothing
}
}
if (newDigestShort) {
if (updateType === 'pin') {
upgrade.newValue = config.newDigestShort;
}
if (newValue) {
upgrade.newValue = newValue + '@' + newDigestShort;
} else {
upgrade.newValue = newDigestShort;
}
} else if (updateType !== 'lockFileMaintenance') {
upgrade.newValue = newValue;
}
/* eslint-enable no-param-reassign */
});
const tableDefinitions = getTableDefinition(config);
const tableValues = config.upgrades.map(upgrade => {
const res = {};
for (const column of tableDefinitions) {
const { header, value } = column;
try {
// istanbul ignore else
if (value) {
res[header] = handlebars
.compile(value)(upgrade)
.replace(/^``$/, '');
} else {
res[header] = '';
}
} catch (err) /* istanbul ignore next */ {
logger.warn({ header, value, err }, 'Handlebars compilation error');
}
}
return res;
});
const tableColumns = getNonEmptyColumns(tableDefinitions, tableValues);
let prBody = '';
// istanbul ignore if
if (config.prBanner && !config.isGroup) {
prBody += handlebars.compile(config.prBanner)(config) + '\n\n';
}
prBody += '\n\nThis PR contains the following updates:\n\n';
prBody += '| ' + tableColumns.join(' | ') + ' |\n';
prBody += '|' + tableColumns.map(() => '---|').join('') + '\n';
const rows = [];
for (const row of tableValues) {
let val = '|';
for (const column of tableColumns) {
val += ` ${row[column].replace(/^@/, '@​')} |`;
}
val += '\n';
rows.push(val);
}
const uniqueRows = [...new Set(rows)];
prBody += uniqueRows.join('');
prBody += '\n\n';
const notes = [];
for (const upgrade of config.upgrades) {
if (is.nonEmptyArray(upgrade.prBodyNotes)) {
for (const note of upgrade.prBodyNotes) {
try {
const res = handlebars
.compile(note)(upgrade)
.trim();
if (res && res.length) {
notes.push(res);
}
} catch (err) {
logger.warn({ note }, 'Error compiling upgrade note');
}
}
}
}
const uniqueNotes = [...new Set(notes)];
prBody += uniqueNotes.join('\n\n');
prBody += '\n\n';
if (config.upgrades.some(upgrade => upgrade.gitRef)) {
prBody +=
':abcd: If you wish to disable git hash updates, add `":disableDigestUpdates"` to the extends array in your config.\n\n';
}
if (config.updateType === 'lockFileMaintenance') {
prBody +=
':wrench: This Pull Request updates `package.json` lock files to use the latest dependency versions.\n\n';
}
if (config.isPin) {
prBody += `:pushpin: **Important**: ${appName} will wait until you have merged this Pin PR before creating any *upgrade* PRs for the affected packages. Add the preset \`:preserveSemverRanges\` your config if you instead don't wish to pin dependencies.\n\n`;
}
if (config.hasReleaseNotes) {
let releaseNotes =
'\n\n---\n\n' + handlebars.compile(releaseNotesHbs)(config) + '\n\n';
releaseNotes = releaseNotes.replace(/### \[`vv/g, '### [`v');
// Generic replacements/link-breakers
// Put a zero width space after every # followed by a digit
releaseNotes = releaseNotes.replace(/#(\d)/gi, '#​$1');
// Put a zero width space after every @ symbol to prevent unintended hyperlinking
releaseNotes = releaseNotes.replace(/@/g, '@​');
releaseNotes = releaseNotes.replace(/(`\[?@)​/g, '$1');
releaseNotes = releaseNotes.replace(/([a-z]@)​/gi, '$1');
releaseNotes = releaseNotes.replace(/\/compare\/@​/g, '/compare/@');
releaseNotes = releaseNotes.replace(
/(\(https:\/\/[^)]*?)\.\.\.@​/g,
'$1...@'
);
releaseNotes = releaseNotes.replace(
/([\s(])#(\d+)([)\s]?)/g,
'$1#​$2$3'
);
// convert escaped backticks back to `
const backTickRe = /`([^/]*?)`/g;
releaseNotes = releaseNotes.replace(backTickRe, '`$1`');
releaseNotes = releaseNotes.replace(/`#​(\d+)`/g, '`#$1`');
prBody += releaseNotes;
}
prBody += await getPrConfigDescription(config);
prBody += `\n\n---\n\n - [ ] <!-- ${appSlug}-rebase -->If you want to rebase/retry this PR, check this box\n\n`;
// istanbul ignore if
if (config.global) {
if (config.global.prBanner) {
prBody = config.global.prBanner + '\n\n' + prBody;
}
if (config.global.prFooter) {
prBody = prBody + '\n---\n\n' + config.global.prFooter;
}
}
prBody = prBody.trim();
prBody = prBody.replace(/\n\n\n+/g, '\n\n');
prBody = platform.getPrBody(prBody);
return prBody;
}