diff --git a/src/configuration.ts b/src/configuration.ts index 6295da5..b9c3f60 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -14,6 +14,7 @@ export interface Configuration { transformers: Transformer[] tag_resolver: TagResolver base_branches: string[] + custom_placeholders?: Placeholder[] } export interface Category { @@ -57,6 +58,12 @@ export interface TagResolver { 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 = { 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 @@ -94,5 +101,6 @@ export const DefaultConfiguration: Configuration = { filter: undefined, // filter out all tags not matching the regex 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: [] } diff --git a/src/transform.ts b/src/transform.ts index 9516254..f3272ef 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -1,5 +1,5 @@ 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 {ReleaseNotesOptions} from './releaseNotes' import {DiffInfo} from './commits' @@ -51,13 +51,21 @@ export function buildChangelog(diffInfo: DiffInfo, prs: PullRequestInfo[], optio } } + const placeholderMap = new Map() + for (const ph of config.custom_placeholders || []) { + placeholderMap.set(ph.source, ph) + } + const validatedTransformers = validateTransformers(config.transformers) const transformedMap = new Map() // convert PRs to their text representation for (const pr of prs) { transformedMap.set( 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`) @@ -269,28 +277,47 @@ function haveEveryElements(arr1: string[], arr2: Set): Boolean { return arr1.every(item => arr2.has(item)) } -function fillTemplate(pr: PullRequestInfo, template: string): string { +function fillTemplate(pr: PullRequestInfo, template: string, customPlaceholders: Map): string { + const placeholderMap = new Map() + 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 - transformed = transformed.replace(/\${{NUMBER}}/g, pr.number.toString()) - transformed = transformed.replace(/\${{TITLE}}/g, pr.title) - transformed = transformed.replace(/\${{URL}}/g, pr.htmlURL) - transformed = transformed.replace(/\${{STATUS}}/g, pr.status) - transformed = transformed.replace(/\${{CREATED_AT}}/g, pr.createdAt.toISOString()) - 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(', ') || '') + for (const [key, value] of placeholderMap) { + transformed = transformed.replaceAll(`\${{${key}}}`, value) + + // replace custom placeholders + transformed = applyCustomPlaceholder(transformed, value, customPlaceholders.get(key)) + } + 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 { if (transformers.length === 0) { return filled diff --git a/tsconfig.json b/tsconfig.json index f6e7cb5..95a87b7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ "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. */ "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"] }