Merge pull request #192 from mikepenz/feature/enhanced_commit_mode

Introduce API to extract labels from PR body | commit message
This commit is contained in:
Mike Penz
2021-02-19 15:06:30 +01:00
committed by GitHub
7 changed files with 143 additions and 29 deletions
+24 -12
View File
@@ -38,6 +38,8 @@
- Super simple integration
- ...even on huge repositories with hundreds of tags
- Parallel releases support
- Rich changelogs based on PRs
- Alternative commit based mode
- Blazingly fast execution
- Supports any git project
- Highly flexible configuration
@@ -160,6 +162,12 @@ This configuration is a `.json` file in the following format.
"template": "${{CHANGELOG}}\n\n<details>\n<summary>Uncategorized</summary>\n\n${{UNCATEGORIZED}}\n</details>",
"pr_template": "- ${{TITLE}}\n - PR: #${{NUMBER}}",
"empty_template": "- no changes",
"label_extractor": [
{
"pattern": "(.) (.+)",
"target": "$1"
}
],
"transformers": [
{
"pattern": "[\\-\\*] (\\[(...|TEST|CI|SKIP)\\])( )?(.+?)\n(.+?[\\-\\*] )(.+)",
@@ -207,18 +215,19 @@ For advanced use cases additional settings can be provided to the action
💡 All input values are optional. It is only required to provide the `token` either via the input, or as `env` variable.
| **Input** | **Description** |
|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `configuration` | Relative path, to the `configuration.json` file, providing additional configurations |
| `outputFile` | Optional relative path to a file to store the resulting changelog in. |
| `owner` | The owner of the repository to generate the changelog for |
| `repo` | Name of the repository we want to process |
| `fromTag` | Defines the 'start' from where the changelog will consider merged pull requests |
| `toTag` | Defines until which tag the changelog will consider merged pull requests |
| `path` | Allows to specify an alternative sub directory, to use as base |
| `token` | Alternative config to specify token. You should prefer `env.GITHUB_TOKEN` instead though |
| `ignorePreReleases` | Allows to ignore pre-releases for changelog generation (E.g. for 1.0.1... 1.0.0-rc02 <- ignore, 1.0.0 <- pick). Only used if `fromTag` was not specified. Default: false |
| `failOnError` | Defines if the action will result in a build failure if problems occurred. Default: false |
| **Input** | **Description** |
|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `configuration` | Relative path, to the `configuration.json` file, providing additional configurations |
| `outputFile` | Optional relative path to a file to store the resulting changelog in. |
| `owner` | The owner of the repository to generate the changelog for |
| `repo` | Name of the repository we want to process |
| `fromTag` | Defines the 'start' from where the changelog will consider merged pull requests |
| `toTag` | Defines until which tag the changelog will consider merged pull requests |
| `path` | Allows to specify an alternative sub directory, to use as base |
| `token` | Alternative config to specify token. You should prefer `env.GITHUB_TOKEN` instead though |
| `ignorePreReleases` | Allows to ignore pre-releases for changelog generation (E.g. for 1.0.1... 1.0.0-rc02 <- ignore, 1.0.0 <- pick). Only used if `fromTag` was not specified. Default: false |
| `failOnError` | Defines if the action will result in a build failure if problems occurred. Default: false |
| `commitMode` | Special configuration for projects which work without PRs. Uses commit messages as changelog. This mode looses access to information only available for PRs. Default: false |
💡 `${{ secrets.GITHUB_TOKEN }}` only grants rights to the current repository, for other repositories please use a PAT (Personal Access Token).
@@ -271,6 +280,9 @@ Table of descriptions for the `configuration.json` options to configure the resu
| template | Specifies the global template to pick for creating the changelog. See [Template placeholders](#template-placeholders) for possible values |
| pr_template | Defines the per pull request template. See [PR Template placeholders](#pr-template-placeholders) for possible values |
| empty_template | Template to pick if no changes are detected. See [Template placeholders](#template-placeholders) for possible values |
| label_extractor | An array of `transform` specifications, offering a flexible API to extract additinal labels from the body of a PR (in case of commit mode, from the commit message). |
| label_extractor.pattern | A `regex` pattern, extracting values of the change message. |
| label_extractor.target | The result pattern. The result text will be used as label. If empty, no label is created |
| 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 |
| transformer.pattern | A `regex` pattern, extracting values of the change message. |
| transformer.target | The result pattern, the regex groups will be filled into. Allows for full transformation of a pull request message. Including potentially specified texts |
+26 -1
View File
@@ -149,7 +149,6 @@ it('Uncategorized category', async () => {
})
it('Verify commit based changelog', async () => {
const configuration = resolveConfiguration(
'',
@@ -174,3 +173,29 @@ it('Verify commit based changelog', async () => {
`## 📦 Uncategorized\n\n- - introduce proper approach to retrieve tag before a given tag\n\n- - configure test case\n\n- Merge pull request #10 from mikepenz/feature/specify_test\n\n\n\n\nUncategorized:\n- - introduce proper approach to retrieve tag before a given tag\n\n- - configure test case\n\n- Merge pull request #10 from mikepenz/feature/specify_test\n\n\n\nIgnored:\n\n\n3\n0`
)
})
it('Verify commit based changelog, with emoji categorisation', async () => {
const configuration = resolveConfiguration(
'',
'configs_test/configuration_commits_emoji.json'
)
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
'.',
'theapache64',
'stackzy',
'bd3242a6b6eadb24744c478e112c4628e89609c2',
'17a9e4dfaedcabe6a6eff2754bebb715e1c58ec4',
false,
false,
true,
configuration
)
const changeLog = await releaseNotesBuilder.build()
console.log(changeLog)
expect(changeLog).toStrictEqual(
`## 🚀 Features\n\n- add dynamic merging\n- add auto-cleaning\n- add built-in adb support\n- add adb fallback (thanks to @mikepenz ;))\n- add install note\n- add @mikepenz to credits\n\n## 🐛 Fixes\n\n- fix dynamic lib replacement\n- fix apostrophe issue with app name\n- fix java.util.logger error\n\n## 💬 Other\n\n- update screenshot with truecaller stack\n\n`
)
})
@@ -0,0 +1,37 @@
{
"categories": [
{
"title": "## 🚀 Features",
"labels": ["🚀", "🌟"]
},
{
"title": "## 🐛 Fixes",
"labels": ["🐛"]
},
{
"title": "## 🧪 Tests",
"labels": ["🧪"]
},
{
"title": "## 💬 Other",
"labels": ["💬", "📖", "🚨"]
},
{
"title": "## 📦 Dependencies",
"labels": ["dependencies"]
}
],
"pr_template": "${{TITLE}}",
"label_extractor": [
{
"pattern": "(.) (.+)",
"target": "$1"
}
],
"transformers": [
{
"pattern": "(.) (.+)",
"target": "- $2"
}
]
}
Generated Vendored
+39 -14
View File
@@ -146,6 +146,7 @@ exports.DefaultConfiguration = {
}
],
ignore_labels: ['ignore'],
label_extractor: [],
transformers: [],
tag_resolver: {
// defines the logic on how to resolve the previous tag, only relevant if `fromTag` is not specified
@@ -1063,6 +1064,18 @@ function buildChangelog(prs, config, options) {
const sortAsc = sort.toUpperCase() === 'ASC';
prs = pullRequests_1.sortPullRequests(prs, sortAsc);
core.info(`️ Sorted all pull requests ascending: ${sort}`);
// extract additional labels from the commit message
const labelExtractors = validateTransfomers(config.label_extractor);
for (const extractor of labelExtractors) {
if (extractor.pattern != null) {
for (const pr of prs) {
const label = pr.body.replace(extractor.pattern, extractor.target);
if (label !== "") {
pr.labels.push(label);
}
}
}
}
const validatedTransformers = validateTransfomers(config.transformers);
const transformedMap = new Map();
// convert PRs to their text representation
@@ -1191,7 +1204,7 @@ function validateTransfomers(specifiedTransformers) {
.map(transformer => {
try {
return {
pattern: new RegExp(transformer.pattern.replace('\\\\', '\\'), 'g'),
pattern: new RegExp(transformer.pattern.replace('\\\\', '\\'), 'gu'),
target: transformer.target
};
}
@@ -4825,12 +4838,16 @@ const Endpoints = {
update: ["PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}"]
},
codeScanning: {
deleteAnalysis: ["DELETE /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}{?confirm_delete}"],
getAlert: ["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}", {}, {
renamedParameters: {
alert_id: "alert_number"
}
}],
getAnalysis: ["GET /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}"],
getSarif: ["GET /repos/{owner}/{repo}/code-scanning/sarifs/{sarif_id}"],
listAlertsForRepo: ["GET /repos/{owner}/{repo}/code-scanning/alerts"],
listAlertsInstances: ["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances"],
listRecentAnalyses: ["GET /repos/{owner}/{repo}/code-scanning/analyses"],
updateAlert: ["PATCH /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}"],
uploadSarif: ["POST /repos/{owner}/{repo}/code-scanning/sarifs"]
@@ -5103,6 +5120,25 @@ const Endpoints = {
updateWebhook: ["PATCH /orgs/{org}/hooks/{hook_id}"],
updateWebhookConfigForOrg: ["PATCH /orgs/{org}/hooks/{hook_id}/config"]
},
packages: {
deletePackageForAuthenticatedUser: ["DELETE /user/packages/{package_type}/{package_name}"],
deletePackageForOrg: ["DELETE /orgs/{org}/packages/{package_type}/{package_name}"],
deletePackageVersionForAuthenticatedUser: ["DELETE /user/packages/{package_type}/{package_name}/versions/{package_version_id}"],
deletePackageVersionForOrg: ["DELETE /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"],
getAllPackageVersionsForAPackageOwnedByAnOrg: ["GET /orgs/{org}/packages/{package_type}/{package_name}/versions"],
getAllPackageVersionsForAPackageOwnedByTheAuthenticatedUser: ["GET /user/packages/{package_type}/{package_name}/versions"],
getAllPackageVersionsForPackageOwnedByUser: ["GET /users/{username}/packages/{package_type}/{package_name}/versions"],
getPackageForAuthenticatedUser: ["GET /user/packages/{package_type}/{package_name}"],
getPackageForOrganization: ["GET /orgs/{org}/packages/{package_type}/{package_name}"],
getPackageForUser: ["GET /users/{username}/packages/{package_type}/{package_name}"],
getPackageVersionForAuthenticatedUser: ["GET /user/packages/{package_type}/{package_name}/versions/{package_version_id}"],
getPackageVersionForOrganization: ["GET /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"],
getPackageVersionForUser: ["GET /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"],
restorePackageForAuthenticatedUser: ["POST /user/packages/{package_type}/{package_name}/restore"],
restorePackageForOrg: ["POST /orgs/{org}/packages/{package_type}/{package_name}/restore"],
restorePackageVersionForAuthenticatedUser: ["POST /user/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"],
restorePackageVersionForOrg: ["POST /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"]
},
projects: {
addCollaborator: ["PUT /projects/{project_id}/collaborators/{username}", {
mediaType: {
@@ -5704,7 +5740,7 @@ const Endpoints = {
}
};
const VERSION = "4.10.3";
const VERSION = "4.12.0";
function endpointsToMethods(octokit, endpointsMap) {
const newMethods = {};
@@ -5787,17 +5823,6 @@ function decorate(octokit, scope, methodName, defaults, decorations) {
return Object.assign(withDecorations, requestWithDefaults);
}
/**
* This plugin is a 1:1 copy of internal @octokit/rest plugins. The primary
* goal is to rebuild @octokit/rest on top of @octokit/core. Once that is
* done, we will remove the registerEndpoints methods and return the methods
* directly as with the other plugins. At that point we will also remove the
* legacy workarounds and deprecations.
*
* See the plan at
* https://github.com/octokit/plugin-rest-endpoint-methods.js/pull/1
*/
function restEndpointMethods(octokit) {
return endpointsToMethods(octokit, Endpoints);
}
@@ -6041,7 +6066,7 @@ var pluginRequestLog = __nccwpck_require__(8883);
var pluginPaginateRest = __nccwpck_require__(4193);
var pluginRestEndpointMethods = __nccwpck_require__(3044);
const VERSION = "18.1.1";
const VERSION = "18.2.0";
const Octokit = core.Octokit.plugin(pluginRequestLog.requestLog, pluginRestEndpointMethods.restEndpointMethods, pluginPaginateRest.paginateRest).defaults({
userAgent: `octokit-rest.js/${VERSION}`
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -9,6 +9,7 @@ export interface Configuration {
empty_template: string
categories: Category[]
ignore_labels: string[]
label_extractor: Transformer[]
transformers: Transformer[]
tag_resolver: TagResolver
}
@@ -51,6 +52,7 @@ export const DefaultConfiguration: Configuration = {
}
], // the categories to support for the ordering
ignore_labels: ['ignore'], // list of lables being ignored from the changelog
label_extractor: [], // extracts additional labels from the commit message given a regex
transformers: [], // transformers to apply on the PR description according to the `pr_template`
tag_resolver: {
// defines the logic on how to resolve the previous tag, only relevant if `fromTag` is not specified
+14 -1
View File
@@ -19,6 +19,19 @@ export function buildChangelog(
prs = sortPullRequests(prs, sortAsc)
core.info(`️ Sorted all pull requests ascending: ${sort}`)
// extract additional labels from the commit message
const labelExtractors = validateTransfomers(config.label_extractor)
for (const extractor of labelExtractors) {
if (extractor.pattern != null) {
for (const pr of prs) {
const label = pr.body.replace(extractor.pattern, extractor.target)
if (label !== '') {
pr.labels.push(label)
}
}
}
}
const validatedTransformers = validateTransfomers(config.transformers)
const transformedMap = new Map<PullRequestInfo, string>()
// convert PRs to their text representation
@@ -210,7 +223,7 @@ function validateTransfomers(
.map(transformer => {
try {
return {
pattern: new RegExp(transformer.pattern.replace('\\\\', '\\'), 'g'),
pattern: new RegExp(transformer.pattern.replace('\\\\', '\\'), 'gu'),
target: transformer.target
}
} catch (e) {