- introduce new mode configuration (replaces commitMode) with the option for PR, COMMIT or HYBRID (whereas hybrid has commits and PRs alongside)

- introduce new default commit configuration automatically used if `COMMIT` mode is configured. this uses a label extractor to support conventional commits
This commit is contained in:
Mike Penz
2024-03-02 12:03:19 +00:00
committed by GitHub
parent 98e699a556
commit db235a6627
15 changed files with 392 additions and 228 deletions
+41
View File
@@ -108,3 +108,44 @@ export const DefaultConfiguration: Configuration = {
custom_placeholders: [],
trim_values: false // defines if values are being trimmed prior to inserting
}
export const DefaultCommitConfiguration: Configuration = {
max_tags_to_fetch: DefaultConfiguration.max_tags_to_fetch,
max_pull_requests: DefaultConfiguration.max_pull_requests,
max_back_track_time_days: DefaultConfiguration.max_back_track_time_days,
exclude_merge_branches: DefaultConfiguration.exclude_merge_branches,
sort: DefaultConfiguration.sort,
template: '#{{CHANGELOG}}', // the global template to host the changelog
pr_template: '- #{{TITLE}}', // the per PR template to pick for commit based mode
empty_template: DefaultConfiguration.empty_template,
categories: [
{
title: '## 🚀 Features',
labels: ['feature', 'feat']
},
{
title: '## 🐛 Fixes',
labels: ['fix', 'bug']
},
{
title: '## 🧪 Tests',
labels: ['test']
},
{
title: '## 📦 Other',
labels: []
}
], // the categories to support for the ordering
ignore_labels: DefaultConfiguration.ignore_labels,
label_extractor: [
{
pattern: '^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\\([\\w\\-\\.]+\\))?(!)?: ([\\w ])+([\\s\\S]*)',
target: '$1'
}
],
transformers: DefaultConfiguration.transformers,
tag_resolver: DefaultConfiguration.tag_resolver,
base_branches: DefaultConfiguration.base_branches,
custom_placeholders: DefaultConfiguration.custom_placeholders,
trim_values: DefaultConfiguration.trim_values
}
+7 -4
View File
@@ -1,6 +1,6 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import {mergeConfiguration, parseConfiguration, resolveConfiguration, retrieveRepositoryPath, writeOutput} from './utils'
import {mergeConfiguration, parseConfiguration, resolveConfiguration, resolveMode, retrieveRepositoryPath, writeOutput} from './utils'
import {ReleaseNotesBuilder} from './releaseNotesBuilder'
import {Configuration} from './configuration'
import {GithubRepository} from './repositories/GithubRepository'
@@ -51,8 +51,12 @@ async function run(): Promise<void> {
core.info(`️ No configuration provided. Using Defaults.`)
}
// mode of the action (PR, COMMIT, HYBRID)
const mode = resolveMode(core.getInput('mode'), core.getInput('commitMode') === 'true')
core.info(`️ Running in ${mode} mode.`)
// merge configs, use default values from DefaultConfig on missing definition
const configuration = mergeConfiguration(configJson, configFile)
const configuration = mergeConfiguration(configJson, configFile, mode)
// read in repository inputs
const baseUrl = core.getInput('baseUrl')
@@ -70,7 +74,6 @@ async function run(): Promise<void> {
const fetchReviewers = core.getInput('fetchReviewers') === 'true'
const fetchReleaseInformation = core.getInput('fetchReleaseInformation') === 'true'
const fetchReviews = core.getInput('fetchReviews') === 'true'
const commitMode = core.getInput('commitMode') === 'true'
const exportCache = core.getInput('exportCache') === 'true'
const exportOnly = core.getInput('exportOnly') === 'true'
const cache = core.getInput('cache')
@@ -91,7 +94,7 @@ async function run(): Promise<void> {
fetchReviewers,
fetchReleaseInformation,
fetchReviews,
commitMode,
mode,
exportCache,
exportOnly,
cache,
+35 -32
View File
@@ -91,42 +91,45 @@ export class Commits {
}
async generateCommitPRs(options: Options): Promise<[DiffInfo, PullRequestInfo[]]> {
const {owner, repo, configuration} = options
const diffInfo = await this.getCommitHistory(options)
const commits = diffInfo.commitInfo
if (commits.length === 0) {
return [diffInfo, []]
}
const prCommits = filterCommits(commits, configuration.exclude_merge_branches)
core.info(`️ Retrieved ${prCommits.length} commits for ${owner}/${repo}`)
const prs = prCommits.map(function (commit): PullRequestInfo {
return {
number: 0,
title: commit.summary,
htmlURL: '',
baseBranch: '',
createdAt: commit.commitDate,
mergedAt: commit.commitDate,
mergeCommitSha: commit.sha,
author: commit.author || '',
repoName: '',
labels: [],
milestone: '',
body: commit.message || '',
assignees: [],
requestedReviewers: [],
approvedReviewers: [],
status: 'merged'
}
})
return [diffInfo, prs]
return convertCommitsToPrs(options, diffInfo)
}
}
export function convertCommitsToPrs(options: Options, diffInfo: DiffInfo): [DiffInfo, PullRequestInfo[]] {
const {owner, repo, configuration} = options
const commits = diffInfo.commitInfo
if (commits.length === 0) {
return [diffInfo, []]
}
const prCommits = filterCommits(commits, configuration.exclude_merge_branches)
core.info(`️ Retrieved ${prCommits.length} commits for ${owner}/${repo}`)
const prs = prCommits.map(function (commit): PullRequestInfo {
return {
number: 0,
title: commit.summary,
htmlURL: '',
baseBranch: '',
createdAt: commit.commitDate,
mergedAt: commit.commitDate,
mergeCommitSha: commit.sha,
author: commit.author || '',
repoName: '',
labels: [],
milestone: '',
body: commit.message || '',
assignees: [],
requestedReviewers: [],
approvedReviewers: [],
status: 'merged'
}
})
return [diffInfo, prs]
}
/**
* Filters out all commits which match the exclude pattern
*/
+22 -14
View File
@@ -3,7 +3,7 @@ import {PullConfiguration} from './types'
import {TagInfo, Tags} from './tags'
import {failOrError} from './utils'
import {PullRequestInfo, PullRequests} from './pullRequests'
import {Commits, DiffInfo} from './commits'
import {Commits, DefaultDiffInfo, DiffInfo, convertCommitsToPrs} from './commits'
import {BaseRepository} from '../repositories/BaseRepository'
export interface Options {
@@ -17,7 +17,7 @@ export interface Options {
fetchReviewers: boolean // defines if the action should fetch the reviewers for PRs - approved reviewers are not included in the default PR listing
fetchReleaseInformation: boolean // defines if the action should fetch the release information for the from and to tag - e.g. the creation date for the associated release
fetchReviews: boolean // defines if the action should fetch the reviews for the PR.
commitMode: boolean // defines if we use the alternative commit based mode. note: this is only partially supported
mode: 'PR' | 'COMMIT' | 'HYBRID' // defines the mode used. note: the commit or hybrid modes are not fully supported
configuration: PullConfiguration // the configuration as defined in `configuration.ts`
}
@@ -44,7 +44,7 @@ export class PullRequestCollector {
private fetchReviewers = false,
private fetchReleaseInformation = false,
private fetchReviews = false,
private commitMode = false,
private mode: 'PR' | 'COMMIT' | 'HYBRID' = 'PR',
private configuration: PullConfiguration
) {}
@@ -102,29 +102,37 @@ export class PullRequestCollector {
fetchReviewers: this.fetchReviewers,
fetchReleaseInformation: this.fetchReleaseInformation,
fetchReviews: this.fetchReviews,
commitMode: this.commitMode,
mode: this.mode,
configuration: this.configuration
})
}
}
export async function pullData(repositoryUtils: BaseRepository, options: Options): Promise<Data | null> {
let mergedPullRequests: PullRequestInfo[]
let diffInfo: DiffInfo
let mergedPullRequests: PullRequestInfo[] = []
let diffInfo: DiffInfo = Object.assign({}, DefaultDiffInfo)
const commitsApi = new Commits(repositoryUtils)
if (!options.commitMode) {
core.startGroup(`🚀 Load pull requests`)
core.startGroup(`🚀 Load data`)
if (options.mode === 'COMMIT') {
core.info(`🚀 Load commit history (⚠️ Executing experimental commit mode)`)
const [info, prs] = await commitsApi.generateCommitPRs(options)
mergedPullRequests = mergedPullRequests.concat(prs)
diffInfo = info
} else {
// PR mode, HYBRID mode
core.info(`🚀 Load pull requests`)
const pullRequestsApi = new PullRequests(repositoryUtils, commitsApi)
const [info, prs] = await pullRequestsApi.getMergedPullRequests(options)
mergedPullRequests = prs
diffInfo = info
} else {
core.startGroup(`🚀 Load commit history`)
core.info(`⚠️ Executing experimental commit mode`)
const [info, prs] = await commitsApi.generateCommitPRs(options)
mergedPullRequests = prs
diffInfo = info
if (options.mode === 'HYBRID') {
core.info(`🚀 Converting commits to pull requests`)
const [, fakeCommitPrs] = convertCommitsToPrs(options, info)
mergedPullRequests = mergedPullRequests.concat(fakeCommitPrs)
}
}
core.endGroup()
+5 -5
View File
@@ -19,7 +19,7 @@ export interface ReleaseNotesOptions {
fetchReviewers: boolean // defines if the action should fetch the reviewers for PRs - approved reviewers are not included in the default PR listing
fetchReleaseInformation: boolean // defines if the action should fetch the release information for the from and to tag - e.g. the creation date for the associated release
fetchReviews: boolean // defines if the action should fetch the reviews for the PR.
commitMode: boolean // defines if we use the alternative commit based mode. note: this is only partially supported
mode: 'PR' | 'COMMIT' | 'HYBRID' // defines the mode used. note: the commit or hybrid modes are not fully supported
configuration: Configuration // the configuration as defined in `configuration.ts`
repositoryUtils: BaseRepository // the repository implementation used to generate the changelog
}
@@ -46,7 +46,7 @@ export class ReleaseNotesBuilder {
private fetchReviewers = false,
private fetchReleaseInformation = false,
private fetchReviews = false,
private commitMode = false,
private mode: 'PR' | 'COMMIT' | 'HYBRID' = 'PR',
private exportCache = false,
private exportOnly = false,
private cache: string | null = null,
@@ -92,7 +92,7 @@ export class ReleaseNotesBuilder {
this.fetchReviewers,
this.fetchReleaseInformation,
this.fetchReviews,
this.commitMode,
this.mode,
this.configuration
).build()
@@ -110,7 +110,7 @@ export class ReleaseNotesBuilder {
fetchReviewers: this.fetchReviewers,
fetchReleaseInformation: this.fetchReleaseInformation,
fetchReviews: this.fetchReviews,
commitMode: this.commitMode,
mode: this.mode,
configuration: this.configuration,
repositoryUtils: this.repositoryUtils
}
@@ -164,7 +164,7 @@ export class ReleaseNotesBuilder {
fetchReviewers: this.fetchReviewers || orgOptions.fetchReviewers,
fetchReleaseInformation: this.fetchReleaseInformation || orgOptions.fetchReleaseInformation,
fetchReviews: this.fetchReviews || orgOptions.fetchReviews,
commitMode: this.commitMode || orgOptions.commitMode,
mode: this.mode || orgOptions.mode,
configuration: this.configuration || orgOptions.configuration,
repositoryUtils: this.repositoryUtils || orgOptions.repositoryUtils
}
+44 -19
View File
@@ -1,7 +1,7 @@
import * as core from '@actions/core'
import * as fs from 'fs'
import * as path from 'path'
import {Configuration, DefaultConfiguration} from './configuration'
import {Configuration, DefaultCommitConfiguration, DefaultConfiguration} from './configuration'
import moment from 'moment'
import {DiffInfo} from './pr-collector/commits'
import {PullRequestInfo} from './pr-collector/pullRequests'
@@ -117,6 +117,24 @@ export function checkExportedData(exportCache: boolean, cacheInput: string | nul
}
}
export function resolveMode(mode: string | undefined, commitMode: boolean): 'PR' | 'COMMIT' | 'HYBRID' {
if (commitMode === false || mode === undefined) {
if (commitMode === true) {
return 'COMMIT'
} else {
return 'PR'
}
} else {
const upperCaseMode = mode.toUpperCase()
if (upperCaseMode === 'COMMIT') {
return 'COMMIT'
} else if (upperCaseMode === 'HYBRID') {
return 'HYBRID'
}
}
return 'PR'
}
/**
* Retrieves the configuration given the file path, if not found it will fallback to the `DefaultConfiguration`
*/
@@ -173,25 +191,32 @@ export function parseConfiguration(config: string): Configuration | undefined {
/**
* Merges the configurations, will fallback to the DefaultConfiguration value
*/
export function mergeConfiguration(jc?: Configuration, fc?: Configuration): Configuration {
export function mergeConfiguration(jc?: Configuration, fc?: Configuration, mode?: 'PR' | 'COMMIT' | 'HYBRID'): Configuration {
let def: Configuration
if (mode === 'COMMIT') {
def = DefaultCommitConfiguration
} else {
def = DefaultConfiguration
}
return {
max_tags_to_fetch: jc?.max_tags_to_fetch || fc?.max_tags_to_fetch || DefaultConfiguration.max_tags_to_fetch,
max_pull_requests: jc?.max_pull_requests || fc?.max_pull_requests || DefaultConfiguration.max_pull_requests,
max_back_track_time_days: jc?.max_back_track_time_days || fc?.max_back_track_time_days || DefaultConfiguration.max_back_track_time_days,
exclude_merge_branches: jc?.exclude_merge_branches || fc?.exclude_merge_branches || DefaultConfiguration.exclude_merge_branches,
sort: jc?.sort || fc?.sort || DefaultConfiguration.sort,
template: jc?.template || fc?.template || DefaultConfiguration.template,
pr_template: jc?.pr_template || fc?.pr_template || DefaultConfiguration.pr_template,
empty_template: jc?.empty_template || fc?.empty_template || DefaultConfiguration.empty_template,
categories: jc?.categories || fc?.categories || DefaultConfiguration.categories,
ignore_labels: jc?.ignore_labels || fc?.ignore_labels || DefaultConfiguration.ignore_labels,
label_extractor: jc?.label_extractor || fc?.label_extractor || DefaultConfiguration.label_extractor,
duplicate_filter: jc?.duplicate_filter || fc?.duplicate_filter || DefaultConfiguration.duplicate_filter,
transformers: jc?.transformers || fc?.transformers || DefaultConfiguration.transformers,
tag_resolver: jc?.tag_resolver || fc?.tag_resolver || DefaultConfiguration.tag_resolver,
base_branches: jc?.base_branches || fc?.base_branches || DefaultConfiguration.base_branches,
custom_placeholders: jc?.custom_placeholders || fc?.custom_placeholders || DefaultConfiguration.custom_placeholders,
trim_values: jc?.trim_values || fc?.trim_values || DefaultConfiguration.trim_values
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,
max_back_track_time_days: jc?.max_back_track_time_days || fc?.max_back_track_time_days || def.max_back_track_time_days,
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,
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,
label_extractor: jc?.label_extractor || fc?.label_extractor || def.label_extractor,
duplicate_filter: jc?.duplicate_filter || fc?.duplicate_filter || def.duplicate_filter,
transformers: jc?.transformers || fc?.transformers || def.transformers,
tag_resolver: jc?.tag_resolver || fc?.tag_resolver || def.tag_resolver,
base_branches: jc?.base_branches || fc?.base_branches || def.base_branches,
custom_placeholders: jc?.custom_placeholders || fc?.custom_placeholders || def.custom_placeholders,
trim_values: jc?.trim_values || fc?.trim_values || def.trim_values
}
}