- introduce ES2021.String for replaceAll

- expand Configuration with `custom_placeholders` configuration
  - custom placeholders allow to extract values of placeholders and then use these in templates
- refactor replacing PR template values
This commit is contained in:
Mike Penz
2022-07-29 14:29:50 +00:00
committed by GitHub
parent 2fe4b852d3
commit 15a108d43e
3 changed files with 58 additions and 22 deletions
+9 -1
View File
@@ -14,6 +14,7 @@ export interface Configuration {
transformers: Transformer[] transformers: Transformer[]
tag_resolver: TagResolver tag_resolver: TagResolver
base_branches: string[] base_branches: string[]
custom_placeholders?: Placeholder[]
} }
export interface Category { export interface Category {
@@ -57,6 +58,12 @@ export interface TagResolver {
transformer?: Transformer // transforms the tag name using the regex, run after the filter transformer?: Transformer // transforms the tag name using the regex, run after the filter
} }
export interface Placeholder {
name: string // the name of the new placeholder
source: string // the src placeholder which will be used to apply the transformer on
transformer: Transformer // the transformer to use to transform the original placeholder into the custom placheolder
}
export const DefaultConfiguration: Configuration = { export const DefaultConfiguration: Configuration = {
max_tags_to_fetch: 200, // the amount of tags to fetch from the github API max_tags_to_fetch: 200, // the amount of tags to fetch from the github API
max_pull_requests: 200, // the amount of pull requests to process max_pull_requests: 200, // the amount of pull requests to process
@@ -94,5 +101,6 @@ export const DefaultConfiguration: Configuration = {
filter: undefined, // filter out all tags not matching the regex filter: undefined, // filter out all tags not matching the regex
transformer: undefined // transforms the tag name using the regex, run after the filter transformer: undefined // transforms the tag name using the regex, run after the filter
}, },
base_branches: [] // target branches for the merged PR ignoring PRs with different target branch, by default it will get all PRs base_branches: [], // target branches for the merged PR ignoring PRs with different target branch, by default it will get all PRs
custom_placeholders: []
} }
+47 -20
View File
@@ -1,5 +1,5 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {Category, DefaultConfiguration, Extractor, Transformer} from './configuration' import {Category, DefaultConfiguration, Extractor, Placeholder, Transformer} from './configuration'
import {PullRequestInfo, sortPullRequests} from './pullRequests' import {PullRequestInfo, sortPullRequests} from './pullRequests'
import {ReleaseNotesOptions} from './releaseNotes' import {ReleaseNotesOptions} from './releaseNotes'
import {DiffInfo} from './commits' import {DiffInfo} from './commits'
@@ -51,13 +51,21 @@ export function buildChangelog(diffInfo: DiffInfo, prs: PullRequestInfo[], optio
} }
} }
const placeholderMap = new Map<string, Placeholder>()
for (const ph of config.custom_placeholders || []) {
placeholderMap.set(ph.source, ph)
}
const validatedTransformers = validateTransformers(config.transformers) const validatedTransformers = validateTransformers(config.transformers)
const transformedMap = new Map<PullRequestInfo, string>() const transformedMap = new Map<PullRequestInfo, string>()
// convert PRs to their text representation // convert PRs to their text representation
for (const pr of prs) { for (const pr of prs) {
transformedMap.set( transformedMap.set(
pr, pr,
transform(fillTemplate(pr, config.pr_template || DefaultConfiguration.pr_template), validatedTransformers) transform(
fillTemplate(pr, config.pr_template || DefaultConfiguration.pr_template, placeholderMap),
validatedTransformers
)
) )
} }
core.info(`️ Used ${validatedTransformers.length} transformers to adjust message`) core.info(`️ Used ${validatedTransformers.length} transformers to adjust message`)
@@ -269,28 +277,47 @@ function haveEveryElements(arr1: string[], arr2: Set<string>): Boolean {
return arr1.every(item => arr2.has(item)) return arr1.every(item => arr2.has(item))
} }
function fillTemplate(pr: PullRequestInfo, template: string): string { function fillTemplate(pr: PullRequestInfo, template: string, customPlaceholders: Map<string, Placeholder>): string {
const placeholderMap = new Map<string, string>()
placeholderMap.set('NUMBER', pr.number.toString())
placeholderMap.set('TITLE', pr.title)
placeholderMap.set('URL', pr.htmlURL)
placeholderMap.set('STATUS', pr.status)
placeholderMap.set('CREATED_AT', pr.createdAt.toISOString())
placeholderMap.set('MERGED_AT', pr.mergedAt?.toISOString() || '')
placeholderMap.set('MERGE_SHA', pr.mergeCommitSha)
placeholderMap.set('AUTHOR', pr.author)
placeholderMap.set('LABELS', [...pr.labels]?.filter(l => !l.startsWith('--rcba-'))?.join(', ') || '')
placeholderMap.set('MILESTONE', pr.milestone || '')
placeholderMap.set('BODY', pr.body)
placeholderMap.set('ASSIGNEES', pr.assignees?.join(', ') || '')
placeholderMap.set('REVIEWERS', pr.requestedReviewers?.join(', ') || '')
placeholderMap.set('APPROVERS', pr.approvedReviewers?.join(', ') || '')
placeholderMap.set('BRANCH', pr.branch || '')
placeholderMap.set('BASE_BRANCH', pr.baseBranch)
let transformed = template let transformed = template
transformed = transformed.replace(/\${{NUMBER}}/g, pr.number.toString()) for (const [key, value] of placeholderMap) {
transformed = transformed.replace(/\${{TITLE}}/g, pr.title) transformed = transformed.replaceAll(`\${{${key}}}`, value)
transformed = transformed.replace(/\${{URL}}/g, pr.htmlURL)
transformed = transformed.replace(/\${{STATUS}}/g, pr.status) // replace custom placeholders
transformed = transformed.replace(/\${{CREATED_AT}}/g, pr.createdAt.toISOString()) transformed = applyCustomPlaceholder(transformed, value, customPlaceholders.get(key))
transformed = transformed.replace(/\${{MERGED_AT}}/g, pr.mergedAt?.toISOString() || '') }
transformed = transformed.replace(/\${{MERGE_SHA}}/g, pr.mergeCommitSha)
transformed = transformed.replace(/\${{AUTHOR}}/g, pr.author)
transformed = transformed.replace(
/\${{LABELS}}/g,
[...pr.labels]?.filter(l => !l.startsWith('--rcba-'))?.join(', ') || ''
)
transformed = transformed.replace(/\${{MILESTONE}}/g, pr.milestone || '')
transformed = transformed.replace(/\${{BODY}}/g, pr.body)
transformed = transformed.replace(/\${{ASSIGNEES}}/g, pr.assignees?.join(', ') || '')
transformed = transformed.replace(/\${{REVIEWERS}}/g, pr.requestedReviewers?.join(', ') || '')
transformed = transformed.replace(/\${{APPROVERS}}/g, pr.approvedReviewers?.join(', ') || '')
return transformed return transformed
} }
function applyCustomPlaceholder(template: string, value: string, placeholder: Placeholder | undefined) {
if (placeholder) {
const transformer = validateTransformer(placeholder.transformer)
if (transformer?.pattern) {
const extractedValue = value.replace(transformer.pattern, transformer.target)
return template.replaceAll(`\${{${placeholder.name}}}`, extractedValue)
}
}
return template
}
function transform(filled: string, transformers: RegexTransformer[]): string { function transform(filled: string, transformers: RegexTransformer[]): string {
if (transformers.length === 0) { if (transformers.length === 0) {
return filled return filled
+2 -1
View File
@@ -6,7 +6,8 @@
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"strict": true, /* Enable all strict type-checking options. */ "strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
"lib": [ "ES2021.String" ] /* Enable custom `ES2021.String` extension in typescript for `replaceAll` */
}, },
"exclude": ["node_modules", "**/*.test.ts"] "exclude": ["node_modules", "**/*.test.ts"]
} }