From 41c1b3275eea76bdee1d69bff7fce075068e24ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Mon, 7 Jan 2019 00:32:18 -0500 Subject: [PATCH] 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. --- scripts/changelog.js | 85 ++++++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/scripts/changelog.js b/scripts/changelog.js index 7fb705c0..39ad7584 100644 --- a/scripts/changelog.js +++ b/scripts/changelog.js @@ -59,6 +59,8 @@ const token = process.env.CHANGELOG_TOKEN; const readFile = util.promisify(fs.readFile); const writeFile = util.promisify(fs.writeFile); +const changelogPath = "./CHANGELOG.md"; + // CLI argument validations 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) { return ` ## v${items.version} - ${items.date} @@ -129,7 +138,7 @@ For more details, [see the full changelog](${items.fullChangelogUrl}) and [miles ### Changed -${_.isEmpty(items.dependencies) ? "" : +${isEmpty(items.dependencies) ? "" : `- Update production dependencies to their latest versions: ${printDependencyList(items.dependencies)}` } @@ -148,13 +157,13 @@ ${printList(items.security)} ### Documentation -${_.isEmpty(items.documentation) ? "" : +${items.documentation.length === 0 ? "" : `In the main repository: ${printList(items.documentation)}` } -${_.isEmpty(items.websiteDocumentation) ? "" : +${items.websiteDocumentation.length === 0 ? "" : `On the [website repository](https://github.com/thelounge/thelounge.github.io): ${printList(items.websiteDocumentation)}` @@ -162,8 +171,7 @@ ${printList(items.websiteDocumentation)}` ### Internals -${printList(items.internals)}${ - _.isEmpty(items.devDependencies) ? "" : ` +${printList(items.internals)}${isEmpty(items.devDependencies) ? "" : ` - Update development dependencies to their latest versions: ${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 function printAuthorLink({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 function printLine(entry) { if (entry.title) { @@ -462,12 +474,12 @@ function printLine(entry) { // Builds a Markdown list item for a given pull request 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` -function printCommit({abbreviatedOid, messageHeadline, url, author}) { - return `- ${messageHeadline} ([\`${abbreviatedOid}\`](${url}) ${printAuthorLink(author)})`; +function printCommit(commit) { + return `- ${commit.messageHeadline} (${printEntryLink(commit)} ${printAuthorLink(commit.author)})`; } // 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 // of pull request numbers), builds a Markdown list of URLs function printDependencyList(dependencies) { - return _.map(dependencies, (pullRequests, name) => - ` - \`${name}\` (${pullRequests.map(printPullRequestLink).join(", ")})` - ).join("\n"); + const list = []; + + Object.entries(dependencies).forEach(([name, entries]) => { + if (entries.length > 0) { + list.push(` - \`${name}\` (${entries.map(printEntryLink).join(", ")})`); + } + }); + + return list.join("\n"); } 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 // (with format `@username`) of everyone who contributed to this version. 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 // entry and the list of contributors, for both pre-releases and stable // releases. Templates are located at the top of this file. -async function generateChangelogEntry(targetVersion) { +async function generateChangelogEntry(changelog, targetVersion) { let items = {}; let template; let contributors = []; @@ -696,6 +735,8 @@ async function generateChangelogEntry(targetVersion) { const websiteRepo = new RepositoryFetcher(client, "thelounge.github.io"); const previousWebsiteVersion = await websiteRepo.fetchPreviousVersion(targetVersion); items.websiteDocumentation = await websiteRepo.fetchCommitsAndPullRequestsSince("v" + previousWebsiteVersion); + + dedupeEntries(changelog, items); } 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 // indicates where entries are listed. -async function addToChangelog(newEntry) { - const changelogPath = "./CHANGELOG.md"; +function addToChangelog(changelog, newEntry) { const changelogMarker = "\n\n"; - const changelog = await readFile(changelogPath, "utf8"); const markerPosition = changelog.indexOf(changelogMarker) + changelogMarker.length; const newChangelog = changelog.substring(0, markerPosition) + @@ -734,8 +773,10 @@ async function addToChangelog(newEntry) { // Step 1: Generate a changelog entry + const changelog = await readFile(changelogPath, "utf8"); + try { - ({changelogEntry, skipped, contributors} = await generateChangelogEntry(version)); + ({changelogEntry, skipped, contributors} = await generateChangelogEntry(changelog, version)); } catch (error) { if (error.response && error.response.status === 401) { 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 try { - await addToChangelog(`${changelogEntry.trim()}\n\n`); + await addToChangelog(changelog, `${changelogEntry.trim()}\n\n`); } catch (error) { log.error(error); process.exit(1);