Merge pull request #50 from mikepenz/feature/dynamic_placeholders
New additional placeholders for `template` and `empty_template`
This commit is contained in:
@@ -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 🧬
|
||||
|
||||
|
||||
@@ -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}}"
|
||||
}
|
||||
+26
-7
@@ -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));
|
||||
}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+5
-1
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user