Merge pull request #50 from mikepenz/feature/dynamic_placeholders

New additional placeholders for `template` and `empty_template`
This commit is contained in:
Mike Penz
2020-10-19 18:13:13 +02:00
committed by GitHub
8 changed files with 168 additions and 47 deletions
+27 -20
View File
@@ -213,7 +213,7 @@ For advanced use cases additional settings can be provided to the action
### PR Template placeholders
Table of supported placeholders allowed to be used in the `template` configuration.
Table of supported placeholders allowed to be used in the `pr_template` configuration.
| **Placeholder** | **Description** |
|------------------|-------------------------------------------------------------|
@@ -230,33 +230,40 @@ Table of supported placeholders allowed to be used in the `template` configurati
### Template placeholders
Table of supported placeholders allowed to be used in the `pr_template` configuration.
Table of supported placeholders allowed to be used in the `template` and `empty_template` (only supports placeholder marked for empty) configuration.
| **Placeholder** | **Description** | **Empty** |
|----------------------------|----------------------------------------------------------------------------------------------------|:---------:|
| `${{CHANGELOG}}` | The contents of the changelog, matching the labels as specified in the categories configuration | |
| `${{UNCATEGORIZED}}` | All pull requests not matching a specified label in categories | |
| `${{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 |
| `${{TO_TAG}}` | Defines until which tag the changelog did consider merged pull requests | x |
| `${{CATEGORIZED_COUNT}}` | The count of PRs which were categorized | |
| `${{UNCATEGORIZED_COUNT}}` | The count of PRs and changes which were not categorized. No label overlapping with category labels | |
| **Placeholder** | **Description** |
|----------------------|-------------------------------------------------------------------------------------------------|
| `${{CHANGELOG}}` | The contents of the changelog, matching the labels as specified in the categories configuration |
| `${{UNCATEGORIZED}}` | All pull requests not matching a specified label in categories |
### Configuration Specification
Table of descriptions for the `configuration.json` options.
| **Input** | **Description** |
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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 |
| **Input** | **Description** |
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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 |
| sort | The sort order of pull requests. [ASC, DESC] |
| 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. Does not support placeholders |
| sort | The sort order of pull requests. [ASC, DESC] |
| 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 |
| 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 |
| max_tags_to_fetch | The maximum amount of tags to load from the API to find the previous tag. Loaded paginated with 100 per page |
| max_pull_requests | The maximum amount of pull requests to load from the API. Loaded paginated with 30 per page |
| max_back_track_time_days | Defines the max amount of days to go back in time per changelog |
| exclude_merge_branches | An array of branches to be ignored from processing as merge commits |
| 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 |
| max_tags_to_fetch | The maximum amount of tags to load from the API to find the previous tag. Loaded paginated with 100 per page |
| max_pull_requests | The maximum amount of pull requests to load from the API. Loaded paginated with 30 per page |
| max_back_track_time_days | Defines the max amount of days to go back in time per changelog |
| exclude_merge_branches | An array of branches to be ignored from processing as merge commits |
## Contribute 🧬
+63 -13
View File
@@ -1,19 +1,6 @@
import { resolveConfiguration } from '../src/utils';
import { ReleaseNotesBuilder } from '../src/releaseNotesBuilder';
// shows how the runner will run a javascript action with env / stdout protocol
/*
test('test runs', () => {
jest.setTimeout(180000);
process.env['INPUT_CONFIGURATION'] = 'configuration.json'
const ip = path.join(__dirname, '..', 'lib', 'main.js')
const options: cp.ExecSyncOptions = {
env: process.env
}
console.log(cp.execSync(`node ${ip}`, options).toString())
})
*/
it('Should match generated changelog (unspecified fromTag)', async () => {
jest.setTimeout(180000)
@@ -38,4 +25,67 @@ it('Should match generated changelog (unspecified fromTag)', async () => {
- PR: #10
`)
})
it('Should use empty placeholder', async () => {
jest.setTimeout(180000)
const configuration = resolveConfiguration('', 'configs/configuration.json')
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
'.',
'mikepenz',
'release-changelog-builder-action',
'v0.0.2',
'v0.0.3',
false,
false,
configuration
)
const changeLog = await releaseNotesBuilder.build()
console.log(changeLog)
expect(changeLog).toStrictEqual(`- no changes`)
})
it('Should fill empty placeholders', async () => {
jest.setTimeout(180000)
const configuration = resolveConfiguration('', 'configs_test/configuration_empty_all_placeholders.json')
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
'.',
'mikepenz',
'release-changelog-builder-action',
'v0.0.2',
'v0.0.3',
false,
false,
configuration
)
const changeLog = await releaseNotesBuilder.build()
console.log(changeLog)
expect(changeLog).toStrictEqual(`mikepenz\nrelease-changelog-builder-action\nv0.0.2\nv0.0.3`)
})
it('Should fill `template` placeholders', async () => {
jest.setTimeout(180000)
const configuration = resolveConfiguration('', 'configs_test/configuration_empty_all_placeholders.json')
const releaseNotesBuilder = new ReleaseNotesBuilder(
null,
'.',
'mikepenz',
'release-changelog-builder-action',
'v0.0.1',
'v0.0.3',
false,
false,
configuration
)
const changeLog = await releaseNotesBuilder.build()
console.log(changeLog)
expect(changeLog).toStrictEqual(`## 🧪 Tests\n\n- [CI] Specify Test Case\n - PR: #10\n\n\n\nmikepenz\nrelease-changelog-builder-action\nv0.0.1\nv0.0.3\n1\n0`)
})
@@ -0,0 +1,4 @@
{
"template": "${{CHANGELOG}}\n${{UNCATEGORIZED}}\n${{OWNER}}\n${{REPO}}\n${{FROM_TAG}}\n${{TO_TAG}}\n${{CATEGORIZED_COUNT}}\n${{UNCATEGORIZED_COUNT}}",
"empty_template": "${{OWNER}}\n${{REPO}}\n${{FROM_TAG}}\n${{TO_TAG}}"
}
Generated Vendored
+26 -7
View File
@@ -590,7 +590,7 @@ class ReleaseNotes {
return null;
}
core.startGroup('📦 Build changelog');
const resultChangelog = transform_1.buildChangelog(mergedPullRequests, configuration);
const resultChangelog = transform_1.buildChangelog(mergedPullRequests, configuration, this.options);
core.endGroup();
return resultChangelog;
});
@@ -708,6 +708,7 @@ const utils_1 = __webpack_require__(918);
const rest_1 = __webpack_require__(5375);
const tags_1 = __webpack_require__(7532);
const releaseNotes_1 = __webpack_require__(5882);
const transform_1 = __webpack_require__(1644);
class ReleaseNotesBuilder {
constructor(token, repositoryPath, owner, repo, fromTag, toTag, failOnError, ignorePreReleases, configuration) {
this.token = token;
@@ -781,17 +782,18 @@ class ReleaseNotesBuilder {
core.debug(`fromTag resolved via previousTag as: ${previousTag.name}`);
core.endGroup();
}
const releaseNotes = new releaseNotes_1.ReleaseNotes(octokit, {
const options = {
owner: this.owner,
repo: this.repo,
fromTag: this.fromTag,
toTag: this.toTag,
failOnError: this.failOnError,
configuration: this.configuration
});
};
const releaseNotes = new releaseNotes_1.ReleaseNotes(octokit, options);
return ((yield releaseNotes.pull()) ||
this.configuration.empty_template ||
configuration_1.DefaultConfiguration.empty_template);
transform_1.fillAdditionalPlaceholders(this.configuration.empty_template ||
configuration_1.DefaultConfiguration.empty_template, options));
});
}
}
@@ -976,11 +978,11 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.buildChangelog = void 0;
exports.fillAdditionalPlaceholders = exports.buildChangelog = void 0;
const pullRequests_1 = __webpack_require__(4217);
const core = __importStar(__webpack_require__(2186));
const configuration_1 = __webpack_require__(5527);
function buildChangelog(prs, config) {
function buildChangelog(prs, config, options) {
// sort to target order
const sort = config.sort || configuration_1.DefaultConfiguration.sort;
const sortAsc = sort.toUpperCase() === 'ASC';
@@ -1000,6 +1002,7 @@ function buildChangelog(prs, config) {
for (const category of categories) {
categorized.set(category, []);
}
const categorizedPrs = [];
const uncategorized = [];
// bring elements in order
for (const [pr, body] of transformedMap) {
@@ -1013,6 +1016,9 @@ function buildChangelog(prs, config) {
if (!matched) {
uncategorized.push(body);
}
else {
categorizedPrs.push(body);
}
}
core.info(`️ Ordered all pull requests into ${categories.length} categories`);
// construct final changelog
@@ -1037,10 +1043,23 @@ function buildChangelog(prs, config) {
let transformedChangelog = config.template || configuration_1.DefaultConfiguration.template;
transformedChangelog = transformedChangelog.replace('${{CHANGELOG}}', changelog);
transformedChangelog = transformedChangelog.replace('${{UNCATEGORIZED}}', changelogUncategorized);
// fill other placeholders
transformedChangelog = transformedChangelog.replace('${{CATEGORIZED_COUNT}}', categorizedPrs.length.toString());
transformedChangelog = transformedChangelog.replace('${{UNCATEGORIZED_COUNT}}', uncategorized.length.toString());
transformedChangelog = fillAdditionalPlaceholders(transformedChangelog, options);
core.info(`️ Filled template`);
return transformedChangelog;
}
exports.buildChangelog = buildChangelog;
function fillAdditionalPlaceholders(text, options) {
let transformed = text;
transformed = transformed.replace('${{OWNER}}', options.owner);
transformed = transformed.replace('${{REPO}}', options.repo);
transformed = transformed.replace('${{FROM_TAG}}', options.fromTag);
transformed = transformed.replace('${{TO_TAG}}', options.toTag);
return transformed;
}
exports.fillAdditionalPlaceholders = fillAdditionalPlaceholders;
function haveCommonElements(arr1, arr2) {
return arr1.some(item => arr2.includes(item));
}
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -31,7 +31,11 @@ export class ReleaseNotes {
}
core.startGroup('📦 Build changelog')
const resultChangelog = buildChangelog(mergedPullRequests, configuration)
const resultChangelog = buildChangelog(
mergedPullRequests,
configuration,
this.options
)
core.endGroup()
return resultChangelog
}
+9 -4
View File
@@ -6,6 +6,7 @@ import {failOrError} from './utils'
import {Octokit} from '@octokit/rest'
import {Tags} from './tags'
import {ReleaseNotes} from './releaseNotes'
import {fillAdditionalPlaceholders} from './transform'
export class ReleaseNotesBuilder {
constructor(
@@ -96,19 +97,23 @@ export class ReleaseNotesBuilder {
core.endGroup()
}
const releaseNotes = new ReleaseNotes(octokit, {
const options = {
owner: this.owner,
repo: this.repo,
fromTag: this.fromTag,
toTag: this.toTag,
failOnError: this.failOnError,
configuration: this.configuration
})
}
const releaseNotes = new ReleaseNotes(octokit, options)
return (
(await releaseNotes.pull()) ||
this.configuration.empty_template ||
DefaultConfiguration.empty_template
fillAdditionalPlaceholders(
this.configuration.empty_template ||
DefaultConfiguration.empty_template,
options
)
)
}
}
+33 -1
View File
@@ -1,5 +1,6 @@
import {PullRequestInfo, sortPullRequests} from './pullRequests'
import * as core from '@actions/core'
import {ReleaseNotesOptions} from './releaseNotes'
import {
Category,
Configuration,
@@ -9,7 +10,8 @@ import {
export function buildChangelog(
prs: PullRequestInfo[],
config: Configuration
config: Configuration,
options: ReleaseNotesOptions
): string {
// sort to target order
const sort = config.sort || DefaultConfiguration.sort
@@ -43,6 +45,7 @@ export function buildChangelog(
for (const category of categories) {
categorized.set(category, [])
}
const categorizedPrs: string[] = []
const uncategorized: string[] = []
// bring elements in order
@@ -58,6 +61,8 @@ export function buildChangelog(
if (!matched) {
uncategorized.push(body)
} else {
categorizedPrs.push(body)
}
}
core.info(`️ Ordered all pull requests into ${categories.length} categories`)
@@ -96,10 +101,37 @@ export function buildChangelog(
'${{UNCATEGORIZED}}',
changelogUncategorized
)
// fill other placeholders
transformedChangelog = transformedChangelog.replace(
'${{CATEGORIZED_COUNT}}',
categorizedPrs.length.toString()
)
transformedChangelog = transformedChangelog.replace(
'${{UNCATEGORIZED_COUNT}}',
uncategorized.length.toString()
)
transformedChangelog = fillAdditionalPlaceholders(
transformedChangelog,
options
)
core.info(`️ Filled template`)
return transformedChangelog
}
export function fillAdditionalPlaceholders(
text: string,
options: ReleaseNotesOptions
): string {
let transformed = text
transformed = transformed.replace('${{OWNER}}', options.owner)
transformed = transformed.replace('${{REPO}}', options.repo)
transformed = transformed.replace('${{FROM_TAG}}', options.fromTag)
transformed = transformed.replace('${{TO_TAG}}', options.toTag)
return transformed
}
function haveCommonElements(arr1: string[], arr2: string[]): Boolean {
return arr1.some(item => arr2.includes(item))
}