Implement commit_template

This commit is contained in:
Bob Loblaw
2025-01-31 20:10:11 +01:00
committed by Mike Penz
parent 623490aa2a
commit b8fa364bab
7 changed files with 794 additions and 14 deletions
+3 -1
View File
@@ -3,6 +3,7 @@ import {Extractor, PullConfiguration, Regex, Rule} from './pr-collector/types.js
export interface Configuration extends PullConfiguration {
template: string
pr_template: string
commit_template: string // (COMMIT and HYBRID mode only for PRs converted to commits)
empty_template: string
categories: Category[]
ignore_labels: string[]
@@ -25,6 +26,7 @@ export interface Category {
empty_content?: string // if the category has no matching PRs, this content will be used. If not set, the category will be skipped in the changelog.
categories?: Category[] // allows for nested categories, items matched for a child category won't show up in the parent
consume?: boolean // defines if the matched PR will be consumed by this category. Consumed PRs won't show up in any category *after*
mode?: 'HYBRID' | 'COMMIT' | 'PR' // defines if this category applies to PRs, commits or both
entries?: string[] // array of single changelog entries, used to construct the changelog. (this is filled during the build)
}
@@ -70,6 +72,7 @@ export const DefaultConfiguration: Configuration = {
},
template: '#{{CHANGELOG}}', // the global template to host the changelog
pr_template: '- #{{TITLE}}\n - PR: ##{{NUMBER}}', // the per PR template to pick
commit_template: '- #{{TITLE}}', // the per PR template to pick for commit based mode
empty_template: '- no changes', // the template to use if no pull requests are found
categories: [
{
@@ -106,7 +109,6 @@ export const DefaultConfiguration: Configuration = {
export const DefaultCommitConfiguration: Configuration = {
...DefaultConfiguration,
pr_template: '- #{{TITLE}}', // the per PR template to pick
categories: [
{
title: '## 🚀 Features',
+61 -6
View File
@@ -127,22 +127,47 @@ export function buildChangelog(diffInfo: DiffInfo, origPrs: PullRequestInfo[], o
core.info(`️ Using ${validatedTransformers.length} transformers to rewrite content`)
const includePrs = options.mode === 'PR' || options.mode === 'HYBRID'
const includeCommits = options.mode === 'COMMIT' || options.mode === 'HYBRID'
// convert PRs to their text representation
const realPrs = includePrs ? prs.filter(x => x.number !== 0) : []
const commitPrs = includeCommits ? prs.filter(x => x.number === 0) : []
if (validatedTransformers.length > 0) {
for (const pr of prs) {
const prAsObject = pr as unknown as Record<string, unknown>
transformObject(prAsObject, validatedTransformers)
}
core.info(`✒️ Transformed ${prs.length} pull requests`)
if (includePrs) {
core.info(`✒️ Transformed ${realPrs.length} pull requests`)
}
if (includeCommits) {
core.info(`✒️ Transformed ${commitPrs.length} commits`)
}
}
const prInfoMap = buildInfoMapAndFillPlaceholderContext(
prs,
realPrs,
config.pr_template,
groupedPlaceholders,
customPlaceholdersTemplateContext,
config
)
const commitInfoMap = buildInfoMapAndFillPlaceholderContext(
commitPrs,
config.commit_template,
groupedPlaceholders,
customPlaceholdersTemplateContext,
config
)
// If the mode is not HYBRID, the map will contain only one or the other map
const combinedInfoMap = mergeMaps(prInfoMap, commitInfoMap)
// bring PRs into the order of categories
const categories = config.categories
const flatCategories = flatten(config.categories)
@@ -154,7 +179,7 @@ export function buildChangelog(diffInfo: DiffInfo, origPrs: PullRequestInfo[], o
}
}
const prStrings = buildPrStringsAndFillCategoryEntries(prInfoMap, config.ignore_labels, categories, flatCategories)
const prStrings = buildPrStringsAndFillCategoryEntries(combinedInfoMap, config.ignore_labels, categories, flatCategories)
core.info(`️ Ordered all pull requests into ${categories.length} categories`)
// serialize and provide the categorized content as json
@@ -272,7 +297,8 @@ function buildPrStringsAndFillCategoryEntries(
}
let matchedOnce = false // in case we matched once at least, the PR can't be uncategorized
for (const category of categories) {
const filteredCategories = filterCategoriesByPrType(categories, pr)
for (const category of filteredCategories) {
const [matched, consumed] = recursiveCategorizePr(category, pr, body)
if (consumed) {
continue prLoop
@@ -282,7 +308,8 @@ function buildPrStringsAndFillCategoryEntries(
if (!matchedOnce) {
// we allow to have pull requests included in an "uncategorized" category
for (const category of flatCategories) {
const filteredFlatCategories = filterCategoriesByPrType(flatCategories, pr)
for (const category of filteredFlatCategories) {
category.entries = category.entries || []
if ((category.labels === undefined || category.labels.length === 0) && category.rules === undefined) {
// check if any exclude label matches for the "uncategorized" category
@@ -546,7 +573,15 @@ export function renderEmptyChangelogTemplate(template: string, options: ReleaseN
const releaseNotesTemplateContext = buildCoreReleaseNotesTemplateContext(options)
return renderTemplateAndFillPlaceholderContext(template, releaseNotesTemplateContext, placeholders, undefined, options.configuration)
const renderedEmptyChangelogTemplate = renderTemplateAndFillPlaceholderContext(
template,
releaseNotesTemplateContext,
placeholders,
undefined,
options.configuration
)
return renderedEmptyChangelogTemplate
}
function buildCoreReleaseNotesTemplateContext(options: ReleaseNotesOptions): TemplateContext {
@@ -867,3 +902,23 @@ function hasChildWithEntries(category: Category): boolean {
}
return hasEntries
}
/**
* Filters the provided categories based on the type of pull request information.
*
* @param {Category[]} categories - The list of categories to filter.
* @param {PullRequestInfo} prInfo - The pull request information used to determine the type of PR.
* @returns {Category[]} The filtered list of categories:
* - If 'prInfo' represents a real pull request (has a number other than 0), it excludes categories with mode 'COMMIT'.
* - If 'prInfo' represents a commit (has number 0), it excludes categories with mode 'PR'.
* - Defaults to keeping categories with mode 'HYBRID' in either case.
*/
function filterCategoriesByPrType(categories: Category[], prInfo: PullRequestInfo): Category[] {
const isRealPr = prInfo.number !== 0
if (isRealPr) {
return categories.filter(category => (category.mode || 'HYBRID') !== 'COMMIT')
} else {
return categories.filter(category => (category.mode || 'HYBRID') !== 'PR')
}
}
+25 -1
View File
@@ -107,6 +107,16 @@ export function checkExportedData(exportCache: boolean, cacheInput: string | nul
options.toTag.date = moment(options.toTag.date)
}
// Handle backwards compatibility for addition of `commit_template` in COMMIT and HYBRID mode
// If there is no provided commit_template, fallback to the provided pr_template,
// and if that is not provided either, fallback to the default commit_template
const {mode, configuration} = options
const prTemplate = configuration?.pr_template
const commitTemplate = configuration?.commit_template
if ((mode === 'COMMIT' || mode === 'HYBRID') && !commitTemplate) {
options.configuration.commit_template = prTemplate || DefaultConfiguration.commit_template
}
return {
diffInfo,
mergedPullRequests,
@@ -198,6 +208,19 @@ export function mergeConfiguration(jc?: Configuration, fc?: Configuration, mode?
def = DefaultConfiguration
}
// Handle backwards compatibility for addition of `commit_template` in COMMIT and HYBRID mode
// If there is no provided commit_template, fallback to the provided pr_template,
// and if that is not provided either, fallback to the default commit_template
const prTemplate = jc?.pr_template || fc?.pr_template
let commitTemplate = jc?.commit_template || fc?.commit_template
if ((mode === 'COMMIT' || mode === 'HYBRID') && !commitTemplate) {
if (prTemplate) {
commitTemplate = prTemplate
} else {
commitTemplate = def.commit_template
}
}
return {
max_tags_to_fetch: jc?.max_tags_to_fetch || fc?.max_tags_to_fetch || def.max_tags_to_fetch,
max_pull_requests: jc?.max_pull_requests || fc?.max_pull_requests || def.max_pull_requests,
@@ -205,7 +228,8 @@ export function mergeConfiguration(jc?: Configuration, fc?: Configuration, mode?
exclude_merge_branches: jc?.exclude_merge_branches || fc?.exclude_merge_branches || def.exclude_merge_branches,
sort: jc?.sort || fc?.sort || def.sort,
template: jc?.template || fc?.template || def.template,
pr_template: jc?.pr_template || fc?.pr_template || def.pr_template,
pr_template: prTemplate || def.pr_template,
commit_template: commitTemplate || def.commit_template,
empty_template: jc?.empty_template || fc?.empty_template || def.empty_template,
categories: jc?.categories || fc?.categories || def.categories,
ignore_labels: jc?.ignore_labels || fc?.ignore_labels || def.ignore_labels,