Only generate entries for commits/PRs that do not already exist in the CHANGELOG upon re-generation

This is helpful when commits are added after starting to work on the changelog entry.
This commit is contained in:
Jérémie Astori 2019-01-07 00:32:18 -05:00
parent 0995f95296
commit 41c1b3275e
No known key found for this signature in database
GPG key ID: B9A4F245CD67BDE8

View file

@ -59,6 +59,8 @@ const token = process.env.CHANGELOG_TOKEN;
const readFile = util.promisify(fs.readFile); const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile); const writeFile = util.promisify(fs.writeFile);
const changelogPath = "./CHANGELOG.md";
// CLI argument validations // CLI argument validations
if (token === undefined) { if (token === undefined) {
@ -115,6 +117,13 @@ yarn global add thelounge@next
`; `;
} }
// Check if the object is empty, or if all array values within this object are
// empty
function isEmpty(list) {
const values = Object.values(list);
return values.length === 0 || values.every((entries) => entries.length === 0);
}
function stableTemplate(items) { function stableTemplate(items) {
return ` return `
## v${items.version} - ${items.date} ## v${items.version} - ${items.date}
@ -129,7 +138,7 @@ For more details, [see the full changelog](${items.fullChangelogUrl}) and [miles
### Changed ### Changed
${_.isEmpty(items.dependencies) ? "" : ${isEmpty(items.dependencies) ? "" :
`- Update production dependencies to their latest versions: `- Update production dependencies to their latest versions:
${printDependencyList(items.dependencies)}` ${printDependencyList(items.dependencies)}`
} }
@ -148,13 +157,13 @@ ${printList(items.security)}
### Documentation ### Documentation
${_.isEmpty(items.documentation) ? "" : ${items.documentation.length === 0 ? "" :
`In the main repository: `In the main repository:
${printList(items.documentation)}` ${printList(items.documentation)}`
} }
${_.isEmpty(items.websiteDocumentation) ? "" : ${items.websiteDocumentation.length === 0 ? "" :
`On the [website repository](https://github.com/thelounge/thelounge.github.io): `On the [website repository](https://github.com/thelounge/thelounge.github.io):
${printList(items.websiteDocumentation)}` ${printList(items.websiteDocumentation)}`
@ -162,8 +171,7 @@ ${printList(items.websiteDocumentation)}`
### Internals ### Internals
${printList(items.internals)}${ ${printList(items.internals)}${isEmpty(items.devDependencies) ? "" : `
_.isEmpty(items.devDependencies) ? "" : `
- Update development dependencies to their latest versions: - Update development dependencies to their latest versions:
${printDependencyList(items.devDependencies)}`} ${printDependencyList(items.devDependencies)}`}
@ -441,16 +449,20 @@ function combine(allCommits, allPullRequests) {
}, []); }, []);
} }
// Builds a Markdown link for a given pull request object
function printPullRequestLink({number, url}) {
return `[#${number}](${url})`;
}
// Builds a Markdown link for a given author object // Builds a Markdown link for a given author object
function printAuthorLink({login, url}) { function printAuthorLink({login, url}) {
return `by [@${login}](${url})`; return `by [@${login}](${url})`;
} }
// Builds a Markdown link for a given pull request or commit object
function printEntryLink(entry) {
const label = entry.title
? `#${entry.number}`
: `\`${entry.abbreviatedOid}\``;
return `[${label}](${entry.url})`;
}
// Builds a Markdown entry list item depending on its type // Builds a Markdown entry list item depending on its type
function printLine(entry) { function printLine(entry) {
if (entry.title) { if (entry.title) {
@ -462,12 +474,12 @@ function printLine(entry) {
// Builds a Markdown list item for a given pull request // Builds a Markdown list item for a given pull request
function printPullRequest(pullRequest) { function printPullRequest(pullRequest) {
return `- ${pullRequest.title} (${printPullRequestLink(pullRequest)} ${printAuthorLink(pullRequest.author)})`; return `- ${pullRequest.title} (${printEntryLink(pullRequest)} ${printAuthorLink(pullRequest.author)})`;
} }
// Builds a Markdown list item for a commit made directly in `master` // Builds a Markdown list item for a commit made directly in `master`
function printCommit({abbreviatedOid, messageHeadline, url, author}) { function printCommit(commit) {
return `- ${messageHeadline} ([\`${abbreviatedOid}\`](${url}) ${printAuthorLink(author)})`; return `- ${commit.messageHeadline} (${printEntryLink(commit)} ${printAuthorLink(commit.author)})`;
} }
// Builds a Markdown list of all given items // Builds a Markdown list of all given items
@ -478,9 +490,15 @@ function printList(items) {
// Given a "dependencies object" (i.e. keys are package names, values are arrays // Given a "dependencies object" (i.e. keys are package names, values are arrays
// of pull request numbers), builds a Markdown list of URLs // of pull request numbers), builds a Markdown list of URLs
function printDependencyList(dependencies) { function printDependencyList(dependencies) {
return _.map(dependencies, (pullRequests, name) => const list = [];
` - \`${name}\` (${pullRequests.map(printPullRequestLink).join(", ")})`
).join("\n"); Object.entries(dependencies).forEach(([name, entries]) => {
if (entries.length > 0) {
list.push(` - \`${name}\` (${entries.map(printEntryLink).join(", ")})`);
}
});
return list.join("\n");
} }
function printUncategorizedList(uncategorized) { function printUncategorizedList(uncategorized) {
@ -651,6 +669,27 @@ function parse(entries) {
}); });
} }
function dedupeEntries(changelog, items) {
const isNewEntry = (entry) => !changelog.includes(printEntryLink(entry));
items.deprecations = items.deprecations.filter(isNewEntry);
items.documentation = items.documentation.filter(isNewEntry);
items.websiteDocumentation = items.websiteDocumentation.filter(isNewEntry);
items.internals = items.documentation.filter(isNewEntry);
items.security = items.documentation.filter(isNewEntry);
items.uncategorized.feature = items.uncategorized.feature.filter(isNewEntry);
items.uncategorized.bug = items.uncategorized.bug.filter(isNewEntry);
items.uncategorized.other = items.uncategorized.other.filter(isNewEntry);
Object.entries(items.dependencies).forEach(([name, entries]) => {
items.dependencies[name] = entries.filter(isNewEntry);
});
Object.entries(items.devDependencies).forEach(([name, entries]) => {
items.devDependencies[name] = entries.filter(isNewEntry);
});
}
// Given a list of entries (pull requests, commits), retrieves GitHub usernames // Given a list of entries (pull requests, commits), retrieves GitHub usernames
// (with format `@username`) of everyone who contributed to this version. // (with format `@username`) of everyone who contributed to this version.
function extractContributors(entries) { function extractContributors(entries) {
@ -675,7 +714,7 @@ const client = new GraphQLClient("https://api.github.com/graphql", {
// Main function. Given a version string (i.e. not a tag!), returns a changelog // Main function. Given a version string (i.e. not a tag!), returns a changelog
// entry and the list of contributors, for both pre-releases and stable // entry and the list of contributors, for both pre-releases and stable
// releases. Templates are located at the top of this file. // releases. Templates are located at the top of this file.
async function generateChangelogEntry(targetVersion) { async function generateChangelogEntry(changelog, targetVersion) {
let items = {}; let items = {};
let template; let template;
let contributors = []; let contributors = [];
@ -696,6 +735,8 @@ async function generateChangelogEntry(targetVersion) {
const websiteRepo = new RepositoryFetcher(client, "thelounge.github.io"); const websiteRepo = new RepositoryFetcher(client, "thelounge.github.io");
const previousWebsiteVersion = await websiteRepo.fetchPreviousVersion(targetVersion); const previousWebsiteVersion = await websiteRepo.fetchPreviousVersion(targetVersion);
items.websiteDocumentation = await websiteRepo.fetchCommitsAndPullRequestsSince("v" + previousWebsiteVersion); items.websiteDocumentation = await websiteRepo.fetchCommitsAndPullRequestsSince("v" + previousWebsiteVersion);
dedupeEntries(changelog, items);
} }
items.version = targetVersion; items.version = targetVersion;
@ -711,11 +752,9 @@ async function generateChangelogEntry(targetVersion) {
// Write a changelog entry into the CHANGELOG.md file, right after a marker that // Write a changelog entry into the CHANGELOG.md file, right after a marker that
// indicates where entries are listed. // indicates where entries are listed.
async function addToChangelog(newEntry) { function addToChangelog(changelog, newEntry) {
const changelogPath = "./CHANGELOG.md";
const changelogMarker = "<!-- New entries go after this line -->\n\n"; const changelogMarker = "<!-- New entries go after this line -->\n\n";
const changelog = await readFile(changelogPath, "utf8");
const markerPosition = changelog.indexOf(changelogMarker) + changelogMarker.length; const markerPosition = changelog.indexOf(changelogMarker) + changelogMarker.length;
const newChangelog = const newChangelog =
changelog.substring(0, markerPosition) + changelog.substring(0, markerPosition) +
@ -734,8 +773,10 @@ async function addToChangelog(newEntry) {
// Step 1: Generate a changelog entry // Step 1: Generate a changelog entry
const changelog = await readFile(changelogPath, "utf8");
try { try {
({changelogEntry, skipped, contributors} = await generateChangelogEntry(version)); ({changelogEntry, skipped, contributors} = await generateChangelogEntry(changelog, version));
} catch (error) { } catch (error) {
if (error.response && error.response.status === 401) { if (error.response && error.response.status === 401) {
log.error(`GitHub returned an error: ${colors.red(error.response.message)}`); log.error(`GitHub returned an error: ${colors.red(error.response.message)}`);
@ -750,7 +791,7 @@ async function addToChangelog(newEntry) {
// Step 2: Write that changelog entry into the CHANGELOG.md file // Step 2: Write that changelog entry into the CHANGELOG.md file
try { try {
await addToChangelog(`${changelogEntry.trim()}\n\n`); await addToChangelog(changelog, `${changelogEntry.trim()}\n\n`);
} catch (error) { } catch (error) {
log.error(error); log.error(error);
process.exit(1); process.exit(1);