Merge pull request #457 from mikepenz/feature/filter_duplicates

Feature/filter duplicates
This commit is contained in:
Mike Penz
2021-08-27 13:43:34 +02:00
committed by GitHub
9 changed files with 485 additions and 293 deletions
+1 -1
View File
@@ -326,7 +326,7 @@ $ npm run build && npm run package
$ npm test $ npm test
# Verify lint is happy # Verify lint is happy
$ npm run lint -- --fixnpm run lint -- --fix $ npm run lint -- --fix
``` ```
It's suggested to export the token to your path before running the tests so that API calls can be done to GitHub. It's suggested to export the token to your path before running the tests so that API calls can be done to GitHub.
+2 -2
View File
@@ -54,9 +54,9 @@ test('should write result to file', () => {
// should succeed // should succeed
expect(result).toBeDefined() expect(result).toBeDefined()
const readOutput = fs.readFileSync("test.md") const readOutput = fs.readFileSync('test.md')
fs.unlinkSync("test.md") fs.unlinkSync('test.md')
expect(readOutput.toString()).not.toBe('') expect(readOutput.toString()).not.toBe('')
}) })
+8 -8
View File
@@ -18,7 +18,7 @@ it('Should have empty changelog (tags)', async () => {
toTag: 'v0.0.2', toTag: 'v0.0.2',
failOnError: false, failOnError: false,
commitMode: false, commitMode: false,
configuration: configuration configuration
}) })
const changeLog = await releaseNotes.pull() const changeLog = await releaseNotes.pull()
@@ -35,7 +35,7 @@ it('Should match generated changelog (tags)', async () => {
toTag: 'v0.0.3', toTag: 'v0.0.3',
failOnError: false, failOnError: false,
commitMode: false, commitMode: false,
configuration: configuration configuration
}) })
const changeLog = await releaseNotes.pull() const changeLog = await releaseNotes.pull()
@@ -60,7 +60,7 @@ it('Should match generated changelog (refs)', async () => {
toTag: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa', toTag: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa',
failOnError: false, failOnError: false,
commitMode: false, commitMode: false,
configuration: configuration configuration
}) })
const changeLog = await releaseNotes.pull() const changeLog = await releaseNotes.pull()
@@ -93,7 +93,7 @@ it('Should match generated changelog and replace all occurrences (refs)', async
toTag: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa', toTag: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa',
failOnError: false, failOnError: false,
commitMode: false, commitMode: false,
configuration: configuration configuration
}) })
const changeLog = await releaseNotes.pull() const changeLog = await releaseNotes.pull()
@@ -128,7 +128,7 @@ it('Should match ordered ASC', async () => {
toTag: 'v0.5.0', toTag: 'v0.5.0',
failOnError: false, failOnError: false,
commitMode: false, commitMode: false,
configuration: configuration configuration
}) })
const changeLog = await releaseNotes.pull() const changeLog = await releaseNotes.pull()
@@ -150,7 +150,7 @@ it('Should match ordered DESC', async () => {
toTag: 'v0.5.0', toTag: 'v0.5.0',
failOnError: false, failOnError: false,
commitMode: false, commitMode: false,
configuration: configuration configuration
}) })
const changeLog = await releaseNotes.pull() const changeLog = await releaseNotes.pull()
@@ -172,7 +172,7 @@ it('Should ignore PRs not merged into develop branch', async () => {
toTag: 'v1.4.0', toTag: 'v1.4.0',
failOnError: false, failOnError: false,
commitMode: false, commitMode: false,
configuration: configuration configuration
}) })
const changeLog = await releaseNotes.pull() const changeLog = await releaseNotes.pull()
@@ -192,7 +192,7 @@ it('Should ignore PRs not merged into main branch', async () => {
toTag: 'v1.4.0', toTag: 'v1.4.0',
failOnError: false, failOnError: false,
commitMode: false, commitMode: false,
configuration: configuration configuration
}) })
const changeLog = await releaseNotes.pull() const changeLog = await releaseNotes.pull()
+3 -3
View File
@@ -45,7 +45,9 @@ it('Should match generated changelog (unspecified tags)', async () => {
const changeLog = await releaseNotesBuilder.build() const changeLog = await releaseNotesBuilder.build()
console.log(changeLog) console.log(changeLog)
expect(changeLog).toStrictEqual(`## 🐛 Fixes\n\n- Stacktrace Data can be an array\n - PR: #39\n\n`) expect(changeLog).toStrictEqual(
`## 🐛 Fixes\n\n- Stacktrace Data can be an array\n - PR: #39\n\n`
)
}) })
it('Should use empty placeholder', async () => { it('Should use empty placeholder', async () => {
@@ -168,7 +170,6 @@ it('Uncategorized category', async () => {
) )
}) })
it('Verify commit based changelog', async () => { it('Verify commit based changelog', async () => {
const configuration = resolveConfiguration( const configuration = resolveConfiguration(
'', '',
@@ -194,7 +195,6 @@ it('Verify commit based changelog', async () => {
) )
}) })
it('Verify commit based changelog, with emoji categorisation', async () => { it('Verify commit based changelog, with emoji categorisation', async () => {
const configuration = resolveConfiguration( const configuration = resolveConfiguration(
'', '',
+274 -174
View File
@@ -1,240 +1,340 @@
import {buildChangelog} from '../src/transform' import {buildChangelog} from '../src/transform'
import { PullRequestInfo } from '../src/pullRequests' import {PullRequestInfo} from '../src/pullRequests'
import moment from 'moment' import moment from 'moment'
import { DefaultConfiguration } from '../src/configuration'; import { DefaultConfiguration, Configuration } from '../src/configuration';
jest.setTimeout(180000) jest.setTimeout(180000)
let configuration = DefaultConfiguration const configuration = Object.assign({}, DefaultConfiguration)
configuration.categories = [ configuration.categories = [
{ {
"title": "## 🚀 Features", title: '## 🚀 Features',
"labels": ["[Feature]"] labels: ['[Feature]']
}, },
{ {
"title": "## 🐛 Fixes", title: '## 🐛 Fixes',
"labels": ["[Bug]", "[Issue]"] labels: ['[Bug]', '[Issue]']
}, },
{ {
"title": "## 🧪 Tests", title: '## 🧪 Tests',
"labels": ["[Test]"] labels: ['[Test]']
} }
] ]
let mergedPullRequests: PullRequestInfo[] = [] // list of PRs without labels assigned (extract from title)
mergedPullRequests.push({ const mergedPullRequests: PullRequestInfo[] = []
number: 1, mergedPullRequests.push(
title: "[Feature][AB-1234] - this is a PR 1 title message", {
htmlURL: "", number: 1,
baseBranch: "", title: '[Feature][AB-1234] - this is a PR 1 title message',
mergedAt: moment(), htmlURL: '',
mergeCommitSha: "sha1", baseBranch: '',
author: "Mike", mergedAt: moment(),
repoName: "test-repo", mergeCommitSha: 'sha1',
labels: new Set<string>(), author: 'Mike',
milestone: "", repoName: 'test-repo',
body: "no magic body for this matter", labels: new Set<string>(),
assignees: [], milestone: '',
requestedReviewers: [] body: 'no magic body for this matter',
}, { assignees: [],
number: 2, requestedReviewers: []
title: "[Issue][AB-4321] - this is a PR 2 title message", },
htmlURL: "", {
baseBranch: "", number: 2,
mergedAt: moment(), title: '[Issue][AB-4321] - this is a PR 2 title message',
mergeCommitSha: "sha1", htmlURL: '',
author: "Mike", baseBranch: '',
repoName: "test-repo", mergedAt: moment(),
labels: new Set<string>(), mergeCommitSha: 'sha1',
milestone: "", author: 'Mike',
body: "no magic body for this matter", repoName: 'test-repo',
assignees: [], labels: new Set<string>(),
requestedReviewers: [] milestone: '',
}, { body: 'no magic body for this matter',
number: 3, assignees: [],
title: "[Issue][Feature][AB-1234321] - this is a PR 3 title message", requestedReviewers: []
htmlURL: "", },
baseBranch: "", {
mergedAt: moment(), number: 3,
mergeCommitSha: "sha1", title: '[Issue][Feature][AB-1234321] - this is a PR 3 title message',
author: "Mike", htmlURL: '',
repoName: "test-repo", baseBranch: '',
labels: new Set<string>(), mergedAt: moment(),
milestone: "", mergeCommitSha: 'sha1',
body: "no magic body for this matter", author: 'Mike',
assignees: [], repoName: 'test-repo',
requestedReviewers: [] labels: new Set<string>(),
}, { milestone: '',
number: 4, body: 'no magic body for this matter',
title: "[AB-404] - not found label", assignees: [],
htmlURL: "", requestedReviewers: []
baseBranch: "", },
mergedAt: moment(), {
mergeCommitSha: "sha1", number: 4,
author: "Mike", title: '[AB-404] - not found label',
repoName: "test-repo", htmlURL: '',
labels: new Set<string>(), baseBranch: '',
milestone: "", mergedAt: moment(),
body: "no magic body for this matter", mergeCommitSha: 'sha1',
assignees: [], author: 'Mike',
requestedReviewers: [] repoName: 'test-repo',
}) labels: new Set<string>(),
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
}
)
it('Extract label from title, combined regex', async () => { it('Extract label from title, combined regex', async () => {
configuration.label_extractor = [ configuration.label_extractor = [
{ {
"pattern": ".*(\\[Feature\\]|\\[Issue\\]).*", pattern: '.*(\\[Feature\\]|\\[Issue\\]).*',
"target": "$1", target: '$1',
"on_property": "title" on_property: 'title'
} }
] ]
const resultChangelog = buildChangelog( const resultChangelog = buildChangelog(mergedPullRequests, {
mergedPullRequests, owner: 'mikepenz',
{ repo: 'test-repo',
owner: "mikepenz", fromTag: '1.0.0',
repo: "test-repo", toTag: '2.0.0',
fromTag: "1.0.0", failOnError: false,
toTag: "2.0.0", commitMode: false,
failOnError: false, configuration
commitMode: false, })
configuration: configuration
}
)
expect(resultChangelog).toStrictEqual(`## 🚀 Features\n\n- [Feature][AB-1234] - this is a PR 1 title message\n - PR: #1\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n## 🐛 Fixes\n\n- [Issue][AB-4321] - this is a PR 2 title message\n - PR: #2\n\n`) expect(resultChangelog).toStrictEqual(
`## 🚀 Features\n\n- [Feature][AB-1234] - this is a PR 1 title message\n - PR: #1\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n## 🐛 Fixes\n\n- [Issue][AB-4321] - this is a PR 2 title message\n - PR: #2\n\n`
)
}) })
it('Extract label from title, split regex', async () => { it('Extract label from title, split regex', async () => {
configuration.label_extractor = [ configuration.label_extractor = [
{ {
"pattern": ".*(\\[Feature\\]).*", pattern: '.*(\\[Feature\\]).*',
"target": "$1", target: '$1',
"on_property": "title" on_property: 'title'
}, },
{ {
"pattern": ".*(\\[Issue\\]).*", pattern: '.*(\\[Issue\\]).*',
"target": "$1", target: '$1',
"on_property": "title" on_property: 'title'
} }
] ]
const resultChangelog = buildChangelog( const resultChangelog = buildChangelog(mergedPullRequests, {
mergedPullRequests, owner: 'mikepenz',
{ repo: 'test-repo',
owner: "mikepenz", fromTag: '1.0.0',
repo: "test-repo", toTag: '2.0.0',
fromTag: "1.0.0", failOnError: false,
toTag: "2.0.0", commitMode: false,
failOnError: false, configuration
commitMode: false, })
configuration: configuration
}
)
expect(resultChangelog).toStrictEqual(`## 🚀 Features\n\n- [Feature][AB-1234] - this is a PR 1 title message\n - PR: #1\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n## 🐛 Fixes\n\n- [Issue][AB-4321] - this is a PR 2 title message\n - PR: #2\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n`) expect(resultChangelog).toStrictEqual(
`## 🚀 Features\n\n- [Feature][AB-1234] - this is a PR 1 title message\n - PR: #1\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n## 🐛 Fixes\n\n- [Issue][AB-4321] - this is a PR 2 title message\n - PR: #2\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n`
)
}) })
it('Extract label from title, match', async () => { it('Extract label from title, match', async () => {
configuration.label_extractor = [ configuration.label_extractor = [
{ {
"pattern": "\\[Feature\\]", pattern: '\\[Feature\\]',
"on_property": "title", on_property: 'title',
"method": "match" method: 'match'
}, },
{ {
"pattern": "\\[Issue\\]", pattern: '\\[Issue\\]',
"on_property": "title", on_property: 'title',
"method": "match" method: 'match'
} }
] ]
const resultChangelog = buildChangelog( const resultChangelog = buildChangelog(mergedPullRequests, {
mergedPullRequests, owner: 'mikepenz',
{ repo: 'test-repo',
owner: "mikepenz", fromTag: '1.0.0',
repo: "test-repo", toTag: '2.0.0',
fromTag: "1.0.0", failOnError: false,
toTag: "2.0.0", commitMode: false,
failOnError: false, configuration
commitMode: false, })
configuration: configuration
}
)
expect(resultChangelog).toStrictEqual(`## 🚀 Features\n\n- [Feature][AB-1234] - this is a PR 1 title message\n - PR: #1\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n## 🐛 Fixes\n\n- [Issue][AB-4321] - this is a PR 2 title message\n - PR: #2\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n`) expect(resultChangelog).toStrictEqual(
`## 🚀 Features\n\n- [Feature][AB-1234] - this is a PR 1 title message\n - PR: #1\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n## 🐛 Fixes\n\n- [Issue][AB-4321] - this is a PR 2 title message\n - PR: #2\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n`
)
}) })
it('Extract label from title, match multiple', async () => { it('Extract label from title, match multiple', async () => {
configuration.label_extractor = [ configuration.label_extractor = [
{ {
"pattern": "\\[Feature\\]|\\[Issue\\]", pattern: '\\[Feature\\]|\\[Issue\\]',
"on_property": "title", on_property: 'title',
"method": "match" method: 'match'
} }
] ]
const resultChangelog = buildChangelog( const resultChangelog = buildChangelog(mergedPullRequests, {
mergedPullRequests, owner: 'mikepenz',
{ repo: 'test-repo',
owner: "mikepenz", fromTag: '1.0.0',
repo: "test-repo", toTag: '2.0.0',
fromTag: "1.0.0", failOnError: false,
toTag: "2.0.0", commitMode: false,
failOnError: false, configuration
commitMode: false, })
configuration: configuration
}
)
expect(resultChangelog).toStrictEqual(`## 🚀 Features\n\n- [Feature][AB-1234] - this is a PR 1 title message\n - PR: #1\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n## 🐛 Fixes\n\n- [Issue][AB-4321] - this is a PR 2 title message\n - PR: #2\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n`) expect(resultChangelog).toStrictEqual(
`## 🚀 Features\n\n- [Feature][AB-1234] - this is a PR 1 title message\n - PR: #1\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n## 🐛 Fixes\n\n- [Issue][AB-4321] - this is a PR 2 title message\n - PR: #2\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n`
)
}) })
it('Extract label from title, match multiple exhaustive', async () => { // test set of PRs with lables predefined
let customConfig = configuration const pullRequestsWithLabels: PullRequestInfo[] = []
pullRequestsWithLabels.push(
{
number: 1,
title: '[ABC-1234] - this is a PR 1 title message',
htmlURL: '',
baseBranch: '',
mergedAt: moment(),
mergeCommitSha: 'sha1',
author: 'Mike',
repoName: 'test-repo',
labels: new Set<string>().add('feature'),
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
},
{
number: 2,
title: '[ABC-4321] - this is a PR 2 title message',
htmlURL: '',
baseBranch: '',
mergedAt: moment(),
mergeCommitSha: 'sha1',
author: 'Mike',
repoName: 'test-repo',
labels: new Set<string>().add('issue').add('fix'),
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
},
{
number: 3,
title: '[ABC-1234] - this is a PR 3 title message',
htmlURL: '',
baseBranch: '',
mergedAt: moment().add(1, 'days'),
mergeCommitSha: 'sha1',
author: 'Mike',
repoName: 'test-repo',
labels: new Set<string>().add('issue').add('feature').add('fix'),
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
},
{
number: 4,
title: '[AB-404] - not found label',
htmlURL: '',
baseBranch: '',
mergedAt: moment(),
mergeCommitSha: 'sha1',
author: 'Mike',
repoName: 'test-repo',
labels: new Set<string>().add(''),
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
}
)
it('Match multiple labels exhaustive for category', async () => {
const customConfig = Object.assign({}, DefaultConfiguration)
customConfig.categories = [ customConfig.categories = [
{ {
"title": "## 🚀 Features and 🐛 Issues", title: '## 🚀 Features and 🐛 Issues',
"labels": ["[Feature]", "[Issue]"], labels: ['Feature', 'Issue'],
"exhaustive": true exhaustive: true
}, },
{ {
"title": "## 🚀 Features", title: '## 🚀 Features',
"labels": ["[Feature]", "[Feature2]"], labels: ['Feature', 'Feature2'],
"exhaustive": true exhaustive: true
}, },
{ {
"title": "## 🐛 Fixes", title: '## 🐛 Fixes',
"labels": ["[Issue]", "[Issue2]"], labels: ['Issue', 'Issue2'],
"exhaustive": true exhaustive: true
}
]
customConfig.label_extractor = [
{
"pattern": "\\[Feature\\]",
"on_property": "title",
"method": "match"
},
{
"pattern": "\\[Issue\\]",
"on_property": "title",
"method": "match"
} }
] ]
const resultChangelog = buildChangelog( const resultChangelog = buildChangelog(pullRequestsWithLabels, {
mergedPullRequests, owner: 'mikepenz',
{ repo: 'test-repo',
owner: "mikepenz", fromTag: '1.0.0',
repo: "test-repo", toTag: '2.0.0',
fromTag: "1.0.0", failOnError: false,
toTag: "2.0.0", commitMode: false,
failOnError: false, configuration: customConfig
commitMode: false, })
configuration: customConfig
} expect(resultChangelog).toStrictEqual(
`## 🚀 Features and 🐛 Issues\n\n- [ABC-1234] - this is a PR 3 title message\n - PR: #3\n\n`
) )
expect(resultChangelog).toStrictEqual(`## 🚀 Features and 🐛 Issues\n\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n`)
}) })
it('Deduplicate duplicated PRs', async () => {
const customConfig = Object.assign({}, DefaultConfiguration)
customConfig.duplicate_filter = {
pattern: '\\[ABC-....\\]',
on_property: 'title',
method: 'match'
}
const resultChangelog = buildChangelog(pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
failOnError: false,
commitMode: false,
configuration: customConfig
})
expect(resultChangelog).toStrictEqual(
`## 🚀 Features\n\n- [ABC-1234] - this is a PR 3 title message\n - PR: #3\n\n## 🐛 Fixes\n\n- [ABC-1234] - this is a PR 3 title message\n - PR: #3\n- [ABC-4321] - this is a PR 2 title message\n - PR: #2\n\n`
)
})
it('Deduplicate duplicated PRs DESC', async () => {
const customConfig = Object.assign({}, DefaultConfiguration)
customConfig.sort = "DESC"
customConfig.duplicate_filter = {
pattern: '\\[ABC-....\\]',
on_property: 'title',
method: 'match'
}
const resultChangelog = buildChangelog(pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
failOnError: false,
commitMode: false,
configuration: customConfig
})
expect(resultChangelog).toStrictEqual(
`## 🚀 Features\n\n- [ABC-1234] - this is a PR 1 title message\n - PR: #1\n\n## 🐛 Fixes\n\n- [ABC-4321] - this is a PR 2 title message\n - PR: #2\n\n`
)
})
Generated Vendored
+89 -50
View File
@@ -169,6 +169,7 @@ exports.DefaultConfiguration = {
], ],
ignore_labels: ['ignore'], ignore_labels: ['ignore'],
label_extractor: [], label_extractor: [],
duplicate_filter: undefined,
transformers: [], transformers: [],
tag_resolver: { tag_resolver: {
// defines the logic on how to resolve the previous tag, only relevant if `fromTag` is not specified // defines the logic on how to resolve the previous tag, only relevant if `fromTag` is not specified
@@ -1135,36 +1136,38 @@ function buildChangelog(prs, options) {
const sortAsc = sort.toUpperCase() === 'ASC'; const sortAsc = sort.toUpperCase() === 'ASC';
prs = (0, pullRequests_1.sortPullRequests)(prs, sortAsc); prs = (0, pullRequests_1.sortPullRequests)(prs, sortAsc);
core.info(`️ Sorted all pull requests ascending: ${sort}`); core.info(`️ Sorted all pull requests ascending: ${sort}`);
// drop duplicate pull requests
if (config.duplicate_filter !== undefined) {
const extractor = validateTransformer(config.duplicate_filter);
if (extractor != null) {
core.info(`️ Remove duplicated pull requests using \`duplicate_filter\``);
const deduplicatedMap = new Map();
for (const pr of prs) {
const extracted = extractValues(pr, extractor, 'dupliate_filter');
if (extracted !== null && extracted.length > 0) {
deduplicatedMap.set(extracted[0], pr);
}
else {
core.debug(`️ PR (${pr.number}) did not resolve a ID using the \`duplicate_filter\``);
}
}
const deduplicatedPRs = Array.from(deduplicatedMap.values());
const removedElements = prs.length - deduplicatedPRs.length;
core.info(`️ Removed ${removedElements} pull requests during deduplication`);
prs = deduplicatedPRs;
}
else {
core.warning(`⚠️ Configured \`duplicate_filter\` invalid.`);
}
}
// extract additional labels from the commit message // extract additional labels from the commit message
const labelExtractors = validateTransformers(config.label_extractor); const labelExtractors = validateTransformers(config.label_extractor);
for (const extractor of labelExtractors) { for (const extractor of labelExtractors) {
if (extractor.pattern != null) { for (const pr of prs) {
for (const pr of prs) { const extracted = extractValues(pr, extractor, 'label_extractor');
let onValue; if (extracted !== null) {
if (extractor.onProperty !== undefined) { for (const label of extracted) {
let value = pr[extractor.onProperty]; pr.labels.add(label);
if (value === undefined) {
core.warning(`⚠️ the provided property '${extractor.onProperty}' for \`label_extractor\` is not valid`);
value = pr['body'];
}
onValue = value;
}
else {
onValue = pr.body;
}
if (extractor.method === 'match') {
const lables = onValue.match(extractor.pattern);
if (lables !== null) {
for (const label of lables) {
pr.labels.add(label.toLocaleLowerCase());
}
}
}
else {
const label = onValue.replace(extractor.pattern, extractor.target);
if (label !== '') {
pr.labels.add(label.toLocaleLowerCase());
}
} }
} }
} }
@@ -1306,30 +1309,66 @@ function validateTransformers(specifiedTransformers) {
const transformers = specifiedTransformers || configuration_1.DefaultConfiguration.transformers; const transformers = specifiedTransformers || configuration_1.DefaultConfiguration.transformers;
return transformers return transformers
.map(transformer => { .map(transformer => {
var _a; return validateTransformer(transformer);
try {
let onProperty = undefined;
let method = undefined;
if (transformer.hasOwnProperty('on_property')) {
onProperty = transformer.on_property;
method = transformer.method;
}
return {
pattern: new RegExp(transformer.pattern.replace('\\\\', '\\'), (_a = transformer.flags) !== null && _a !== void 0 ? _a : 'gu'),
target: transformer.target || '',
onProperty,
method
};
}
catch (e) {
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`);
return {
pattern: null,
target: ''
};
}
}) })
.filter(transformer => transformer.pattern != null); .filter(transformer => (transformer === null || transformer === void 0 ? void 0 : transformer.pattern) != null)
.map(transformer => {
return transformer;
});
}
function validateTransformer(transformer) {
var _a;
if (transformer === undefined) {
return null;
}
try {
let onProperty = undefined;
let method = undefined;
if (transformer.hasOwnProperty('on_property')) {
onProperty = transformer.on_property;
method = transformer.method;
}
return {
pattern: new RegExp(transformer.pattern.replace('\\\\', '\\'), (_a = transformer.flags) !== null && _a !== void 0 ? _a : 'gu'),
target: transformer.target || '',
onProperty,
method
};
}
catch (e) {
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`);
return null;
}
}
function extractValues(pr, extractor, extractor_usecase) {
if (extractor.pattern == null) {
return null;
}
let onValue;
if (extractor.onProperty !== undefined) {
let value = pr[extractor.onProperty];
if (value === undefined) {
core.warning(`⚠️ the provided property '${extractor.onProperty}' for \`${extractor_usecase}\` is not valid`);
value = pr['body'];
}
onValue = value;
}
else {
onValue = pr.body;
}
if (extractor.method === 'match') {
const lables = onValue.match(extractor.pattern);
if (lables !== null) {
return lables.map(label => label.toLocaleLowerCase());
}
}
else {
const label = onValue.replace(extractor.pattern, extractor.target);
if (label !== '') {
return [label.toLocaleLowerCase()];
}
}
return null;
} }
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -1
View File
@@ -3,13 +3,14 @@ export interface Configuration {
max_pull_requests: number max_pull_requests: number
max_back_track_time_days: number max_back_track_time_days: number
exclude_merge_branches: string[] exclude_merge_branches: string[]
sort: string sort: string // "ASC" or "DESC"
template: string template: string
pr_template: string pr_template: string
empty_template: string empty_template: string
categories: Category[] categories: Category[]
ignore_labels: string[] ignore_labels: string[]
label_extractor: Extractor[] label_extractor: Extractor[]
duplicate_filter?: Extractor // extract an identifier from a PR used to detect duplicates, will keep the last match (depends on `sort`)
transformers: Transformer[] transformers: Transformer[]
tag_resolver: TagResolver tag_resolver: TagResolver
base_branches: string[] base_branches: string[]
@@ -61,6 +62,7 @@ export const DefaultConfiguration: Configuration = {
], // the categories to support for the ordering ], // the categories to support for the ordering
ignore_labels: ['ignore'], // list of lables being ignored from the changelog ignore_labels: ['ignore'], // list of lables being ignored from the changelog
label_extractor: [], // extracts additional labels from the commit message given a regex label_extractor: [], // extracts additional labels from the commit message given a regex
duplicate_filter: undefined, // extract an identifier from a PR used to detect duplicates, will keep the last match (depends on `sort`)
transformers: [], // transformers to apply on the PR description according to the `pr_template` transformers: [], // transformers to apply on the PR description according to the `pr_template`
tag_resolver: { tag_resolver: {
// defines the logic on how to resolve the previous tag, only relevant if `fromTag` is not specified // defines the logic on how to resolve the previous tag, only relevant if `fromTag` is not specified
+104 -53
View File
@@ -19,37 +19,42 @@ export function buildChangelog(
prs = sortPullRequests(prs, sortAsc) prs = sortPullRequests(prs, sortAsc)
core.info(`️ Sorted all pull requests ascending: ${sort}`) core.info(`️ Sorted all pull requests ascending: ${sort}`)
// drop duplicate pull requests
if (config.duplicate_filter !== undefined) {
const extractor = validateTransformer(config.duplicate_filter)
if (extractor != null) {
core.info(`️ Remove duplicated pull requests using \`duplicate_filter\``)
const deduplicatedMap = new Map<string, PullRequestInfo>()
for (const pr of prs) {
const extracted = extractValues(pr, extractor, 'dupliate_filter')
if (extracted !== null && extracted.length > 0) {
deduplicatedMap.set(extracted[0], pr)
} else {
core.debug(
`️ PR (${pr.number}) did not resolve a ID using the \`duplicate_filter\``
)
}
}
const deduplicatedPRs = Array.from(deduplicatedMap.values())
const removedElements = prs.length - deduplicatedPRs.length
core.info(
`️ Removed ${removedElements} pull requests during deduplication`
)
prs = deduplicatedPRs
} else {
core.warning(`⚠️ Configured \`duplicate_filter\` invalid.`)
}
}
// extract additional labels from the commit message // extract additional labels from the commit message
const labelExtractors = validateTransformers(config.label_extractor) const labelExtractors = validateTransformers(config.label_extractor)
for (const extractor of labelExtractors) { for (const extractor of labelExtractors) {
if (extractor.pattern != null) { for (const pr of prs) {
for (const pr of prs) { const extracted = extractValues(pr, extractor, 'label_extractor')
let onValue if (extracted !== null) {
if (extractor.onProperty !== undefined) { for (const label of extracted) {
let value: string = pr[extractor.onProperty] pr.labels.add(label)
if (value === undefined) {
core.warning(
`⚠️ the provided property '${extractor.onProperty}' for \`label_extractor\` is not valid`
)
value = pr['body']
}
onValue = value
} else {
onValue = pr.body
}
if (extractor.method === 'match') {
const lables = onValue.match(extractor.pattern)
if (lables !== null) {
for (const label of lables) {
pr.labels.add(label.toLocaleLowerCase())
}
}
} else {
const label = onValue.replace(extractor.pattern, extractor.target)
if (label !== '') {
pr.labels.add(label.toLocaleLowerCase())
}
} }
} }
} }
@@ -276,32 +281,78 @@ function validateTransformers(
specifiedTransformers || DefaultConfiguration.transformers specifiedTransformers || DefaultConfiguration.transformers
return transformers return transformers
.map(transformer => { .map(transformer => {
try { return validateTransformer(transformer)
let onProperty = undefined
let method = undefined
if (transformer.hasOwnProperty('on_property')) {
onProperty = (transformer as Extractor).on_property
method = (transformer as Extractor).method
}
return {
pattern: new RegExp(
transformer.pattern.replace('\\\\', '\\'),
transformer.flags ?? 'gu'
),
target: transformer.target || '',
onProperty,
method
}
} catch (e) {
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`)
return {
pattern: null,
target: ''
}
}
}) })
.filter(transformer => transformer.pattern != null) .filter(transformer => transformer?.pattern != null)
.map(transformer => {
return transformer as RegexTransformer
})
}
function validateTransformer(
transformer?: Transformer
): RegexTransformer | null {
if (transformer === undefined) {
return null
}
try {
let onProperty = undefined
let method = undefined
if (transformer.hasOwnProperty('on_property')) {
onProperty = (transformer as Extractor).on_property
method = (transformer as Extractor).method
}
return {
pattern: new RegExp(
transformer.pattern.replace('\\\\', '\\'),
transformer.flags ?? 'gu'
),
target: transformer.target || '',
onProperty,
method
}
} catch (e) {
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`)
return null
}
}
function extractValues(
pr: PullRequestInfo,
extractor: RegexTransformer,
extractor_usecase: string
): string[] | null {
if (extractor.pattern == null) {
return null
}
let onValue
if (extractor.onProperty !== undefined) {
let value: string = pr[extractor.onProperty]
if (value === undefined) {
core.warning(
`⚠️ the provided property '${extractor.onProperty}' for \`${extractor_usecase}\` is not valid`
)
value = pr['body']
}
onValue = value
} else {
onValue = pr.body
}
if (extractor.method === 'match') {
const lables = onValue.match(extractor.pattern)
if (lables !== null) {
return lables.map(label => label.toLocaleLowerCase())
}
} else {
const label = onValue.replace(extractor.pattern, extractor.target)
if (label !== '') {
return [label.toLocaleLowerCase()]
}
}
return null
} }
interface RegexTransformer { interface RegexTransformer {