Merge pull request #457 from mikepenz/feature/filter_duplicates
Feature/filter duplicates
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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('')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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(
|
||||||
'',
|
'',
|
||||||
|
|||||||
+243
-143
@@ -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[] = []
|
||||||
|
mergedPullRequests.push(
|
||||||
|
{
|
||||||
number: 1,
|
number: 1,
|
||||||
title: "[Feature][AB-1234] - this is a PR 1 title message",
|
title: '[Feature][AB-1234] - this is a PR 1 title message',
|
||||||
htmlURL: "",
|
htmlURL: '',
|
||||||
baseBranch: "",
|
baseBranch: '',
|
||||||
mergedAt: moment(),
|
mergedAt: moment(),
|
||||||
mergeCommitSha: "sha1",
|
mergeCommitSha: 'sha1',
|
||||||
author: "Mike",
|
author: 'Mike',
|
||||||
repoName: "test-repo",
|
repoName: 'test-repo',
|
||||||
labels: new Set<string>(),
|
labels: new Set<string>(),
|
||||||
milestone: "",
|
milestone: '',
|
||||||
body: "no magic body for this matter",
|
body: 'no magic body for this matter',
|
||||||
assignees: [],
|
assignees: [],
|
||||||
requestedReviewers: []
|
requestedReviewers: []
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
number: 2,
|
number: 2,
|
||||||
title: "[Issue][AB-4321] - this is a PR 2 title message",
|
title: '[Issue][AB-4321] - this is a PR 2 title message',
|
||||||
htmlURL: "",
|
htmlURL: '',
|
||||||
baseBranch: "",
|
baseBranch: '',
|
||||||
mergedAt: moment(),
|
mergedAt: moment(),
|
||||||
mergeCommitSha: "sha1",
|
mergeCommitSha: 'sha1',
|
||||||
author: "Mike",
|
author: 'Mike',
|
||||||
repoName: "test-repo",
|
repoName: 'test-repo',
|
||||||
labels: new Set<string>(),
|
labels: new Set<string>(),
|
||||||
milestone: "",
|
milestone: '',
|
||||||
body: "no magic body for this matter",
|
body: 'no magic body for this matter',
|
||||||
assignees: [],
|
assignees: [],
|
||||||
requestedReviewers: []
|
requestedReviewers: []
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
number: 3,
|
number: 3,
|
||||||
title: "[Issue][Feature][AB-1234321] - this is a PR 3 title message",
|
title: '[Issue][Feature][AB-1234321] - this is a PR 3 title message',
|
||||||
htmlURL: "",
|
htmlURL: '',
|
||||||
baseBranch: "",
|
baseBranch: '',
|
||||||
mergedAt: moment(),
|
mergedAt: moment(),
|
||||||
mergeCommitSha: "sha1",
|
mergeCommitSha: 'sha1',
|
||||||
author: "Mike",
|
author: 'Mike',
|
||||||
repoName: "test-repo",
|
repoName: 'test-repo',
|
||||||
labels: new Set<string>(),
|
labels: new Set<string>(),
|
||||||
milestone: "",
|
milestone: '',
|
||||||
body: "no magic body for this matter",
|
body: 'no magic body for this matter',
|
||||||
assignees: [],
|
assignees: [],
|
||||||
requestedReviewers: []
|
requestedReviewers: []
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
number: 4,
|
number: 4,
|
||||||
title: "[AB-404] - not found label",
|
title: '[AB-404] - not found label',
|
||||||
htmlURL: "",
|
htmlURL: '',
|
||||||
baseBranch: "",
|
baseBranch: '',
|
||||||
mergedAt: moment(),
|
mergedAt: moment(),
|
||||||
mergeCommitSha: "sha1",
|
mergeCommitSha: 'sha1',
|
||||||
author: "Mike",
|
author: 'Mike',
|
||||||
repoName: "test-repo",
|
repoName: 'test-repo',
|
||||||
labels: new Set<string>(),
|
labels: new Set<string>(),
|
||||||
milestone: "",
|
milestone: '',
|
||||||
body: "no magic body for this matter",
|
body: 'no magic body for this matter',
|
||||||
assignees: [],
|
assignees: [],
|
||||||
requestedReviewers: []
|
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",
|
|
||||||
toTag: "2.0.0",
|
|
||||||
failOnError: false,
|
failOnError: false,
|
||||||
commitMode: false,
|
commitMode: false,
|
||||||
configuration: 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",
|
|
||||||
toTag: "2.0.0",
|
|
||||||
failOnError: false,
|
failOnError: false,
|
||||||
commitMode: false,
|
commitMode: false,
|
||||||
configuration: 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",
|
|
||||||
toTag: "2.0.0",
|
|
||||||
failOnError: false,
|
failOnError: false,
|
||||||
commitMode: false,
|
commitMode: false,
|
||||||
configuration: 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",
|
|
||||||
toTag: "2.0.0",
|
|
||||||
failOnError: false,
|
failOnError: false,
|
||||||
commitMode: false,
|
commitMode: false,
|
||||||
configuration: 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 = [
|
const resultChangelog = buildChangelog(pullRequestsWithLabels, {
|
||||||
{
|
owner: 'mikepenz',
|
||||||
"pattern": "\\[Feature\\]",
|
repo: 'test-repo',
|
||||||
"on_property": "title",
|
fromTag: '1.0.0',
|
||||||
"method": "match"
|
toTag: '2.0.0',
|
||||||
},
|
|
||||||
{
|
|
||||||
"pattern": "\\[Issue\\]",
|
|
||||||
"on_property": "title",
|
|
||||||
"method": "match"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const resultChangelog = buildChangelog(
|
|
||||||
mergedPullRequests,
|
|
||||||
{
|
|
||||||
owner: "mikepenz",
|
|
||||||
repo: "test-repo",
|
|
||||||
fromTag: "1.0.0",
|
|
||||||
toTag: "2.0.0",
|
|
||||||
failOnError: false,
|
failOnError: false,
|
||||||
commitMode: false,
|
commitMode: false,
|
||||||
configuration: customConfig
|
configuration: customConfig
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
expect(resultChangelog).toStrictEqual(`## 🚀 Features and 🐛 Issues\n\n- [Issue][Feature][AB-1234321] - this is a PR 3 title message\n - PR: #3\n\n`)
|
expect(resultChangelog).toStrictEqual(
|
||||||
|
`## 🚀 Features and 🐛 Issues\n\n- [ABC-1234] - 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`
|
||||||
|
)
|
||||||
})
|
})
|
||||||
+71
-32
@@ -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) {
|
||||||
let onValue;
|
const extracted = extractValues(pr, extractor, 'label_extractor');
|
||||||
if (extractor.onProperty !== undefined) {
|
if (extracted !== null) {
|
||||||
let value = pr[extractor.onProperty];
|
for (const label of extracted) {
|
||||||
if (value === undefined) {
|
pr.labels.add(label);
|
||||||
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,7 +1309,18 @@ 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 => {
|
||||||
|
return validateTransformer(transformer);
|
||||||
|
})
|
||||||
|
.filter(transformer => (transformer === null || transformer === void 0 ? void 0 : transformer.pattern) != null)
|
||||||
|
.map(transformer => {
|
||||||
|
return transformer;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function validateTransformer(transformer) {
|
||||||
var _a;
|
var _a;
|
||||||
|
if (transformer === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
let onProperty = undefined;
|
let onProperty = undefined;
|
||||||
let method = undefined;
|
let method = undefined;
|
||||||
@@ -1323,13 +1337,38 @@ function validateTransformers(specifiedTransformers) {
|
|||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`);
|
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`);
|
||||||
return {
|
return null;
|
||||||
pattern: null,
|
|
||||||
target: ''
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.filter(transformer => transformer.pattern != 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -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
|
||||||
|
|||||||
+83
-32
@@ -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) {
|
||||||
let onValue
|
const extracted = extractValues(pr, extractor, 'label_extractor')
|
||||||
if (extractor.onProperty !== undefined) {
|
if (extracted !== null) {
|
||||||
let value: string = pr[extractor.onProperty]
|
for (const label of extracted) {
|
||||||
if (value === undefined) {
|
pr.labels.add(label)
|
||||||
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,6 +281,20 @@ function validateTransformers(
|
|||||||
specifiedTransformers || DefaultConfiguration.transformers
|
specifiedTransformers || DefaultConfiguration.transformers
|
||||||
return transformers
|
return transformers
|
||||||
.map(transformer => {
|
.map(transformer => {
|
||||||
|
return validateTransformer(transformer)
|
||||||
|
})
|
||||||
|
.filter(transformer => transformer?.pattern != null)
|
||||||
|
.map(transformer => {
|
||||||
|
return transformer as RegexTransformer
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateTransformer(
|
||||||
|
transformer?: Transformer
|
||||||
|
): RegexTransformer | null {
|
||||||
|
if (transformer === undefined) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
let onProperty = undefined
|
let onProperty = undefined
|
||||||
let method = undefined
|
let method = undefined
|
||||||
@@ -295,13 +314,45 @@ function validateTransformers(
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`)
|
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`)
|
||||||
return {
|
return null
|
||||||
pattern: null,
|
}
|
||||||
target: ''
|
}
|
||||||
|
|
||||||
|
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
|
||||||
.filter(transformer => transformer.pattern != null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RegexTransformer {
|
interface RegexTransformer {
|
||||||
|
|||||||
Reference in New Issue
Block a user