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:
parent
0995f95296
commit
41c1b3275e
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user