Merge pull request #998 from mikepenz/develop

dev -> main
This commit is contained in:
Mike Penz
2023-01-06 11:19:40 +01:00
committed by GitHub
19 changed files with 751 additions and 563 deletions
+19 -6
View File
@@ -195,7 +195,9 @@ The action supports flexible configuration options to modify vast areas of its b
> **Warning** It is required to have a `checkout` step prior to the changelog step if `configuration` is used, to allow the action to discover the configuration file. Use `configurationJson` as alternative.
This configuration is a `.json` file in the following format. (The below shocases *example* configurations for all possible options. In most scenarios most of the settings will not be needed, and the defaults will be appropiate.)
> **Note** It is possible to provide the configuration as file and as json via the yml file. The order of config values used: `configurationJson` > `configuration` > `DefaultConfiguration`.
This configuration is a `JSON` in the following format. (The below shocases *example* configurations for all possible options. In most scenarios most of the settings will not be needed, and the defaults will be appropiate.)
```json
{
@@ -217,7 +219,14 @@ This configuration is a `.json` file in the following format. (The below shocase
"labels": ["test", "magic"],
"exclude_labels": ["no-magic"],
"exhaustive": true,
"empty_content": "- no matching PRs"
"empty_content": "- no matching PRs",
"rules": [
{
"pattern": "open",
"on_property": "status",
"flags": "gu"
}
]
}
],
"ignore_labels": [
@@ -356,9 +365,9 @@ Table of supported placeholders allowed to be used in the `template` and `empty_
| `${{OWNER}}` | Describes the owner of the repository the changelog was generated for | x |
| `${{REPO}}` | The repository name of the repo the changelog was generated for | x |
| `${{FROM_TAG}}` | Defines the 'start' from where the changelog did consider merged pull requests | x |
| `${{FROM_TAG_DATE}}` | Defines the date at which the 'start' tag was created | x |
| `${{FROM_TAG_DATE}}` | Defines the date at which the 'start' tag was created. Requires `fetchReleaseInformation`. | x |
| `${{TO_TAG}}` | Defines until which tag the changelog did consider merged pull requests | x |
| `${{TO_TAG_DATE}}` | Defines the date at which the 'until' tag was created | x |
| `${{TO_TAG_DATE}}` | Defines the date at which the 'until' tag was created. Requires `fetchReleaseInformation`. | x |
| `${{RELEASE_DIFF}}` | Introduces a link to the full diff between from tag and to tag releases | x |
| `${{CHANGED_FILES}}` | The count of changed files. | |
| `${{ADDITIONS}}` | The count of code additions (lines). | |
@@ -379,10 +388,14 @@ Table of descriptions for the `configuration.json` options to configure the resu
|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| categories | An array of `category` specifications, offering a flexible way to group changes into categories. |
| category.title | The display name of a category in the changelog. |
| category.labels | An array of labels, to match pull request labels against. If any PR label matches any category label, the pull request will show up under this category. |
| category.labels | An array of labels, to match pull request labels against. If any PR label matches any category label, the pull request will show up under this category. (See `exhaustive` to change this) |
| category.exclude_labels | Similar to `labels`, an array of labels to match PRs against, but if a match occurs the PR is excluded from this category. |
| category.exhaustive | Will require all labels defined within this category to be present on the matching PR. |
| category.empty_content | If the category has no matching PRs, this content will be used. When not set, the category will be skipped in the changelog. |
| category.rules | An array of `rules` used to match PRs against. Any match will include the PR. (See `exhaustive` to change this) |
| category.rules.pattern | A `regex` pattern to match the property value towards. Uses `RegExp.test("val")` |
| category.rules.flags | Defines the regex flags specified for the pattern. Default: `gu`. |
| category.rules.on_property | The PR property to match against. [Possible values](https://github.com/mikepenz/release-changelog-builder-action/blob/feature/category_rules/src/configuration.ts#L33-L43). |
| ignore_labels | An array of labels, to match pull request labels against. If any PR label overlaps, the pull request will be ignored from the changelog. This takes precedence over category labels |
| sort | A `sort` specification, offering the ability to define sort order and property. |
| sort.order | The sort order. Allowed values: `ASC`, `DESC` |
@@ -395,7 +408,7 @@ Table of descriptions for the `configuration.json` options to configure the resu
| label_extractor.target | The result pattern. The result text will be used as label. If empty, no label is created. (Unused for `match` method) |
| label_extractor.on_property | The property to retrieve the text from. This is optional. Defaults to: `body`. Alternative values: `title`, `author`, `milestone`. |
| label_extractor.method | The extraction method used. Defaults to: `replace`. Alternative value: `match`. The method specified references the JavaScript String method. |
| label_extractor.flags | Defines the regex flags specified for the pattern. Default: `gu` |
| label_extractor.flags | Defines the regex flags specified for the pattern. Default: `gu`. |
| label_extractor.on_empty | Defines the placeholder to be filled in, if the regex does not lead to a result. |
| duplicate_filter | Defines the `Extractor` to use for retrieving the identifier for a PR. In case of duplicates will keep the last matching pull request (depends on `sort`). See `label_extractor` for details on `Extractor` properties. |
| transformers | An array of `transform` specifications, offering a flexible API to modify the text per pull request. This is applied on the change text created with `pr_template`. `transformers` are executed per change, in the order specified |
+17
View File
@@ -0,0 +1,17 @@
import {mergeConfiguration, parseConfiguration, resolveConfiguration} from '../src/utils'
jest.setTimeout(180000)
it('Configurations are merged correctly', async () => {
const configurationJson = parseConfiguration(`{
"sort": "DESC",
"empty_template": "- no magic changes",
"trim_values": true
}`)
const configurationFile = resolveConfiguration('', 'configs/configuration.json')
const mergedConfiguration = mergeConfiguration(configurationJson, configurationFile)
console.log(mergedConfiguration)
expect(JSON.stringify(mergedConfiguration)).toEqual(`{\"max_tags_to_fetch\":200,\"max_pull_requests\":1000,\"max_back_track_time_days\":1000,\"exclude_merge_branches\":[],\"sort\":\"DESC\",\"template\":\"$\{\{CHANGELOG}}\",\"pr_template\":\"- $\{\{TITLE}}\\n - PR: #$\{\{NUMBER}}\",\"empty_template\":\"- no magic changes\",\"categories\":[{\"title\":\"## 🚀 Features\",\"labels\":[\"feature\"]},{\"title\":\"## 🐛 Fixes\",\"labels\":[\"fix\"]},{\"title\":\"## 🧪 Tests\",\"labels\":[\"test\"]}],\"ignore_labels\":[\"ignore\"],\"label_extractor\":[],\"transformers\":[],\"tag_resolver\":{\"method\":\"semver\"},\"base_branches\":[],\"custom_placeholders\":[],\"trim_values\":true}`)
})
+11 -11
View File
@@ -1,5 +1,5 @@
import {ReleaseNotes} from '../src/releaseNotes'
import {resolveConfiguration} from '../src/utils'
import {mergeConfiguration, resolveConfiguration} from '../src/utils'
import {Octokit} from '@octokit/rest'
jest.setTimeout(180000)
@@ -10,7 +10,7 @@ const octokit = new Octokit({
})
it('Should have empty changelog (tags)', async () => {
const configuration = resolveConfiguration('', 'configs/configuration.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs/configuration.json'))
const releaseNotes = new ReleaseNotes(octokit, {
owner: 'mikepenz',
repo: 'release-changelog-builder-action',
@@ -31,7 +31,7 @@ it('Should have empty changelog (tags)', async () => {
})
it('Should match generated changelog (tags)', async () => {
const configuration = resolveConfiguration('', 'configs/configuration.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs/configuration.json'))
const releaseNotes = new ReleaseNotes(octokit, {
owner: 'mikepenz',
repo: 'release-changelog-builder-action',
@@ -57,7 +57,7 @@ it('Should match generated changelog (tags)', async () => {
})
it('Should match generated changelog (refs)', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_all_placeholders.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_all_placeholders.json'))
const releaseNotes = new ReleaseNotes(octokit, {
owner: 'mikepenz',
repo: 'release-changelog-builder-action',
@@ -91,7 +91,7 @@ nhoelzl
})
it('Should match generated changelog and replace all occurrences (refs)', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_replace_all_placeholders.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_replace_all_placeholders.json'))
const releaseNotes = new ReleaseNotes(octokit, {
owner: 'mikepenz',
repo: 'release-changelog-builder-action',
@@ -127,7 +127,7 @@ nhoelzl
})
it('Should match ordered ASC', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_asc.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_asc.json'))
const releaseNotes = new ReleaseNotes(octokit, {
owner: 'mikepenz',
repo: 'release-changelog-builder-action',
@@ -148,7 +148,7 @@ it('Should match ordered ASC', async () => {
})
it('Should match ordered DESC', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_desc.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_desc.json'))
const releaseNotes = new ReleaseNotes(octokit, {
owner: 'mikepenz',
repo: 'release-changelog-builder-action',
@@ -169,7 +169,7 @@ it('Should match ordered DESC', async () => {
})
it('Should match ordered by title ASC', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_sort_title_asc.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_sort_title_asc.json'))
const releaseNotes = new ReleaseNotes(octokit, {
owner: 'mikepenz',
repo: 'release-changelog-builder-action',
@@ -192,7 +192,7 @@ it('Should match ordered by title ASC', async () => {
})
it('Should match ordered by title DESC', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_sort_title_desc.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_sort_title_desc.json'))
const releaseNotes = new ReleaseNotes(octokit, {
owner: 'mikepenz',
repo: 'release-changelog-builder-action',
@@ -215,7 +215,7 @@ it('Should match ordered by title DESC', async () => {
})
it('Should ignore PRs not merged into develop branch', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_base_branches_develop.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_base_branches_develop.json'))
const releaseNotes = new ReleaseNotes(octokit, {
owner: 'mikepenz',
repo: 'release-changelog-builder-action',
@@ -236,7 +236,7 @@ it('Should ignore PRs not merged into develop branch', async () => {
})
it('Should ignore PRs not merged into main branch', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_base_branches_main.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_base_branches_main.json'))
const releaseNotes = new ReleaseNotes(octokit, {
owner: 'mikepenz',
repo: 'release-changelog-builder-action',
+15 -15
View File
@@ -1,10 +1,10 @@
import {resolveConfiguration} from '../src/utils'
import {mergeConfiguration, resolveConfiguration} from '../src/utils'
import {ReleaseNotesBuilder} from '../src/releaseNotesBuilder'
jest.setTimeout(180000)
it('Should match generated changelog (unspecified fromTag)', async () => {
const configuration = resolveConfiguration('', 'configs/configuration.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs/configuration.json'))
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
null,
@@ -34,7 +34,7 @@ it('Should match generated changelog (unspecified fromTag)', async () => {
})
it('Should match generated changelog (unspecified tags)', async () => {
const configuration = resolveConfiguration('', 'configs/configuration.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs/configuration.json'))
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
null,
@@ -59,7 +59,7 @@ it('Should match generated changelog (unspecified tags)', async () => {
})
it('Should use empty placeholder', async () => {
const configuration = resolveConfiguration('', 'configs/configuration.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs/configuration.json'))
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
null,
@@ -84,7 +84,7 @@ it('Should use empty placeholder', async () => {
})
it('Should fill empty placeholders', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_empty_all_placeholders.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_empty_all_placeholders.json'))
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
null,
@@ -111,7 +111,7 @@ it('Should fill empty placeholders', async () => {
})
it('Should fill `template` placeholders', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_empty_all_placeholders.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_empty_all_placeholders.json'))
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
null,
@@ -138,7 +138,7 @@ it('Should fill `template` placeholders', async () => {
})
it('Should fill `template` placeholders, ignore', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_empty_all_placeholders.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_empty_all_placeholders.json'))
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
null,
@@ -165,7 +165,7 @@ it('Should fill `template` placeholders, ignore', async () => {
})
it('Uncategorized category', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_uncategorized_category.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_uncategorized_category.json'))
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
null,
@@ -192,7 +192,7 @@ it('Uncategorized category', async () => {
})
it('Verify commit based changelog', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_commits.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_commits.json'))
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
null,
@@ -219,7 +219,7 @@ it('Verify commit based changelog', async () => {
})
it('Verify commit based changelog, with emoji categorisation', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_commits_emoji.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_commits_emoji.json'))
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
null,
@@ -246,7 +246,7 @@ it('Verify commit based changelog, with emoji categorisation', async () => {
})
it('Verify default inclusion of open PRs', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_including_open.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_including_open.json'))
const releaseNotesBuilder = new ReleaseNotesBuilder(
null, // baseUrl
null, // token
@@ -273,7 +273,7 @@ it('Verify default inclusion of open PRs', async () => {
})
it('Verify custom categorisation of open PRs', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_excluding_open.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_excluding_open.json'))
const releaseNotesBuilder = new ReleaseNotesBuilder(
null, // baseUrl
null, // token
@@ -300,7 +300,7 @@ it('Verify custom categorisation of open PRs', async () => {
})
it('Verify reviewers who approved are fetched and also release information', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_approvers.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_approvers.json'))
const releaseNotesBuilder = new ReleaseNotesBuilder(
null, // baseUrl
null, // token
@@ -327,7 +327,7 @@ it('Verify reviewers who approved are fetched and also release information', asy
})
it('Fetch release information', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_approvers.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_approvers.json'))
configuration.template = '${{FROM_TAG}}-${{FROM_TAG_DATE}}\n${{TO_TAG}}-${{TO_TAG_DATE}}\n${{DAYS_SINCE}}'
const releaseNotesBuilder = new ReleaseNotesBuilder(
null, // baseUrl
@@ -353,7 +353,7 @@ it('Fetch release information', async () => {
})
it('Fetch release information for non existing tag / release', async () => {
const configuration = resolveConfiguration('', 'configs_test/configuration_approvers.json')
const configuration = mergeConfiguration(undefined, resolveConfiguration('', 'configs_test/configuration_approvers.json'))
configuration.template = '${{FROM_TAG}}-${{FROM_TAG_DATE}}\n${{TO_TAG}}-${{TO_TAG_DATE}}\n${{DAYS_SINCE}}'
const releaseNotesBuilder = new ReleaseNotesBuilder(
null, // baseUrl
+118 -182
View File
@@ -122,6 +122,25 @@ const pullRequestWithLabelInBody: PullRequestInfo = {
status: 'merged'
}
const openPullRequest: PullRequestInfo = {
number: 6,
title: 'Still pending open pull request',
htmlURL: '',
baseBranch: '',
createdAt: moment(),
mergedAt: moment(),
mergeCommitSha: 'sha1',
author: 'Mike',
repoName: 'test-repo',
labels: new Set<string>(),
milestone: '',
body: 'Some fancy body message',
assignees: [],
requestedReviewers: [],
approvedReviewers: [],
status: 'open'
}
it('Extract label from title, combined regex', async () => {
configuration.label_extractor = [
{
@@ -130,22 +149,7 @@ it('Extract label from title, combined regex', async () => {
on_property: 'title'
}
]
const resultChangelog = buildChangelog(DefaultDiffInfo, mergedPullRequests, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: {name: '1.0.0'},
toTag: {name: '2.0.0'},
includeOpen: false,
failOnError: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
commitMode: false,
configuration
})
expect(resultChangelog).toStrictEqual(
expect(buildChangelogTest(configuration, mergedPullRequests)).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`
)
})
@@ -161,21 +165,7 @@ it('Extract label from title and body, combined regex', async () => {
let prs = Array.from(mergedPullRequests)
prs.push(pullRequestWithLabelInBody)
const resultChangelog = buildChangelog(DefaultDiffInfo, prs, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: {name: '1.0.0'},
toTag: {name: '2.0.0'},
includeOpen: false,
failOnError: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
commitMode: false,
configuration
})
expect(resultChangelog).toStrictEqual(
expect(buildChangelogTest(configuration, prs)).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- label in body\n - PR: #5\n\n## 🐛 Fixes\n\n- [Issue][AB-4321] - this is a PR 2 title message\n - PR: #2\n\n`
)
})
@@ -193,22 +183,7 @@ it('Extract label from title, split regex', async () => {
on_property: 'title'
}
]
const resultChangelog = buildChangelog(DefaultDiffInfo, mergedPullRequests, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: {name: '1.0.0'},
toTag: {name: '2.0.0'},
includeOpen: false,
failOnError: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
commitMode: false,
configuration
})
expect(resultChangelog).toStrictEqual(
expect(buildChangelogTest(configuration, mergedPullRequests)).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`
)
})
@@ -226,22 +201,7 @@ it('Extract label from title, match', async () => {
method: 'match'
}
]
const resultChangelog = buildChangelog(DefaultDiffInfo, mergedPullRequests, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: {name: '1.0.0'},
toTag: {name: '2.0.0'},
includeOpen: false,
failOnError: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
commitMode: false,
configuration
})
expect(resultChangelog).toStrictEqual(
expect(buildChangelogTest(configuration, mergedPullRequests)).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`
)
})
@@ -254,22 +214,7 @@ it('Extract label from title, match multiple', async () => {
method: 'match'
}
]
const resultChangelog = buildChangelog(DefaultDiffInfo, mergedPullRequests, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: {name: '1.0.0'},
toTag: {name: '2.0.0'},
includeOpen: false,
failOnError: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
commitMode: false,
configuration
})
expect(resultChangelog).toStrictEqual(
expect(buildChangelogTest(configuration, mergedPullRequests)).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`
)
})
@@ -283,22 +228,7 @@ it('Extract label from title, match multiple, custon non matching label', async
on_empty: '[Other]'
}
]
const resultChangelog = buildChangelog(DefaultDiffInfo, mergedPullRequests, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: {name: '1.0.0'},
toTag: {name: '2.0.0'},
includeOpen: false,
failOnError: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
commitMode: false,
configuration
})
expect(resultChangelog).toStrictEqual(
expect(buildChangelogTest(configuration, mergedPullRequests)).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## 🧪 Others\n\n- [AB-404] - not found label\n - PR: #4\n\n`
)
})
@@ -399,22 +329,7 @@ it('Match multiple labels exhaustive for category', async () => {
exhaustive: true
}
]
const resultChangelog = buildChangelog(DefaultDiffInfo, pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: {name: '1.0.0'},
toTag: {name: '2.0.0'},
includeOpen: false,
failOnError: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
commitMode: false,
configuration: customConfig
})
expect(resultChangelog).toStrictEqual(
expect(buildChangelogTest(customConfig, pullRequestsWithLabels)).toStrictEqual(
`## 🚀 Features and 🐛 Issues\n\n- [ABC-1234] - this is a PR 3 title message\n - PR: #3\n\n`
)
})
@@ -426,22 +341,7 @@ it('Deduplicate duplicated PRs', async () => {
on_property: 'title',
method: 'match'
}
const resultChangelog = buildChangelog(DefaultDiffInfo, pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: {name: '1.0.0'},
toTag: {name: '2.0.0'},
includeOpen: false,
failOnError: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
commitMode: false,
configuration: customConfig
})
expect(resultChangelog).toStrictEqual(
expect(buildChangelogTest(customConfig, pullRequestsWithLabels)).toStrictEqual(
`## 🚀 Features\n\n- [ABC-1234] - this is a PR 3 title message\n - PR: #3\n\n## 🐛 Fixes\n\n- [ABC-4321] - this is a PR 2 title message\n - PR: #2\n- [ABC-1234] - this is a PR 3 title message\n - PR: #3\n\n`
)
})
@@ -454,22 +354,7 @@ it('Deduplicate duplicated PRs DESC', async () => {
on_property: 'title',
method: 'match'
}
const resultChangelog = buildChangelog(DefaultDiffInfo, pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: {name: '1.0.0'},
toTag: {name: '2.0.0'},
includeOpen: false,
failOnError: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
commitMode: false,
configuration: customConfig
})
expect(resultChangelog).toStrictEqual(
expect(buildChangelogTest(customConfig, pullRequestsWithLabels)).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`
)
})
@@ -487,22 +372,7 @@ it('Use empty_content for empty category', async () => {
labels: ['Feature']
}
]
const resultChangelog = buildChangelog(DefaultDiffInfo, pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: {name: '1.0.0'},
toTag: {name: '2.0.0'},
includeOpen: false,
failOnError: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
commitMode: false,
configuration: customConfig
})
expect(resultChangelog).toStrictEqual(
expect(buildChangelogTest(customConfig, pullRequestsWithLabels)).toStrictEqual(
`## 🚀 Features and 🐛 Issues\n\n- No PRs in this category\n\n## 🚀 Features\n\n- [ABC-1234] - this is a PR 1 title message\n - PR: #1\n- [ABC-1234] - this is a PR 3 title message\n - PR: #3\n\n`
)
})
@@ -572,22 +442,7 @@ it('Use exclude labels to not include a PR within a category.', async () => {
exclude_labels: ['Fix']
}
]
const resultChangelog = buildChangelog(DefaultDiffInfo, pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: {name: '1.0.0'},
toTag: {name: '2.0.0'},
includeOpen: false,
failOnError: false,
fetchReviewers: false,
fetchReleaseInformation: false,
fetchReviews: false,
commitMode: false,
configuration: customConfig
})
expect(resultChangelog).toStrictEqual(
expect(buildChangelogTest(customConfig, pullRequestsWithLabels)).toStrictEqual(
`## 🚀 Features and 🐛 Issues\n\n- [ABC-1234] - this is a PR 3 title message\n - PR: #3\n\n## 🚀 Features and/or 🐛 Issues But No 🐛 Fixes\n\n- [ABC-1234] - this is a PR 1 title message\n - PR: #1\n\n`
)
})
@@ -632,7 +487,92 @@ it('Extract custom placeholder from PR body and replace in global template', asy
'${{CHANGELOG}}\n\n${{C_PLACEHOLER_2[2]}}\n\n${{C_PLACEHOLER_2[*]}}${{C_PLACEHOLDER_1[7]}}${{C_PLACEHOLER_2[1493]}}${{C_PLACEHOLER_4[*]}}${{C_PLACEHOLER_4[0]}}${{C_PLACEHOLER_3[1]}}'
customConfig.pr_template = '${{BODY}} ----> ${{C_PLACEHOLDER_1}}${{C_PLACEHOLER_3}}'
const resultChangelog = buildChangelog(DefaultDiffInfo, mergedPullRequests, {
expect(buildChangelogTest(customConfig, mergedPullRequests)).toStrictEqual(
`## 🚀 Features\n\nno magic body1 for this matter ----> - body1body1\nno magic body3 for this matter ----> - body3\n\n## 🐛 Fixes\n\nno magic body2 for this matter ----> - body2\nno magic body3 for this matter ----> - body3\n\n## 🧪 Others\n\nno magic body4 for this matter ----> - body4\n\n\n\n\n- ody3\n\n\n- ody1\n- ody2\n- ody3\n- ody4`
)
})
it('Use Rules to include a PR within a Category.', async () => {
const customConfig = Object.assign({}, DefaultConfiguration)
customConfig.categories = [
{
title: '## 🚀 Features But No 🐛 Fixes and only merged with a title containing `[ABC-1234]`',
labels: ['Feature'],
exclude_labels: ['Fix'],
rules: [
{
pattern: "\[ABC-1234\]",
on_property: "title"
},
{
pattern: "merged",
on_property: "status"
}
],
exhaustive: true
}
]
expect(buildChangelogTest(customConfig, pullRequestsWithLabels)).toStrictEqual(
`## 🚀 Features But No 🐛 Fixes and only merged with a title containing \`[ABC-1234]\`\n\n- [ABC-1234] - this is a PR 1 title message\n - PR: #1\n\n`
)
})
it('Use Rules to get all open PRs in a Category.', async () => {
let prs = Array.from(pullRequestsWithLabels)
prs.push(openPullRequest)
const customConfig = Object.assign({}, DefaultConfiguration)
customConfig.categories = [
{
title: '## Open PRs only',
rules: [
{
pattern: "open",
on_property: "status"
}
]
}
]
expect(buildChangelogTest(customConfig, prs)).toStrictEqual(
`## Open PRs only\n\n- Still pending open pull request\n - PR: #6\n\n`
)
})
it('Use Rules to get all open PRs in one Category and merged categorised.', async () => {
let prs = Array.from(pullRequestsWithLabels)
prs.push(openPullRequest)
const customConfig = Object.assign({}, DefaultConfiguration)
customConfig.categories = [
{
title: '## Open PRs only',
rules: [
{
pattern: "open",
on_property: "status"
}
]
},
{
title: '## 🚀 Features and 🐛 Issues',
labels: ['Feature', 'Issue'],
rules: [
{
pattern: "merged",
on_property: "status"
}
],
exhaustive: true,
}
]
expect(buildChangelogTest(customConfig, prs)).toStrictEqual(
`## Open PRs only\n\n- Still pending open pull request\n - PR: #6\n\n## 🚀 Features and 🐛 Issues\n\n- [ABC-1234] - this is a PR 3 title message\n - PR: #3\n\n`
)
})
function buildChangelogTest(config: Configuration, prs: PullRequestInfo[]): string {
return buildChangelog(DefaultDiffInfo, prs, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: {name: '1.0.0'},
@@ -643,10 +583,6 @@ it('Extract custom placeholder from PR body and replace in global template', asy
fetchReleaseInformation: false,
fetchReviews: false,
commitMode: false,
configuration: customConfig
configuration: config
})
expect(resultChangelog).toStrictEqual(
`## 🚀 Features\n\nno magic body1 for this matter ----> - body1body1\nno magic body3 for this matter ----> - body3\n\n## 🐛 Fixes\n\nno magic body2 for this matter ----> - body2\nno magic body3 for this matter ----> - body3\n\n## 🧪 Others\n\nno magic body4 for this matter ----> - body4\n\n\n\n\n- ody3\n\n\n- ody1\n- ody2\n- ody3\n- ody4`
)
})
}
Generated Vendored
+220 -81
View File
@@ -395,18 +395,19 @@ function run() {
// read in path specification, resolve github workspace, and repo path
const inputPath = core.getInput('path');
const repositoryPath = (0, utils_1.retrieveRepositoryPath)(inputPath);
// read in configuration file if possible
let configuration = undefined;
// read in configuration from json if possible
let configJson = undefined;
const configurationJson = core.getInput('configurationJson', {
trimWhitespace: true
});
if (configurationJson) {
configuration = (0, utils_1.parseConfiguration)(configurationJson);
}
if (!configuration) {
const configurationFile = core.getInput('configuration');
configuration = (0, utils_1.resolveConfiguration)(repositoryPath, configurationFile);
configJson = (0, utils_1.parseConfiguration)(configurationJson);
}
// read in the configuration from the file if possible
const configurationFile = core.getInput('configuration');
const configFile = (0, utils_1.resolveConfiguration)(repositoryPath, configurationFile);
// merge configs, use default values from DefaultConfig on missing definition
const configuration = (0, utils_1.mergeConfiguration)(configJson, configFile);
// read in repository inputs
const baseUrl = core.getInput('baseUrl');
const token = core.getInput('token');
@@ -490,7 +491,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.compare = exports.sortPullRequests = exports.PullRequests = exports.EMPTY_COMMENT_INFO = void 0;
exports.retrieveProperty = exports.compare = exports.sortPullRequests = exports.PullRequests = exports.EMPTY_COMMENT_INFO = void 0;
const core = __importStar(__nccwpck_require__(2186));
const moment_1 = __importDefault(__nccwpck_require__(9623));
exports.EMPTY_COMMENT_INFO = {
@@ -737,6 +738,24 @@ function compare(a, b, sort) {
}
}
exports.compare = compare;
/**
* Helper function to retrieve a property from the PullRequestInfo
*/
function retrieveProperty(pr, property, useCase) {
let value = pr[property];
if (value === undefined) {
core.warning(`⚠️ the provided property '${property}' for \`${useCase}\` is not valid. Fallback to 'body'`);
value = pr['body'];
}
else if (value instanceof Set) {
value = Array.from(value).join(','); // join into single string
}
else if (Array.isArray(value)) {
value = value.join(','); // join into single string
}
return value;
}
exports.retrieveProperty = retrieveProperty;
// helper function to add a special open label to prs not merged.
function attachSpeciaLabels(status, labels) {
labels.add(`--rcba-${status}`);
@@ -778,6 +797,127 @@ const mapComment = (comment) => {
};
/***/ }),
/***/ 2364:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.buildRegex = exports.validateTransformer = exports.matchesRules = void 0;
const core = __importStar(__nccwpck_require__(2186));
const pullRequests_1 = __nccwpck_require__(4217);
/**
* Checks if any of the rules match the given PR
*/
function matchesRules(rules, pr, exhaustive) {
const transformers = rules.map(rule => validateTransformer(rule)).filter(t => t !== null);
if (exhaustive) {
return transformers.every(transformer => {
return matches(pr, transformer, 'rule');
});
}
else {
return transformers.some(transformer => {
return matches(pr, transformer, 'rule');
});
}
}
exports.matchesRules = matchesRules;
/**
* Checks if the configured property results in a positive `test` with the regex.
*/
function matches(pr, extractor, extractor_usecase) {
if (extractor.pattern == null) {
return false;
}
if (extractor.onProperty !== undefined && extractor.onProperty.length === 1) {
const prop = extractor.onProperty[0];
const value = (0, pullRequests_1.retrieveProperty)(pr, prop, extractor_usecase);
return extractor.pattern.test(value);
}
return false;
}
function validateTransformer(transformer) {
if (transformer === undefined) {
return null;
}
try {
let target = undefined;
if (transformer.hasOwnProperty('target')) {
target = transformer.target;
}
let onProperty = undefined;
let method = undefined;
let onEmpty = undefined;
if (transformer.hasOwnProperty('method')) {
method = transformer.method;
onEmpty = transformer.on_empty;
onProperty = transformer.on_property;
}
else if (transformer.hasOwnProperty('on_property')) {
onProperty = transformer.on_property;
}
// legacy handling, transform single value input to array
if (!Array.isArray(onProperty)) {
if (onProperty !== undefined) {
onProperty = [onProperty];
}
}
return buildRegex(transformer, target, onProperty, method, onEmpty);
}
catch (e) {
core.warning(`⚠️ Failed to validate transformer: ${transformer.pattern}`);
return null;
}
}
exports.validateTransformer = validateTransformer;
/**
* Constructs the RegExp, providing the configured Regex and additional values
*/
function buildRegex(regex, target, onProperty, method, onEmpty) {
var _a;
try {
return {
pattern: new RegExp(regex.pattern.replace('\\\\', '\\'), (_a = regex.flags) !== null && _a !== void 0 ? _a : 'gu'),
target: target || '',
onProperty,
method,
onEmpty
};
}
catch (e) {
core.warning(`⚠️ Bad replacer regex: ${regex.pattern}`);
return null;
}
}
exports.buildRegex = buildRegex;
/***/ }),
/***/ 5882:
@@ -821,7 +961,6 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.ReleaseNotes = void 0;
const core = __importStar(__nccwpck_require__(2186));
const commits_1 = __nccwpck_require__(3916);
const configuration_1 = __nccwpck_require__(5527);
const pullRequests_1 = __nccwpck_require__(4217);
const transform_1 = __nccwpck_require__(1644);
const utils_1 = __nccwpck_require__(918);
@@ -862,7 +1001,7 @@ class ReleaseNotes {
core.setOutput('commits', diffInfo.commits);
if (mergedPullRequests.length === 0) {
core.warning(`⚠️ No pull requests found`);
return (0, transform_1.replaceEmptyTemplate)(this.options.configuration.empty_template || configuration_1.DefaultConfiguration.empty_template, this.options);
return (0, transform_1.replaceEmptyTemplate)(this.options.configuration.empty_template, this.options);
}
core.startGroup('📦 Build changelog');
const resultChangelog = (0, transform_1.buildChangelog)(diffInfo, mergedPullRequests, this.options);
@@ -903,7 +1042,7 @@ class ReleaseNotes {
const lastCommit = commits[commits.length - 1];
let fromDate = firstCommit.date;
const toDate = lastCommit.date;
const maxDays = configuration.max_back_track_time_days || configuration_1.DefaultConfiguration.max_back_track_time_days;
const maxDays = configuration.max_back_track_time_days;
const maxFromDate = toDate.clone().subtract(maxDays, 'days');
if (maxFromDate.isAfter(fromDate)) {
core.info(`⚠️ Adjusted 'fromDate' to go max ${maxDays} back`);
@@ -911,9 +1050,9 @@ class ReleaseNotes {
}
core.info(`️ Fetching PRs between dates ${fromDate.toISOString()} to ${toDate.toISOString()} for ${owner}/${repo}`);
const pullRequestsApi = new pullRequests_1.PullRequests(octokit);
const pullRequests = yield pullRequestsApi.getBetweenDates(owner, repo, fromDate, toDate, configuration.max_pull_requests || configuration_1.DefaultConfiguration.max_pull_requests);
const pullRequests = yield pullRequestsApi.getBetweenDates(owner, repo, fromDate, toDate, configuration.max_pull_requests);
core.info(`️ Retrieved ${pullRequests.length} PRs for ${owner}/${repo} in date range from API`);
const prCommits = (0, commits_1.filterCommits)(commits, configuration.exclude_merge_branches || configuration_1.DefaultConfiguration.exclude_merge_branches);
const prCommits = (0, commits_1.filterCommits)(commits, configuration.exclude_merge_branches);
core.info(`️ Retrieved ${prCommits.length} release commits for ${owner}/${repo}`);
// create array of commits for this release
const releaseCommitHashes = prCommits.map(commmit => {
@@ -927,14 +1066,14 @@ class ReleaseNotes {
let allPullRequests = mergedPullRequests;
if (includeOpen) {
// retrieve all open pull requests
const openPullRequests = yield pullRequestsApi.getOpen(owner, repo, configuration.max_pull_requests || configuration_1.DefaultConfiguration.max_pull_requests);
const openPullRequests = yield pullRequestsApi.getOpen(owner, repo, configuration.max_pull_requests);
core.info(`️ Retrieved ${openPullRequests.length} open PRs for ${owner}/${repo}`);
// all pull requests
allPullRequests = allPullRequests.concat(openPullRequests);
core.info(`️ Retrieved ${allPullRequests.length} total PRs for ${owner}/${repo}`);
}
// retrieve base branches we allow
const baseBranches = configuration.base_branches || configuration_1.DefaultConfiguration.base_branches;
const baseBranches = configuration.base_branches;
const baseBranchPatterns = baseBranches.map(baseBranch => {
return new RegExp(baseBranch.replace('\\\\', '\\'), 'gu');
});
@@ -987,7 +1126,7 @@ class ReleaseNotes {
if (commits.length === 0) {
return [diffInfo, []];
}
const prCommits = (0, commits_1.filterCommits)(commits, configuration.exclude_merge_branches || configuration_1.DefaultConfiguration.exclude_merge_branches);
const prCommits = (0, commits_1.filterCommits)(commits, configuration.exclude_merge_branches);
core.info(`️ Retrieved ${prCommits.length} commits for ${owner}/${repo}`);
const prs = prCommits.map(function (commit) {
return {
@@ -1058,7 +1197,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.ReleaseNotesBuilder = void 0;
const core = __importStar(__nccwpck_require__(2186));
const configuration_1 = __nccwpck_require__(5527);
const rest_1 = __nccwpck_require__(5375);
const releaseNotes_1 = __nccwpck_require__(5882);
const tags_1 = __nccwpck_require__(7532);
@@ -1125,7 +1263,7 @@ class ReleaseNotesBuilder {
// ensure proper from <-> to tag range
core.startGroup(`🔖 Resolve tags`);
const tagsApi = new tags_1.Tags(octokit);
const tagRange = yield tagsApi.retrieveRange(this.repositoryPath, this.owner, this.repo, this.fromTag, this.toTag, this.ignorePreReleases, this.configuration.max_tags_to_fetch || configuration_1.DefaultConfiguration.max_tags_to_fetch, this.configuration.tag_resolver || configuration_1.DefaultConfiguration.tag_resolver);
const tagRange = yield tagsApi.retrieveRange(this.repositoryPath, this.owner, this.repo, this.fromTag, this.toTag, this.ignorePreReleases, this.configuration.max_tags_to_fetch, this.configuration.tag_resolver);
let thisTag = tagRange.to;
if (!thisTag) {
(0, utils_1.failOrError)(`💥 Missing or couldn't resolve 'toTag'`, this.failOnError);
@@ -1229,8 +1367,8 @@ const github = __importStar(__nccwpck_require__(5438));
const semver = __importStar(__nccwpck_require__(1383));
const semver_1 = __nccwpck_require__(1383);
const gitHelper_1 = __nccwpck_require__(353);
const transform_1 = __nccwpck_require__(1644);
const moment_1 = __importDefault(__nccwpck_require__(9623));
const regexUtils_1 = __nccwpck_require__(2364);
class Tags {
constructor(octokit) {
this.octokit = octokit;
@@ -1354,7 +1492,7 @@ class Tags {
// retrieve the tags from the API
yield this.getTags(owner, repo, maxTagsToFetch), tagResolver);
// check if a transformer was defined
const tagTransformer = (0, transform_1.validateTransformer)(tagResolver.transformer);
const tagTransformer = (0, regexUtils_1.validateTransformer)(tagResolver.transformer);
let transformedTags;
if (tagTransformer != null) {
core.debug(`️ Using configured tagTransformer`);
@@ -1567,21 +1705,21 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.validateTransformer = exports.replaceEmptyTemplate = exports.buildChangelog = void 0;
exports.replaceEmptyTemplate = exports.buildChangelog = void 0;
const core = __importStar(__nccwpck_require__(2186));
const configuration_1 = __nccwpck_require__(5527);
const pullRequests_1 = __nccwpck_require__(4217);
const utils_1 = __nccwpck_require__(918);
const regexUtils_1 = __nccwpck_require__(2364);
const EMPTY_MAP = new Map();
function buildChangelog(diffInfo, prs, options) {
// sort to target order
const config = options.configuration;
const sort = config.sort || configuration_1.DefaultConfiguration.sort;
const sort = config.sort;
prs = (0, pullRequests_1.sortPullRequests)(prs, sort);
core.info(`️ Sorted all pull requests ascending: ${JSON.stringify(sort)}`);
// drop duplicate pull requests
if (config.duplicate_filter !== undefined) {
const extractor = validateTransformer(config.duplicate_filter);
const extractor = (0, regexUtils_1.validateTransformer)(config.duplicate_filter);
if (extractor != null) {
core.info(`️ Remove duplicated pull requests using \`duplicate_filter\``);
const deduplicatedMap = new Map();
@@ -1628,14 +1766,14 @@ function buildChangelog(diffInfo, prs, options) {
const transformedMap = new Map();
// convert PRs to their text representation
for (const pr of prs) {
transformedMap.set(pr, transform(fillPrTemplate(pr, config.pr_template || configuration_1.DefaultConfiguration.pr_template, placeholders, placeholderPrMap, config), validatedTransformers));
transformedMap.set(pr, transform(fillPrTemplate(pr, config.pr_template, placeholders, placeholderPrMap, config), validatedTransformers));
}
core.info(`️ Used ${validatedTransformers.length} transformers to adjust message`);
core.info(`✒️ Wrote messages for ${prs.length} pull requests`);
// bring PRs into the order of categories
const categorized = new Map();
const categories = config.categories || configuration_1.DefaultConfiguration.categories;
const ignoredLabels = config.ignore_labels || configuration_1.DefaultConfiguration.ignore_labels;
const categories = config.categories;
const ignoredLabels = config.ignore_labels;
for (const category of categories) {
categorized.set(category, []);
}
@@ -1652,8 +1790,9 @@ function buildChangelog(diffInfo, prs, options) {
if (pr.status === 'open') {
openPrs.push(body);
}
let matched = false;
let matchedOnce = false; // in case we matched once at least, the PR can't be uncategorized
for (const [category, pullRequests] of categorized) {
let matched = false; // check if we matched within the given category
// check if any exclude label matches
if (category.exclude_labels !== undefined) {
if ((0, utils_1.haveCommonElements)(category.exclude_labels.map(lbl => lbl.toLocaleLowerCase('en')), pr.labels)) {
@@ -1666,23 +1805,36 @@ function buildChangelog(diffInfo, prs, options) {
continue; // one of the exclude labels matched, skip the PR for this category
}
}
if (category.exhaustive === true) {
if ((0, utils_1.haveEveryElements)(category.labels.map(lbl => lbl.toLocaleLowerCase('en')), pr.labels)) {
pullRequests.push(body);
matched = true;
// in case we have exhaustive matching enabled, and have labels and/or rules
// validate for an exhaustive match (e.g. every provided rule applies)
if (category.exhaustive === true && (category.labels !== undefined || category.rules !== undefined)) {
if (category.labels !== undefined) {
matched = (0, utils_1.haveEveryElements)(category.labels.map(lbl => lbl.toLocaleLowerCase('en')), pr.labels);
}
if (matched && category.rules !== undefined) {
matched = (0, regexUtils_1.matchesRules)(category.rules, pr, true);
}
}
else {
if ((0, utils_1.haveCommonElements)(category.labels.map(lbl => lbl.toLocaleLowerCase('en')), pr.labels)) {
pullRequests.push(body);
matched = true;
// if not exhaustive, do individual matches
if (category.labels !== undefined) {
// check if either any of the labels applies
matched = (0, utils_1.haveCommonElements)(category.labels.map(lbl => lbl.toLocaleLowerCase('en')), pr.labels);
}
if (!matched && category.rules !== undefined) {
// if no label did apply, check if any rule applies
matched = (0, regexUtils_1.matchesRules)(category.rules, pr, false);
}
}
if (matched) {
pullRequests.push(body); // if matched add the PR to the list
}
matchedOnce = matchedOnce || matched;
}
if (!matched) {
if (!matchedOnce) {
// we allow to have pull requests included in an "uncategorized" category
for (const [category, pullRequests] of categorized) {
if (category.labels.length === 0) {
if ((category.labels === undefined || category.labels.length === 0) && category.rules === undefined) {
pullRequests.push(body);
break;
}
@@ -1768,7 +1920,7 @@ function buildChangelog(diffInfo, prs, options) {
placeholderMap.set('CHANGES', diffInfo.changes.toString());
placeholderMap.set('COMMITS', diffInfo.commits.toString());
fillAdditionalPlaceholders(options, placeholderMap);
let transformedChangelog = config.template || configuration_1.DefaultConfiguration.template;
let transformedChangelog = config.template;
transformedChangelog = replacePlaceholders(transformedChangelog, EMPTY_MAP, placeholderMap, placeholders, placeholderPrMap, config);
transformedChangelog = replacePrPlaceholders(transformedChangelog, placeholderPrMap, config);
transformedChangelog = cleanupPrPlaceholders(transformedChangelog, placeholders);
@@ -1848,7 +2000,7 @@ function handlePlaceholder(template, key, value, placeholders /* placeholders to
const phs = placeholders.get(key);
if (phs) {
for (const placeholder of phs) {
const transformer = validateTransformer(placeholder.transformer);
const transformer = (0, regexUtils_1.validateTransformer)(placeholder.transformer);
if (transformer === null || transformer === void 0 ? void 0 : transformer.pattern) {
const extractedValue = value.replace(transformer.pattern, transformer.target);
// note: `.replace` will return the full string again if there was no match
@@ -1917,50 +2069,16 @@ function transform(filled, transformers) {
return transformed;
}
function validateTransformers(specifiedTransformers) {
const transformers = specifiedTransformers || configuration_1.DefaultConfiguration.transformers;
const transformers = specifiedTransformers;
return transformers
.map(transformer => {
return validateTransformer(transformer);
return (0, regexUtils_1.validateTransformer)(transformer);
})
.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;
let onEmpty = undefined;
if (transformer.hasOwnProperty('on_property')) {
onProperty = transformer.on_property;
method = transformer.method;
onEmpty = transformer.on_empty;
}
// legacy handling, transform single value input to array
if (!Array.isArray(onProperty)) {
if (onProperty !== undefined) {
onProperty = [onProperty];
}
}
return {
pattern: new RegExp(transformer.pattern.replace('\\\\', '\\'), (_a = transformer.flags) !== null && _a !== void 0 ? _a : 'gu'),
target: transformer.target || '',
onProperty,
method,
onEmpty
};
}
catch (e) {
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`);
return null;
}
}
exports.validateTransformer = validateTransformer;
function extractValues(pr, extractor, extractor_usecase) {
if (extractor.pattern == null) {
return null;
@@ -1971,11 +2089,7 @@ function extractValues(pr, extractor, extractor_usecase) {
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < list.length; i++) {
const prop = list[i];
let value = pr[prop];
if (value === undefined) {
core.warning(`⚠️ the provided property '${extractor.onProperty}' for \`${extractor_usecase}\` is not valid`);
value = pr['body'];
}
const value = (0, pullRequests_1.retrieveProperty)(pr, prop, extractor_usecase);
const values = extractValuesFromString(value, extractor);
if (values !== null) {
results = results.concat(values);
@@ -2041,7 +2155,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.haveEveryElements = exports.haveCommonElements = exports.createOrSet = exports.writeOutput = exports.directoryExistsSync = exports.parseConfiguration = exports.resolveConfiguration = exports.failOrError = exports.retrieveRepositoryPath = void 0;
exports.haveEveryElements = exports.haveCommonElements = exports.createOrSet = exports.writeOutput = exports.directoryExistsSync = exports.mergeConfiguration = exports.parseConfiguration = exports.resolveConfiguration = exports.failOrError = exports.retrieveRepositoryPath = void 0;
const core = __importStar(__nccwpck_require__(2186));
const fs = __importStar(__nccwpck_require__(7147));
const path = __importStar(__nccwpck_require__(1017));
@@ -2130,6 +2244,31 @@ function parseConfiguration(config) {
}
}
exports.parseConfiguration = parseConfiguration;
/**
* Merges the configurations, will fallback to the DefaultConfiguration value
*/
function mergeConfiguration(jc, fc) {
return {
max_tags_to_fetch: (jc === null || jc === void 0 ? void 0 : jc.max_tags_to_fetch) || (fc === null || fc === void 0 ? void 0 : fc.max_tags_to_fetch) || configuration_1.DefaultConfiguration.max_tags_to_fetch,
max_pull_requests: (jc === null || jc === void 0 ? void 0 : jc.max_pull_requests) || (fc === null || fc === void 0 ? void 0 : fc.max_pull_requests) || configuration_1.DefaultConfiguration.max_pull_requests,
max_back_track_time_days: (jc === null || jc === void 0 ? void 0 : jc.max_back_track_time_days) || (fc === null || fc === void 0 ? void 0 : fc.max_back_track_time_days) || configuration_1.DefaultConfiguration.max_back_track_time_days,
exclude_merge_branches: (jc === null || jc === void 0 ? void 0 : jc.exclude_merge_branches) || (fc === null || fc === void 0 ? void 0 : fc.exclude_merge_branches) || configuration_1.DefaultConfiguration.exclude_merge_branches,
sort: (jc === null || jc === void 0 ? void 0 : jc.sort) || (fc === null || fc === void 0 ? void 0 : fc.sort) || configuration_1.DefaultConfiguration.sort,
template: (jc === null || jc === void 0 ? void 0 : jc.template) || (fc === null || fc === void 0 ? void 0 : fc.template) || configuration_1.DefaultConfiguration.template,
pr_template: (jc === null || jc === void 0 ? void 0 : jc.pr_template) || (fc === null || fc === void 0 ? void 0 : fc.pr_template) || configuration_1.DefaultConfiguration.pr_template,
empty_template: (jc === null || jc === void 0 ? void 0 : jc.empty_template) || (fc === null || fc === void 0 ? void 0 : fc.empty_template) || configuration_1.DefaultConfiguration.empty_template,
categories: (jc === null || jc === void 0 ? void 0 : jc.categories) || (fc === null || fc === void 0 ? void 0 : fc.categories) || configuration_1.DefaultConfiguration.categories,
ignore_labels: (jc === null || jc === void 0 ? void 0 : jc.ignore_labels) || (fc === null || fc === void 0 ? void 0 : fc.ignore_labels) || configuration_1.DefaultConfiguration.ignore_labels,
label_extractor: (jc === null || jc === void 0 ? void 0 : jc.label_extractor) || (fc === null || fc === void 0 ? void 0 : fc.label_extractor) || configuration_1.DefaultConfiguration.label_extractor,
duplicate_filter: (jc === null || jc === void 0 ? void 0 : jc.duplicate_filter) || (fc === null || fc === void 0 ? void 0 : fc.duplicate_filter) || configuration_1.DefaultConfiguration.duplicate_filter,
transformers: (jc === null || jc === void 0 ? void 0 : jc.transformers) || (fc === null || fc === void 0 ? void 0 : fc.transformers) || configuration_1.DefaultConfiguration.transformers,
tag_resolver: (jc === null || jc === void 0 ? void 0 : jc.tag_resolver) || (fc === null || fc === void 0 ? void 0 : fc.tag_resolver) || configuration_1.DefaultConfiguration.tag_resolver,
base_branches: (jc === null || jc === void 0 ? void 0 : jc.base_branches) || (fc === null || fc === void 0 ? void 0 : fc.base_branches) || configuration_1.DefaultConfiguration.base_branches,
custom_placeholders: (jc === null || jc === void 0 ? void 0 : jc.custom_placeholders) || (fc === null || fc === void 0 ? void 0 : fc.custom_placeholders) || configuration_1.DefaultConfiguration.custom_placeholders,
trim_values: (jc === null || jc === void 0 ? void 0 : jc.trim_values) || (fc === null || fc === void 0 ? void 0 : fc.trim_values) || configuration_1.DefaultConfiguration.trim_values
};
}
exports.mergeConfiguration = mergeConfiguration;
/**
* Checks if a given directory exists
*/
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+116 -137
View File
@@ -20,14 +20,14 @@
"webpack": "^5.75.0"
},
"devDependencies": {
"@types/jest": "^29.2.4",
"@types/node": "^18.11.12",
"@types/jest": "^29.2.5",
"@types/node": "^18.11.18",
"@types/semver": "^7.3.13",
"@typescript-eslint/parser": "^5.46.0",
"@typescript-eslint/parser": "^5.48.0",
"@vercel/ncc": "^0.36.0",
"eslint": "^8.29.0",
"eslint": "^8.31.0",
"eslint-plugin-github": "^4.6.0",
"eslint-plugin-jest": "^27.1.6",
"eslint-plugin-jest": "^27.2.0",
"jest": "^29.3.1",
"jest-circus": "^29.3.1",
"js-yaml": "^4.1.0",
@@ -708,15 +708,15 @@
"dev": true
},
"node_modules/@eslint/eslintrc": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz",
"integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
"integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==",
"dev": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.4.0",
"globals": "^13.15.0",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
@@ -737,9 +737,9 @@
"dev": true
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
"integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==",
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
"integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
"dev": true,
"dependencies": {
"@humanwhocodes/object-schema": "^1.2.1",
@@ -1631,9 +1631,9 @@
}
},
"node_modules/@types/jest": {
"version": "29.2.4",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.4.tgz",
"integrity": "sha512-PipFB04k2qTRPePduVLTRiPzQfvMeLwUN3Z21hsAKaB/W9IIzgB2pizCL466ftJlcyZqnHoC9ZHpxLGl3fS86A==",
"version": "29.2.5",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.5.tgz",
"integrity": "sha512-H2cSxkKgVmqNHXP7TC2L/WUorrZu8ZigyRywfVzv6EyBlxj39n4C00hjXYQWsbwqgElaj/CiAeSRmk5GoaKTgw==",
"dev": true,
"dependencies": {
"expect": "^29.0.0",
@@ -1652,9 +1652,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "18.11.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.12.tgz",
"integrity": "sha512-FgD3NtTAKvyMmD44T07zz2fEf+OKwutgBCEVM8GcvMGVGaDktiLNTDvPwC/LUe3PinMW+X6CuLOF2Ui1mAlSXg=="
"version": "18.11.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
},
"node_modules/@types/prettier": {
"version": "2.7.1",
@@ -1723,14 +1723,14 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "5.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.0.tgz",
"integrity": "sha512-joNO6zMGUZg+C73vwrKXCd8usnsmOYmgW/w5ZW0pG0RGvqeznjtGDk61EqqTpNrFLUYBW2RSBFrxdAZMqA4OZA==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz",
"integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.46.0",
"@typescript-eslint/types": "5.46.0",
"@typescript-eslint/typescript-estree": "5.46.0",
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/typescript-estree": "5.48.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1750,13 +1750,13 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
"version": "5.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.0.tgz",
"integrity": "sha512-7wWBq9d/GbPiIM6SqPK9tfynNxVbfpihoY5cSFMer19OYUA3l4powA2uv0AV2eAZV6KoAh6lkzxv4PoxOLh1oA==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz",
"integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.46.0",
"@typescript-eslint/visitor-keys": "5.46.0"
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/visitor-keys": "5.48.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1767,9 +1767,9 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
"version": "5.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.0.tgz",
"integrity": "sha512-wHWgQHFB+qh6bu0IAPAJCdeCdI0wwzZnnWThlmHNY01XJ9Z97oKqKOzWYpR2I83QmshhQJl6LDM9TqMiMwJBTw==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz",
"integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1780,13 +1780,13 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
"version": "5.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.0.tgz",
"integrity": "sha512-kDLNn/tQP+Yp8Ro2dUpyyVV0Ksn2rmpPpB0/3MO874RNmXtypMwSeazjEN/Q6CTp8D7ExXAAekPEcCEB/vtJkw==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz",
"integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.46.0",
"@typescript-eslint/visitor-keys": "5.46.0",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/visitor-keys": "5.48.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -1807,12 +1807,12 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
"version": "5.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.0.tgz",
"integrity": "sha512-E13gBoIXmaNhwjipuvQg1ByqSAu/GbEpP/qzFihugJ+MomtoJtFAJG/+2DRPByf57B863m0/q7Zt16V9ohhANw==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz",
"integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "5.46.0",
"@typescript-eslint/types": "5.48.0",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
@@ -2928,13 +2928,13 @@
}
},
"node_modules/eslint": {
"version": "8.29.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz",
"integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==",
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz",
"integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==",
"dev": true,
"dependencies": {
"@eslint/eslintrc": "^1.3.3",
"@humanwhocodes/config-array": "^0.11.6",
"@eslint/eslintrc": "^1.4.1",
"@humanwhocodes/config-array": "^0.11.8",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.10.0",
@@ -2953,7 +2953,7 @@
"file-entry-cache": "^6.0.1",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
"globals": "^13.15.0",
"globals": "^13.19.0",
"grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0",
"import-fresh": "^3.0.0",
@@ -3189,9 +3189,9 @@
"dev": true
},
"node_modules/eslint-plugin-jest": {
"version": "27.1.6",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.1.6.tgz",
"integrity": "sha512-XA7RFLSrlQF9IGtAmhddkUkBuICCTuryfOTfCSWcZHiHb69OilIH05oozH2XA6CEOtztnOd0vgXyvxZodkxGjg==",
"version": "27.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.0.tgz",
"integrity": "sha512-KGIYtelk4rIhKocxRKUEeX+kJ0ZCab/CiSgS8BMcKD7AY7YxXhlg/d51oF5jq2rOrtuJEDYWRwXD95l6l2vtrA==",
"dev": true,
"dependencies": {
"@typescript-eslint/utils": "^5.10.0"
@@ -3337,9 +3337,9 @@
}
},
"node_modules/espree": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz",
"integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==",
"version": "9.4.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
"integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
"dev": true,
"dependencies": {
"acorn": "^8.8.0",
@@ -3755,9 +3755,9 @@
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
},
"node_modules/globals": {
"version": "13.17.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
"integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==",
"version": "13.19.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz",
"integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==",
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@@ -4917,9 +4917,9 @@
"dev": true
},
"node_modules/json5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": {
"json5": "lib/cli.js"
@@ -6292,18 +6292,6 @@
"strip-bom": "^3.0.0"
}
},
"node_modules/tsconfig-paths/node_modules/json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"dev": true,
"dependencies": {
"minimist": "^1.2.0"
},
"bin": {
"json5": "lib/cli.js"
}
},
"node_modules/tsconfig-paths/node_modules/strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -6787,7 +6775,7 @@
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.2.1",
"json5": "^2.2.3",
"semver": "^6.3.0"
},
"dependencies": {
@@ -7221,15 +7209,15 @@
"dev": true
},
"@eslint/eslintrc": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz",
"integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
"integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==",
"dev": true,
"requires": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.4.0",
"globals": "^13.15.0",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
@@ -7244,9 +7232,9 @@
"dev": true
},
"@humanwhocodes/config-array": {
"version": "0.11.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
"integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==",
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
"integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
"dev": true,
"requires": {
"@humanwhocodes/object-schema": "^1.2.1",
@@ -7989,9 +7977,9 @@
}
},
"@types/jest": {
"version": "29.2.4",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.4.tgz",
"integrity": "sha512-PipFB04k2qTRPePduVLTRiPzQfvMeLwUN3Z21hsAKaB/W9IIzgB2pizCL466ftJlcyZqnHoC9ZHpxLGl3fS86A==",
"version": "29.2.5",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.5.tgz",
"integrity": "sha512-H2cSxkKgVmqNHXP7TC2L/WUorrZu8ZigyRywfVzv6EyBlxj39n4C00hjXYQWsbwqgElaj/CiAeSRmk5GoaKTgw==",
"dev": true,
"requires": {
"expect": "^29.0.0",
@@ -8010,9 +7998,9 @@
"dev": true
},
"@types/node": {
"version": "18.11.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.12.tgz",
"integrity": "sha512-FgD3NtTAKvyMmD44T07zz2fEf+OKwutgBCEVM8GcvMGVGaDktiLNTDvPwC/LUe3PinMW+X6CuLOF2Ui1mAlSXg=="
"version": "18.11.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
},
"@types/prettier": {
"version": "2.7.1",
@@ -8065,41 +8053,41 @@
}
},
"@typescript-eslint/parser": {
"version": "5.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.0.tgz",
"integrity": "sha512-joNO6zMGUZg+C73vwrKXCd8usnsmOYmgW/w5ZW0pG0RGvqeznjtGDk61EqqTpNrFLUYBW2RSBFrxdAZMqA4OZA==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz",
"integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.46.0",
"@typescript-eslint/types": "5.46.0",
"@typescript-eslint/typescript-estree": "5.46.0",
"@typescript-eslint/scope-manager": "5.48.0",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/typescript-estree": "5.48.0",
"debug": "^4.3.4"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "5.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.46.0.tgz",
"integrity": "sha512-7wWBq9d/GbPiIM6SqPK9tfynNxVbfpihoY5cSFMer19OYUA3l4powA2uv0AV2eAZV6KoAh6lkzxv4PoxOLh1oA==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz",
"integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.46.0",
"@typescript-eslint/visitor-keys": "5.46.0"
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/visitor-keys": "5.48.0"
}
},
"@typescript-eslint/types": {
"version": "5.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.46.0.tgz",
"integrity": "sha512-wHWgQHFB+qh6bu0IAPAJCdeCdI0wwzZnnWThlmHNY01XJ9Z97oKqKOzWYpR2I83QmshhQJl6LDM9TqMiMwJBTw==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz",
"integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "5.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.46.0.tgz",
"integrity": "sha512-kDLNn/tQP+Yp8Ro2dUpyyVV0Ksn2rmpPpB0/3MO874RNmXtypMwSeazjEN/Q6CTp8D7ExXAAekPEcCEB/vtJkw==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz",
"integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.46.0",
"@typescript-eslint/visitor-keys": "5.46.0",
"@typescript-eslint/types": "5.48.0",
"@typescript-eslint/visitor-keys": "5.48.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -8108,12 +8096,12 @@
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.46.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.46.0.tgz",
"integrity": "sha512-E13gBoIXmaNhwjipuvQg1ByqSAu/GbEpP/qzFihugJ+MomtoJtFAJG/+2DRPByf57B863m0/q7Zt16V9ohhANw==",
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz",
"integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.46.0",
"@typescript-eslint/types": "5.48.0",
"eslint-visitor-keys": "^3.3.0"
}
}
@@ -8946,13 +8934,13 @@
"dev": true
},
"eslint": {
"version": "8.29.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz",
"integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==",
"version": "8.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz",
"integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==",
"dev": true,
"requires": {
"@eslint/eslintrc": "^1.3.3",
"@humanwhocodes/config-array": "^0.11.6",
"@eslint/eslintrc": "^1.4.1",
"@humanwhocodes/config-array": "^0.11.8",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.10.0",
@@ -8971,7 +8959,7 @@
"file-entry-cache": "^6.0.1",
"find-up": "^5.0.0",
"glob-parent": "^6.0.2",
"globals": "^13.15.0",
"globals": "^13.19.0",
"grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0",
"import-fresh": "^3.0.0",
@@ -9158,9 +9146,9 @@
}
},
"eslint-plugin-jest": {
"version": "27.1.6",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.1.6.tgz",
"integrity": "sha512-XA7RFLSrlQF9IGtAmhddkUkBuICCTuryfOTfCSWcZHiHb69OilIH05oozH2XA6CEOtztnOd0vgXyvxZodkxGjg==",
"version": "27.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-27.2.0.tgz",
"integrity": "sha512-KGIYtelk4rIhKocxRKUEeX+kJ0ZCab/CiSgS8BMcKD7AY7YxXhlg/d51oF5jq2rOrtuJEDYWRwXD95l6l2vtrA==",
"dev": true,
"requires": {
"@typescript-eslint/utils": "^5.10.0"
@@ -9250,9 +9238,9 @@
"dev": true
},
"espree": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz",
"integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==",
"version": "9.4.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
"integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
"dev": true,
"requires": {
"acorn": "^8.8.0",
@@ -9560,9 +9548,9 @@
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
},
"globals": {
"version": "13.17.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
"integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==",
"version": "13.19.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz",
"integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==",
"dev": true,
"requires": {
"type-fest": "^0.20.2"
@@ -10415,9 +10403,9 @@
"dev": true
},
"json5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true
},
"jsx-ast-utils": {
@@ -11370,7 +11358,7 @@
"bs-logger": "0.x",
"fast-json-stable-stringify": "2.x",
"jest-util": "^29.0.0",
"json5": "^2.2.1",
"json5": "^2.2.3",
"lodash.memoize": "4.x",
"make-error": "1.x",
"semver": "7.x",
@@ -11384,20 +11372,11 @@
"dev": true,
"requires": {
"@types/json5": "^0.0.29",
"json5": "^1.0.1",
"json5": "^2.2.3",
"minimist": "^1.2.6",
"strip-bom": "^3.0.0"
},
"dependencies": {
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+8 -5
View File
@@ -43,19 +43,22 @@
"webpack": "^5.75.0"
},
"devDependencies": {
"@types/jest": "^29.2.4",
"@types/node": "^18.11.12",
"@types/jest": "^29.2.5",
"@types/node": "^18.11.18",
"@types/semver": "^7.3.13",
"@typescript-eslint/parser": "^5.46.0",
"@typescript-eslint/parser": "^5.48.0",
"@vercel/ncc": "^0.36.0",
"eslint": "^8.29.0",
"eslint": "^8.31.0",
"eslint-plugin-github": "^4.6.0",
"eslint-plugin-jest": "^27.1.6",
"eslint-plugin-jest": "^27.2.0",
"jest": "^29.3.1",
"jest-circus": "^29.3.1",
"js-yaml": "^4.1.0",
"prettier": "2.8.1",
"ts-jest": "^29.0.3",
"typescript": "^4.9.4"
},
"overrides": {
"json5": "^2.2.3"
}
}
+23 -3
View File
@@ -20,12 +20,32 @@ export interface Configuration {
export interface Category {
title: string // the title of this category
labels: string[] // labels to associate PRs to this category
labels?: string[] // labels to associate PRs to this category
exclude_labels?: string[] // if an exclude label is detected, the PR will be excluded from this category
exhaustive?: boolean // requires all labels to be present in the PR
rules?: Rule[] // rules to associate PRs to this category
exhaustive?: boolean // requires all labels AND/OR rules to be present in the PR
empty_content?: string // if the category has no matching PRs, this content will be used. If not set, the category will be skipped in the changelog.
}
/**
* Defines the properties of the PullRequestInfo useable in different configurations
*/
export type Property =
| 'title'
| 'branch'
| 'author'
| 'labels'
| 'milestone'
| 'body'
| 'assignees'
| 'requestedReviewers'
| 'approvedReviewers'
| 'status'
export interface Rule extends Regex {
on_property?: Property // retrieve the property to apply the rule on
}
export interface Sort {
order: 'ASC' | 'DESC' // the sorting order
on_property: 'mergedAt' | 'title' // the property to sort on. (mergedAt falls back to createdAt)
@@ -41,7 +61,7 @@ export interface Transformer extends Regex {
}
export interface Extractor extends Transformer {
on_property?: ('title' | 'author' | 'milestone' | 'body' | 'branch')[] | 'title' | 'author' | 'milestone' | 'body' | 'branch' | undefined // retrieve the property to extract the value from
on_property?: Property[] | Property | undefined // retrieve the property to extract the value from
method?: 'replace' | 'match' | undefined // the method to use to extract the value, `match` will not use the `target` property
on_empty?: string | undefined // in case the regex results in an empty string, this value is gonna be used instead (only for label_extractor currently)
}
+10 -8
View File
@@ -1,6 +1,6 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import {parseConfiguration, resolveConfiguration, retrieveRepositoryPath, writeOutput} from './utils'
import {mergeConfiguration, parseConfiguration, resolveConfiguration, retrieveRepositoryPath, writeOutput} from './utils'
import {ReleaseNotesBuilder} from './releaseNotesBuilder'
import {Configuration} from './configuration'
@@ -13,18 +13,20 @@ async function run(): Promise<void> {
const inputPath = core.getInput('path')
const repositoryPath = retrieveRepositoryPath(inputPath)
// read in configuration file if possible
let configuration: Configuration | undefined = undefined
// read in configuration from json if possible
let configJson: Configuration | undefined = undefined
const configurationJson: string = core.getInput('configurationJson', {
trimWhitespace: true
})
if (configurationJson) {
configuration = parseConfiguration(configurationJson)
}
if (!configuration) {
const configurationFile: string = core.getInput('configuration')
configuration = resolveConfiguration(repositoryPath, configurationFile)
configJson = parseConfiguration(configurationJson)
}
// read in the configuration from the file if possible
const configurationFile: string = core.getInput('configuration')
const configFile = resolveConfiguration(repositoryPath, configurationFile)
// merge configs, use default values from DefaultConfig on missing definition
const configuration = mergeConfiguration(configJson, configFile)
// read in repository inputs
const baseUrl = core.getInput('baseUrl')
+17 -1
View File
@@ -2,7 +2,7 @@ import * as core from '@actions/core'
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
import {Unpacked} from './utils'
import moment from 'moment'
import {Sort} from './configuration'
import {Property, Sort} from './configuration'
export interface PullRequestInfo {
number: number
@@ -226,6 +226,22 @@ export function compare(a: PullRequestInfo, b: PullRequestInfo, sort: Sort): num
}
}
/**
* Helper function to retrieve a property from the PullRequestInfo
*/
export function retrieveProperty(pr: PullRequestInfo, property: Property, useCase: string): string {
let value: string | Set<string> | string[] | undefined = pr[property]
if (value === undefined) {
core.warning(`⚠️ the provided property '${property}' for \`${useCase}\` is not valid. Fallback to 'body'`)
value = pr['body']
} else if (value instanceof Set) {
value = Array.from(value).join(',') // join into single string
} else if (Array.isArray(value)) {
value = value.join(',') // join into single string
}
return value
}
// helper function to add a special open label to prs not merged.
function attachSpeciaLabels(status: 'open' | 'merged', labels: Set<string>): Set<string> {
labels.add(`--rcba-${status}`)
+101
View File
@@ -0,0 +1,101 @@
import * as core from '@actions/core'
import {Extractor, Property, Regex, Rule, Transformer} from './configuration'
import {PullRequestInfo, retrieveProperty} from './pullRequests'
/**
* Checks if any of the rules match the given PR
*/
export function matchesRules(rules: Rule[], pr: PullRequestInfo, exhaustive: Boolean): boolean {
const transformers: RegexTransformer[] = rules.map(rule => validateTransformer(rule)).filter(t => t !== null) as RegexTransformer[]
if (exhaustive) {
return transformers.every(transformer => {
return matches(pr, transformer, 'rule')
})
} else {
return transformers.some(transformer => {
return matches(pr, transformer, 'rule')
})
}
}
/**
* Checks if the configured property results in a positive `test` with the regex.
*/
function matches(pr: PullRequestInfo, extractor: RegexTransformer, extractor_usecase: string): boolean {
if (extractor.pattern == null) {
return false
}
if (extractor.onProperty !== undefined && extractor.onProperty.length === 1) {
const prop = extractor.onProperty[0]
const value = retrieveProperty(pr, prop, extractor_usecase)
return extractor.pattern.test(value)
}
return false
}
export function validateTransformer(transformer?: Regex): RegexTransformer | null {
if (transformer === undefined) {
return null
}
try {
let target = undefined
if (transformer.hasOwnProperty('target')) {
target = (transformer as Transformer).target
}
let onProperty = undefined
let method = undefined
let onEmpty = undefined
if (transformer.hasOwnProperty('method')) {
method = (transformer as Extractor).method
onEmpty = (transformer as Extractor).on_empty
onProperty = (transformer as Extractor).on_property
} else if (transformer.hasOwnProperty('on_property')) {
onProperty = (transformer as Extractor).on_property
}
// legacy handling, transform single value input to array
if (!Array.isArray(onProperty)) {
if (onProperty !== undefined) {
onProperty = [onProperty]
}
}
return buildRegex(transformer, target, onProperty, method, onEmpty)
} catch (e) {
core.warning(`⚠️ Failed to validate transformer: ${transformer.pattern}`)
return null
}
}
/**
* Constructs the RegExp, providing the configured Regex and additional values
*/
export function buildRegex(
regex: Regex,
target: string | undefined,
onProperty?: Property[] | undefined,
method?: 'replace' | 'match' | undefined,
onEmpty?: string | undefined
): RegexTransformer | null {
try {
return {
pattern: new RegExp(regex.pattern.replace('\\\\', '\\'), regex.flags ?? 'gu'),
target: target || '',
onProperty,
method,
onEmpty
}
} catch (e) {
core.warning(`⚠️ Bad replacer regex: ${regex.pattern}`)
return null
}
}
export interface RegexTransformer {
pattern: RegExp | null
target: string
onProperty?: Property[]
method?: 'replace' | 'match'
onEmpty?: string
}
+8 -18
View File
@@ -1,6 +1,6 @@
import * as core from '@actions/core'
import {Commits, filterCommits, DiffInfo, DefaultDiffInfo} from './commits'
import {Configuration, DefaultConfiguration} from './configuration'
import {Configuration} from './configuration'
import {PullRequestInfo, PullRequests} from './pullRequests'
import {Octokit} from '@octokit/rest'
import {buildChangelog, replaceEmptyTemplate} from './transform'
@@ -62,7 +62,7 @@ export class ReleaseNotes {
if (mergedPullRequests.length === 0) {
core.warning(`⚠️ No pull requests found`)
return replaceEmptyTemplate(this.options.configuration.empty_template || DefaultConfiguration.empty_template, this.options)
return replaceEmptyTemplate(this.options.configuration.empty_template, this.options)
}
core.startGroup('📦 Build changelog')
@@ -105,7 +105,7 @@ export class ReleaseNotes {
let fromDate = firstCommit.date
const toDate = lastCommit.date
const maxDays = configuration.max_back_track_time_days || DefaultConfiguration.max_back_track_time_days
const maxDays = configuration.max_back_track_time_days
const maxFromDate = toDate.clone().subtract(maxDays, 'days')
if (maxFromDate.isAfter(fromDate)) {
core.info(`⚠️ Adjusted 'fromDate' to go max ${maxDays} back`)
@@ -115,17 +115,11 @@ export class ReleaseNotes {
core.info(`️ Fetching PRs between dates ${fromDate.toISOString()} to ${toDate.toISOString()} for ${owner}/${repo}`)
const pullRequestsApi = new PullRequests(octokit)
const pullRequests = await pullRequestsApi.getBetweenDates(
owner,
repo,
fromDate,
toDate,
configuration.max_pull_requests || DefaultConfiguration.max_pull_requests
)
const pullRequests = await pullRequestsApi.getBetweenDates(owner, repo, fromDate, toDate, configuration.max_pull_requests)
core.info(`️ Retrieved ${pullRequests.length} PRs for ${owner}/${repo} in date range from API`)
const prCommits = filterCommits(commits, configuration.exclude_merge_branches || DefaultConfiguration.exclude_merge_branches)
const prCommits = filterCommits(commits, configuration.exclude_merge_branches)
core.info(`️ Retrieved ${prCommits.length} release commits for ${owner}/${repo}`)
@@ -144,11 +138,7 @@ export class ReleaseNotes {
let allPullRequests = mergedPullRequests
if (includeOpen) {
// retrieve all open pull requests
const openPullRequests = await pullRequestsApi.getOpen(
owner,
repo,
configuration.max_pull_requests || DefaultConfiguration.max_pull_requests
)
const openPullRequests = await pullRequestsApi.getOpen(owner, repo, configuration.max_pull_requests)
core.info(`️ Retrieved ${openPullRequests.length} open PRs for ${owner}/${repo}`)
@@ -159,7 +149,7 @@ export class ReleaseNotes {
}
// retrieve base branches we allow
const baseBranches = configuration.base_branches || DefaultConfiguration.base_branches
const baseBranches = configuration.base_branches
const baseBranchPatterns = baseBranches.map(baseBranch => {
return new RegExp(baseBranch.replace('\\\\', '\\'), 'gu')
})
@@ -216,7 +206,7 @@ export class ReleaseNotes {
return [diffInfo, []]
}
const prCommits = filterCommits(commits, configuration.exclude_merge_branches || DefaultConfiguration.exclude_merge_branches)
const prCommits = filterCommits(commits, configuration.exclude_merge_branches)
core.info(`️ Retrieved ${prCommits.length} commits for ${owner}/${repo}`)
+3 -3
View File
@@ -1,5 +1,5 @@
import * as core from '@actions/core'
import {Configuration, DefaultConfiguration} from './configuration'
import {Configuration} from './configuration'
import {Octokit} from '@octokit/rest'
import {ReleaseNotes} from './releaseNotes'
import {Tags} from './tags'
@@ -77,8 +77,8 @@ export class ReleaseNotesBuilder {
this.fromTag,
this.toTag,
this.ignorePreReleases,
this.configuration.max_tags_to_fetch || DefaultConfiguration.max_tags_to_fetch,
this.configuration.tag_resolver || DefaultConfiguration.tag_resolver
this.configuration.max_tags_to_fetch,
this.configuration.tag_resolver
)
let thisTag = tagRange.to
+1 -1
View File
@@ -5,8 +5,8 @@ import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
import {SemVer} from 'semver'
import {TagResolver} from './configuration'
import {createCommandManager} from './gitHelper'
import {RegexTransformer, validateTransformer} from './transform'
import moment from 'moment'
import {RegexTransformer, validateTransformer} from './regexUtils'
export interface TagResult {
from: TagInfo | null
+35 -78
View File
@@ -1,24 +1,17 @@
import * as core from '@actions/core'
import {Category, Configuration, DefaultConfiguration, Extractor, Placeholder, Transformer} from './configuration'
import {CommentInfo, EMPTY_COMMENT_INFO, PullRequestInfo, sortPullRequests} from './pullRequests'
import {Category, Configuration, Placeholder, Property, Transformer} from './configuration'
import {CommentInfo, EMPTY_COMMENT_INFO, PullRequestInfo, retrieveProperty, sortPullRequests} from './pullRequests'
import {ReleaseNotesOptions} from './releaseNotes'
import {DiffInfo} from './commits'
import {createOrSet, haveCommonElements, haveEveryElements} from './utils'
export interface RegexTransformer {
pattern: RegExp | null
target: string
onProperty?: ('title' | 'author' | 'milestone' | 'body' | 'status' | 'branch')[] | undefined
method?: 'replace' | 'match' | undefined
onEmpty?: string | undefined
}
import {matchesRules, RegexTransformer, validateTransformer} from './regexUtils'
const EMPTY_MAP = new Map<string, string>()
export function buildChangelog(diffInfo: DiffInfo, prs: PullRequestInfo[], options: ReleaseNotesOptions): string {
// sort to target order
const config = options.configuration
const sort = config.sort || DefaultConfiguration.sort
const sort = config.sort
prs = sortPullRequests(prs, sort)
core.info(`️ Sorted all pull requests ascending: ${JSON.stringify(sort)}`)
@@ -73,21 +66,15 @@ export function buildChangelog(diffInfo: DiffInfo, prs: PullRequestInfo[], optio
const transformedMap = new Map<PullRequestInfo, string>()
// convert PRs to their text representation
for (const pr of prs) {
transformedMap.set(
pr,
transform(
fillPrTemplate(pr, config.pr_template || DefaultConfiguration.pr_template, placeholders, placeholderPrMap, config),
validatedTransformers
)
)
transformedMap.set(pr, transform(fillPrTemplate(pr, config.pr_template, placeholders, placeholderPrMap, config), validatedTransformers))
}
core.info(`️ Used ${validatedTransformers.length} transformers to adjust message`)
core.info(`✒️ Wrote messages for ${prs.length} pull requests`)
// bring PRs into the order of categories
const categorized = new Map<Category, string[]>()
const categories = config.categories || DefaultConfiguration.categories
const ignoredLabels = config.ignore_labels || DefaultConfiguration.ignore_labels
const categories = config.categories
const ignoredLabels = config.ignore_labels
for (const category of categories) {
categorized.set(category, [])
@@ -114,8 +101,9 @@ export function buildChangelog(diffInfo: DiffInfo, prs: PullRequestInfo[], optio
openPrs.push(body)
}
let matched = false
let matchedOnce = false // in case we matched once at least, the PR can't be uncategorized
for (const [category, pullRequests] of categorized) {
let matched = false // check if we matched within the given category
// check if any exclude label matches
if (category.exclude_labels !== undefined) {
if (
@@ -134,38 +122,46 @@ export function buildChangelog(diffInfo: DiffInfo, prs: PullRequestInfo[], optio
}
}
if (category.exhaustive === true) {
if (
haveEveryElements(
// in case we have exhaustive matching enabled, and have labels and/or rules
// validate for an exhaustive match (e.g. every provided rule applies)
if (category.exhaustive === true && (category.labels !== undefined || category.rules !== undefined)) {
if (category.labels !== undefined) {
matched = haveEveryElements(
category.labels.map(lbl => lbl.toLocaleLowerCase('en')),
pr.labels
)
) {
pullRequests.push(body)
matched = true
}
if (matched && category.rules !== undefined) {
matched = matchesRules(category.rules, pr, true)
}
} else {
if (
haveCommonElements(
// if not exhaustive, do individual matches
if (category.labels !== undefined) {
// check if either any of the labels applies
matched = haveCommonElements(
category.labels.map(lbl => lbl.toLocaleLowerCase('en')),
pr.labels
)
) {
pullRequests.push(body)
matched = true
}
if (!matched && category.rules !== undefined) {
// if no label did apply, check if any rule applies
matched = matchesRules(category.rules, pr, false)
}
}
if (matched) {
pullRequests.push(body) // if matched add the PR to the list
}
matchedOnce = matchedOnce || matched
}
if (!matched) {
if (!matchedOnce) {
// we allow to have pull requests included in an "uncategorized" category
for (const [category, pullRequests] of categorized) {
if (category.labels.length === 0) {
if ((category.labels === undefined || category.labels.length === 0) && category.rules === undefined) {
pullRequests.push(body)
break
}
}
uncategorizedPrs.push(body)
} else {
categorizedPrs.push(body)
@@ -252,7 +248,7 @@ export function buildChangelog(diffInfo: DiffInfo, prs: PullRequestInfo[], optio
placeholderMap.set('COMMITS', diffInfo.commits.toString())
fillAdditionalPlaceholders(options, placeholderMap)
let transformedChangelog = config.template || DefaultConfiguration.template
let transformedChangelog = config.template
transformedChangelog = replacePlaceholders(transformedChangelog, EMPTY_MAP, placeholderMap, placeholders, placeholderPrMap, config)
transformedChangelog = replacePrPlaceholders(transformedChangelog, placeholderPrMap, config)
transformedChangelog = cleanupPrPlaceholders(transformedChangelog, placeholders)
@@ -453,7 +449,7 @@ function transform(filled: string, transformers: RegexTransformer[]): string {
}
function validateTransformers(specifiedTransformers: Transformer[]): RegexTransformer[] {
const transformers = specifiedTransformers || DefaultConfiguration.transformers
const transformers = specifiedTransformers
return transformers
.map(transformer => {
return validateTransformer(transformer)
@@ -464,40 +460,6 @@ function validateTransformers(specifiedTransformers: Transformer[]): RegexTransf
})
}
export function validateTransformer(transformer?: Transformer): RegexTransformer | null {
if (transformer === undefined) {
return null
}
try {
let onProperty = undefined
let method = undefined
let onEmpty = undefined
if (transformer.hasOwnProperty('on_property')) {
onProperty = (transformer as Extractor).on_property
method = (transformer as Extractor).method
onEmpty = (transformer as Extractor).on_empty
}
// legacy handling, transform single value input to array
if (!Array.isArray(onProperty)) {
if (onProperty !== undefined) {
onProperty = [onProperty]
}
}
return {
pattern: new RegExp(transformer.pattern.replace('\\\\', '\\'), transformer.flags ?? 'gu'),
target: transformer.target || '',
onProperty,
method,
onEmpty
}
} 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
@@ -505,16 +467,11 @@ function extractValues(pr: PullRequestInfo, extractor: RegexTransformer, extract
if (extractor.onProperty !== undefined) {
let results: string[] = []
const list: ('title' | 'author' | 'milestone' | 'body' | 'status' | 'branch')[] = extractor.onProperty
const list: Property[] = extractor.onProperty
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < list.length; i++) {
const prop = list[i]
let value: string | undefined = pr[prop]
if (value === undefined) {
core.warning(`⚠️ the provided property '${extractor.onProperty}' for \`${extractor_usecase}\` is not valid`)
value = pr['body']
}
const value = retrieveProperty(pr, prop, extractor_usecase)
const values = extractValuesFromString(value, extractor)
if (values !== null) {
results = results.concat(values)
+28 -4
View File
@@ -2,7 +2,6 @@ import * as core from '@actions/core'
import * as fs from 'fs'
import * as path from 'path'
import {Configuration, DefaultConfiguration} from './configuration'
/**
* Resolves the repository path, relatively to the GITHUB_WORKSPACE
*/
@@ -84,6 +83,31 @@ export function parseConfiguration(config: string): Configuration | undefined {
}
}
/**
* Merges the configurations, will fallback to the DefaultConfiguration value
*/
export function mergeConfiguration(jc?: Configuration, fc?: Configuration): Configuration {
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
}
}
/**
* Checks if a given directory exists
*/
@@ -133,7 +157,7 @@ export function writeOutput(githubWorkspacePath: string, outputFile: string, cha
export type Unpacked<T> = T extends (infer U)[] ? U : T
export function createOrSet<T>(map: Map<String, T[]>, key: string, value: T): void {
export function createOrSet<T>(map: Map<string, T[]>, key: string, value: T): void {
const entry = map.get(key)
if (!entry) {
map.set(key, [value])
@@ -142,10 +166,10 @@ export function createOrSet<T>(map: Map<String, T[]>, key: string, value: T): vo
}
}
export function haveCommonElements(arr1: string[], arr2: Set<string>): Boolean {
export function haveCommonElements(arr1: string[], arr2: Set<string>): boolean {
return arr1.some(item => arr2.has(item))
}
export function haveEveryElements(arr1: string[], arr2: Set<string>): Boolean {
export function haveEveryElements(arr1: string[], arr2: Set<string>): boolean {
return arr1.every(item => arr2.has(item))
}
-9
View File
@@ -1,9 +0,0 @@
export async function wait(milliseconds: number): Promise<string> {
return new Promise(resolve => {
if (isNaN(milliseconds)) {
throw new Error('milliseconds not a number')
}
setTimeout(() => resolve('done!'), milliseconds)
})
}