@@ -61,7 +61,9 @@ Specify the action as part of your GitHub actions workflow:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
```
|
```
|
||||||
|
|
||||||
By default the action will try to automatically retrieve the `tag` from the current commit and automatically resolve the `tag` before. Read more about this here.
|
By default the action will try to automatically retrieve the `tag` from the current commit and automatically resolve the `tag` before. Automatic previous tag resolving is done using `semver`.
|
||||||
|
|
||||||
|
If you require a different versioning scheme please open an issue [issue](https://github.com/mikepenz/release-changelog-builder-action/issues). Alternative you can always specifically supply the `fromTag` via the [configuration](#advanced-workflow-specification).
|
||||||
|
|
||||||
### Action outputs
|
### Action outputs
|
||||||
|
|
||||||
@@ -213,7 +215,7 @@ For advanced use cases additional settings can be provided to the action
|
|||||||
|
|
||||||
### PR Template placeholders
|
### 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** |
|
| **Placeholder** | **Description** |
|
||||||
|------------------|-------------------------------------------------------------|
|
|------------------|-------------------------------------------------------------|
|
||||||
@@ -230,33 +232,40 @@ Table of supported placeholders allowed to be used in the `template` configurati
|
|||||||
|
|
||||||
### Template placeholders
|
### 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
|
### Configuration Specification
|
||||||
|
|
||||||
Table of descriptions for the `configuration.json` options.
|
Table of descriptions for the `configuration.json` options.
|
||||||
|
|
||||||
| **Input** | **Description** |
|
| **Input** | **Description** |
|
||||||
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| categories | An array of `category` specifications, offering a flexible way to group changes into categories |
|
| 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.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 |
|
||||||
| sort | The sort order of pull requests. [ASC, DESC] |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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.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 |
|
| 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_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_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 |
|
| 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 |
|
| exclude_merge_branches | An array of branches to be ignored from processing as merge commits |
|
||||||
|
|
||||||
## Contribute 🧬
|
## Contribute 🧬
|
||||||
|
|
||||||
|
|||||||
+25
-143
@@ -1,152 +1,34 @@
|
|||||||
import {ReleaseNotes} from '../src/releaseNotes'
|
import * as path from 'path';
|
||||||
import { resolveConfiguration } from '../src/utils';
|
import * as process from 'process'
|
||||||
|
import * as cp from 'child_process'
|
||||||
|
|
||||||
// shows how the runner will run a javascript action with env / stdout protocol
|
test('missing values should result in failure', () => {
|
||||||
/*
|
process.env['GITHUB_WORKSPACE'] = '.'
|
||||||
test('test runs', () => {
|
|
||||||
jest.setTimeout(180000);
|
|
||||||
|
|
||||||
process.env['INPUT_CONFIGURATION'] = 'configuration.json'
|
process.env['INPUT_CONFIGURATION'] = 'configuration.json'
|
||||||
const ip = path.join(__dirname, '..', 'lib', 'main.js')
|
const ip = path.join(__dirname, '..', 'lib', 'main.js')
|
||||||
const options: cp.ExecSyncOptions = {
|
const options: cp.ExecSyncOptions = {
|
||||||
env: process.env
|
env: process.env
|
||||||
}
|
}
|
||||||
console.log(cp.execSync(`node ${ip}`, options).toString())
|
try {
|
||||||
})
|
cp.execSync(`node ${ip}`, options).toString()
|
||||||
*/
|
fail("Should not succeed, because values miss")
|
||||||
it('Should have empty changelog (tags)', async () => {
|
} catch (error) {
|
||||||
jest.setTimeout(180000)
|
console.log(`correctly failed due to: ${error}`)
|
||||||
|
}
|
||||||
const configuration = resolveConfiguration('', 'configs/configuration.json')
|
|
||||||
const releaseNotes = new ReleaseNotes({
|
|
||||||
owner: 'mikepenz',
|
|
||||||
repo: 'release-changelog-builder-action',
|
|
||||||
fromTag: 'v0.0.1',
|
|
||||||
toTag: 'v0.0.2',
|
|
||||||
ignorePreReleases: false,
|
|
||||||
failOnError: false,
|
|
||||||
configuration: configuration
|
|
||||||
})
|
|
||||||
|
|
||||||
const changeLog = await releaseNotes.pull()
|
|
||||||
console.log(changeLog)
|
|
||||||
expect(changeLog).toStrictEqual(null)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should match generated changelog (tags)', async () => {
|
test('missing values should result in failure', () => {
|
||||||
jest.setTimeout(180000)
|
process.env['GITHUB_WORKSPACE'] = '.'
|
||||||
|
process.env['INPUT_CONFIGURATION'] = 'configuration.json'
|
||||||
const configuration = resolveConfiguration('', 'configs/configuration.json')
|
process.env['INPUT_OWNER'] = 'mikepenz'
|
||||||
const releaseNotes = new ReleaseNotes({
|
process.env['INPUT_REPO'] = 'release-changelog-builder-action'
|
||||||
owner: 'mikepenz',
|
process.env['INPUT_FROMTAG'] = 'v0.3.0'
|
||||||
repo: 'release-changelog-builder-action',
|
process.env['INPUT_TOTAG'] = 'v0.5.0'
|
||||||
fromTag: 'v0.0.1',
|
|
||||||
toTag: 'v0.0.3',
|
const ip = path.join(__dirname, '..', 'lib', 'main.js')
|
||||||
ignorePreReleases: false,
|
const options: cp.ExecSyncOptions = {
|
||||||
failOnError: false,
|
env: process.env
|
||||||
configuration: configuration
|
}
|
||||||
})
|
const result = cp.execSync(`node ${ip}`, options).toString()
|
||||||
|
// should succeed
|
||||||
const changeLog = await releaseNotes.pull()
|
|
||||||
console.log(changeLog)
|
|
||||||
expect(changeLog).toStrictEqual(`## 🧪 Tests
|
|
||||||
|
|
||||||
- [CI] Specify Test Case
|
|
||||||
- PR: #10
|
|
||||||
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should match generated changelog (unspecified fromTag)', async () => {
|
|
||||||
jest.setTimeout(180000)
|
|
||||||
|
|
||||||
const configuration = resolveConfiguration('', 'configs/configuration.json')
|
|
||||||
const releaseNotes = new ReleaseNotes({
|
|
||||||
owner: 'mikepenz',
|
|
||||||
repo: 'release-changelog-builder-action',
|
|
||||||
fromTag: null,
|
|
||||||
toTag: 'v0.0.3',
|
|
||||||
ignorePreReleases: false,
|
|
||||||
failOnError: false,
|
|
||||||
configuration: configuration
|
|
||||||
})
|
|
||||||
|
|
||||||
const changeLog = await releaseNotes.pull()
|
|
||||||
console.log(changeLog)
|
|
||||||
expect(changeLog).toStrictEqual(`## 🧪 Tests
|
|
||||||
|
|
||||||
- [CI] Specify Test Case
|
|
||||||
- PR: #10
|
|
||||||
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should match generated changelog (refs)', async () => {
|
|
||||||
jest.setTimeout(180000)
|
|
||||||
|
|
||||||
const configuration = resolveConfiguration('', 'configs_test/configuration_all_placeholders.json')
|
|
||||||
const releaseNotes = new ReleaseNotes({
|
|
||||||
owner: 'mikepenz',
|
|
||||||
repo: 'release-changelog-builder-action',
|
|
||||||
fromTag: '5ec7a2d86fe9f43fdd38d5e254a1117c8a51b4c3',
|
|
||||||
toTag: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa',
|
|
||||||
ignorePreReleases: false,
|
|
||||||
failOnError: false,
|
|
||||||
configuration: configuration
|
|
||||||
})
|
|
||||||
|
|
||||||
const changeLog = await releaseNotes.pull()
|
|
||||||
console.log(changeLog)
|
|
||||||
expect(changeLog).toStrictEqual(`## 🧪 Tests
|
|
||||||
|
|
||||||
[CI] Specify Test Case
|
|
||||||
10
|
|
||||||
https://github.com/mikepenz/release-changelog-builder-action/pull/10
|
|
||||||
2020-10-16T13:59:36.000Z
|
|
||||||
mikepenz
|
|
||||||
test
|
|
||||||
1.0.0
|
|
||||||
- specify test case
|
|
||||||
mikepenz, nhoelzl
|
|
||||||
nhoelzl
|
|
||||||
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should match ordered ASC', async () => {
|
|
||||||
jest.setTimeout(180000)
|
|
||||||
|
|
||||||
const configuration = resolveConfiguration('', 'configs_test/configuration_asc.json')
|
|
||||||
const releaseNotes = new ReleaseNotes({
|
|
||||||
owner: 'mikepenz',
|
|
||||||
repo: 'release-changelog-builder-action',
|
|
||||||
fromTag: 'v0.3.0',
|
|
||||||
toTag: 'v0.5.0',
|
|
||||||
ignorePreReleases: false,
|
|
||||||
failOnError: false,
|
|
||||||
configuration: configuration
|
|
||||||
})
|
|
||||||
|
|
||||||
const changeLog = await releaseNotes.pull()
|
|
||||||
console.log(changeLog)
|
|
||||||
expect(changeLog).toStrictEqual(`## 🚀 Features\n\n22\n24\n25\n26\n28\n\n## 🐛 Fixes\n\n23\n\n`)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Should match ordered DESC', async () => {
|
|
||||||
jest.setTimeout(180000)
|
|
||||||
|
|
||||||
const configuration = resolveConfiguration('', 'configs_test/configuration_desc.json')
|
|
||||||
const releaseNotes = new ReleaseNotes({
|
|
||||||
owner: 'mikepenz',
|
|
||||||
repo: 'release-changelog-builder-action',
|
|
||||||
fromTag: 'v0.3.0',
|
|
||||||
toTag: 'v0.5.0',
|
|
||||||
ignorePreReleases: false,
|
|
||||||
failOnError: false,
|
|
||||||
configuration: configuration
|
|
||||||
})
|
|
||||||
|
|
||||||
const changeLog = await releaseNotes.pull()
|
|
||||||
console.log(changeLog)
|
|
||||||
expect(changeLog).toStrictEqual(`## 🚀 Features\n\n28\n26\n25\n24\n22\n\n## 🐛 Fixes\n\n23\n\n`)
|
|
||||||
})
|
})
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import { ReleaseNotes } from '../src/releaseNotes'
|
||||||
|
import { resolveConfiguration } from '../src/utils';
|
||||||
|
import { Octokit } from '@octokit/rest';
|
||||||
|
|
||||||
|
// load octokit instance
|
||||||
|
const octokit = new Octokit({
|
||||||
|
auth: `token ${process.env.GITHUB_TOKEN}`
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it('Should have empty changelog (tags)', async () => {
|
||||||
|
jest.setTimeout(180000)
|
||||||
|
|
||||||
|
const configuration = resolveConfiguration('', 'configs/configuration.json')
|
||||||
|
const releaseNotes = new ReleaseNotes(octokit, {
|
||||||
|
owner: 'mikepenz',
|
||||||
|
repo: 'release-changelog-builder-action',
|
||||||
|
fromTag: 'v0.0.1',
|
||||||
|
toTag: 'v0.0.2',
|
||||||
|
failOnError: false,
|
||||||
|
configuration: configuration
|
||||||
|
})
|
||||||
|
|
||||||
|
const changeLog = await releaseNotes.pull()
|
||||||
|
console.log(changeLog)
|
||||||
|
expect(changeLog).toStrictEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should match generated changelog (tags)', async () => {
|
||||||
|
jest.setTimeout(180000)
|
||||||
|
|
||||||
|
const configuration = resolveConfiguration('', 'configs/configuration.json')
|
||||||
|
const releaseNotes = new ReleaseNotes(octokit, {
|
||||||
|
owner: 'mikepenz',
|
||||||
|
repo: 'release-changelog-builder-action',
|
||||||
|
fromTag: 'v0.0.1',
|
||||||
|
toTag: 'v0.0.3',
|
||||||
|
failOnError: false,
|
||||||
|
configuration: configuration
|
||||||
|
})
|
||||||
|
|
||||||
|
const changeLog = await releaseNotes.pull()
|
||||||
|
console.log(changeLog)
|
||||||
|
expect(changeLog).toStrictEqual(`## 🧪 Tests
|
||||||
|
|
||||||
|
- [CI] Specify Test Case
|
||||||
|
- PR: #10
|
||||||
|
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should match generated changelog (refs)', async () => {
|
||||||
|
jest.setTimeout(180000)
|
||||||
|
|
||||||
|
const configuration = resolveConfiguration('', 'configs_test/configuration_all_placeholders.json')
|
||||||
|
const releaseNotes = new ReleaseNotes(octokit, {
|
||||||
|
owner: 'mikepenz',
|
||||||
|
repo: 'release-changelog-builder-action',
|
||||||
|
fromTag: '5ec7a2d86fe9f43fdd38d5e254a1117c8a51b4c3',
|
||||||
|
toTag: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa',
|
||||||
|
failOnError: false,
|
||||||
|
configuration: configuration
|
||||||
|
})
|
||||||
|
|
||||||
|
const changeLog = await releaseNotes.pull()
|
||||||
|
console.log(changeLog)
|
||||||
|
expect(changeLog).toStrictEqual(`## 🧪 Tests
|
||||||
|
|
||||||
|
[CI] Specify Test Case
|
||||||
|
10
|
||||||
|
https://github.com/mikepenz/release-changelog-builder-action/pull/10
|
||||||
|
2020-10-16T13:59:36.000Z
|
||||||
|
mikepenz
|
||||||
|
test
|
||||||
|
1.0.0
|
||||||
|
- specify test case
|
||||||
|
mikepenz, nhoelzl
|
||||||
|
nhoelzl
|
||||||
|
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should match ordered ASC', async () => {
|
||||||
|
jest.setTimeout(180000)
|
||||||
|
|
||||||
|
const configuration = resolveConfiguration('', 'configs_test/configuration_asc.json')
|
||||||
|
const releaseNotes = new ReleaseNotes(octokit, {
|
||||||
|
owner: 'mikepenz',
|
||||||
|
repo: 'release-changelog-builder-action',
|
||||||
|
fromTag: 'v0.3.0',
|
||||||
|
toTag: 'v0.5.0',
|
||||||
|
failOnError: false,
|
||||||
|
configuration: configuration
|
||||||
|
})
|
||||||
|
|
||||||
|
const changeLog = await releaseNotes.pull()
|
||||||
|
console.log(changeLog)
|
||||||
|
expect(changeLog).toStrictEqual(`## 🚀 Features\n\n22\n24\n25\n26\n28\n\n## 🐛 Fixes\n\n23\n\n`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should match ordered DESC', async () => {
|
||||||
|
jest.setTimeout(180000)
|
||||||
|
|
||||||
|
const configuration = resolveConfiguration('', 'configs_test/configuration_desc.json')
|
||||||
|
const releaseNotes = new ReleaseNotes(octokit, {
|
||||||
|
owner: 'mikepenz',
|
||||||
|
repo: 'release-changelog-builder-action',
|
||||||
|
fromTag: 'v0.3.0',
|
||||||
|
toTag: 'v0.5.0',
|
||||||
|
failOnError: false,
|
||||||
|
configuration: configuration
|
||||||
|
})
|
||||||
|
|
||||||
|
const changeLog = await releaseNotes.pull()
|
||||||
|
console.log(changeLog)
|
||||||
|
expect(changeLog).toStrictEqual(`## 🚀 Features\n\n28\n26\n25\n24\n22\n\n## 🐛 Fixes\n\n23\n\n`)
|
||||||
|
})
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { resolveConfiguration } from '../src/utils';
|
||||||
|
import { ReleaseNotesBuilder } from '../src/releaseNotesBuilder';
|
||||||
|
|
||||||
|
it('Should match generated changelog (unspecified fromTag)', async () => {
|
||||||
|
jest.setTimeout(180000)
|
||||||
|
|
||||||
|
const configuration = resolveConfiguration('', 'configs/configuration.json')
|
||||||
|
const releaseNotesBuilder = new ReleaseNotesBuilder(
|
||||||
|
null,
|
||||||
|
'.',
|
||||||
|
'mikepenz',
|
||||||
|
'release-changelog-builder-action',
|
||||||
|
null,
|
||||||
|
'v0.0.3',
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
configuration
|
||||||
|
)
|
||||||
|
|
||||||
|
const changeLog = await releaseNotesBuilder.build()
|
||||||
|
console.log(changeLog)
|
||||||
|
expect(changeLog).toStrictEqual(`## 🧪 Tests
|
||||||
|
|
||||||
|
- [CI] Specify Test Case
|
||||||
|
- 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,51 @@
|
|||||||
|
import { resolveConfiguration } from '../src/utils';
|
||||||
|
import { ReleaseNotesBuilder } from '../src/releaseNotesBuilder';
|
||||||
|
import { TagInfo, sortTags } from '../src/tags';
|
||||||
|
|
||||||
|
it('Should order tags correctly', async () => {
|
||||||
|
jest.setTimeout(180000)
|
||||||
|
|
||||||
|
const tags: TagInfo[] = [
|
||||||
|
{ name: "2020.4.0", commit: "" },
|
||||||
|
{ name: "2020.4.0-rc02", commit: "" },
|
||||||
|
{ name: "2020.3.2", commit: "" },
|
||||||
|
{ name: "v2020.3.1", commit: "" },
|
||||||
|
{ name: "2020.3.1-rc03", commit: "" },
|
||||||
|
{ name: "2020.3.1-rc01", commit: "" },
|
||||||
|
{ name: "2020.3.1-b01", commit: "" },
|
||||||
|
{ name: "v2020.3.0", commit: "" }
|
||||||
|
]
|
||||||
|
|
||||||
|
const sorted = sortTags(tags).map(function (tag) {
|
||||||
|
return tag.name
|
||||||
|
}).join(",")
|
||||||
|
|
||||||
|
expect(sorted).toStrictEqual(`2020.4.0,2020.4.0-rc02,2020.3.2,v2020.3.1,2020.3.1-rc03,2020.3.1-rc01,2020.3.1-b01,v2020.3.0`)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it('Should order tags correctly', async () => {
|
||||||
|
jest.setTimeout(180000)
|
||||||
|
|
||||||
|
const tags: TagInfo[] = [
|
||||||
|
{ name: "0.0.1", commit: "" },
|
||||||
|
{ name: "0.0.1-rc01", commit: "" },
|
||||||
|
{ name: "0.1.0", commit: "" },
|
||||||
|
{ name: "0.1.0-b01", commit: "" },
|
||||||
|
{ name: "1.0.0", commit: "" },
|
||||||
|
{ name: "1.0.0-a01", commit: "" },
|
||||||
|
{ name: "2.0.0", commit: "" },
|
||||||
|
{ name: "10.0.0", commit: "" },
|
||||||
|
{ name: "10.1.0", commit: "" },
|
||||||
|
{ name: "10.1.0-2", commit: "" },
|
||||||
|
{ name: "20.0.2", commit: "" },
|
||||||
|
{ name: "100.0.0", commit: "" },
|
||||||
|
{ name: "1000.0.0", commit: "" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const sorted = sortTags(tags).map(function (tag) {
|
||||||
|
return tag.name
|
||||||
|
}).join(",")
|
||||||
|
|
||||||
|
expect(sorted).toStrictEqual(`1000.0.0,100.0.0,20.0.2,10.1.0,10.1.0-2,10.0.0,2.0.0,1.0.0,1.0.0-a01,0.1.0,0.1.0-b01,0.0.1,0.0.1-rc01`)
|
||||||
|
})
|
||||||
@@ -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}}"
|
||||||
|
}
|
||||||
+2348
-131
File diff suppressed because it is too large
Load Diff
+1
-1
File diff suppressed because one or more lines are too long
+19
@@ -583,6 +583,25 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
|||||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
semver
|
||||||
|
ISC
|
||||||
|
The ISC License
|
||||||
|
|
||||||
|
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||||
|
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
tunnel
|
tunnel
|
||||||
MIT
|
MIT
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|||||||
Generated
+33
-7
@@ -1740,9 +1740,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "14.11.8",
|
"version": "14.11.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.10.tgz",
|
||||||
"integrity": "sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw=="
|
"integrity": "sha512-yV1nWZPlMFpoXyoknm4S56y2nlTAuFYaJuQtYRAOU7xA/FJ9RY0Xm7QOkaYMMmr8ESdHIuUb6oQgR/0+2NqlyA=="
|
||||||
},
|
},
|
||||||
"@types/normalize-package-data": {
|
"@types/normalize-package-data": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
@@ -1756,6 +1756,11 @@
|
|||||||
"integrity": "sha512-IiPhNnenzkqdSdQH3ifk9LoX7oQe61ZlDdDO4+MUv6FyWdPGDPr26gCPVs3oguZEMq//nFZZpwUZcVuNJsG+DQ==",
|
"integrity": "sha512-IiPhNnenzkqdSdQH3ifk9LoX7oQe61ZlDdDO4+MUv6FyWdPGDPr26gCPVs3oguZEMq//nFZZpwUZcVuNJsG+DQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/semver": {
|
||||||
|
"version": "7.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz",
|
||||||
|
"integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ=="
|
||||||
|
},
|
||||||
"@types/stack-utils": {
|
"@types/stack-utils": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||||
@@ -5168,6 +5173,14 @@
|
|||||||
"@babel/types": "^7.4.0",
|
"@babel/types": "^7.4.0",
|
||||||
"istanbul-lib-coverage": "^2.0.5",
|
"istanbul-lib-coverage": "^2.0.5",
|
||||||
"semver": "^6.0.0"
|
"semver": "^6.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"semver": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"istanbul-lib-report": {
|
"istanbul-lib-report": {
|
||||||
@@ -6031,6 +6044,12 @@
|
|||||||
"lodash": "^4.17.19"
|
"lodash": "^4.17.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"semver": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.5.7",
|
"version": "0.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
@@ -7109,6 +7128,14 @@
|
|||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
"pretty-format": "^24.9.0",
|
"pretty-format": "^24.9.0",
|
||||||
"semver": "^6.2.0"
|
"semver": "^6.2.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"semver": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest-util": {
|
"jest-util": {
|
||||||
@@ -8457,10 +8484,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "6.3.0",
|
"version": "7.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"set-blocking": {
|
"set-blocking": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
|||||||
+4
-2
@@ -29,11 +29,13 @@
|
|||||||
"@actions/exec": "^1.0.4",
|
"@actions/exec": "^1.0.4",
|
||||||
"@actions/github": "^4.0.0",
|
"@actions/github": "^4.0.0",
|
||||||
"@octokit/rest": "^18.0.6",
|
"@octokit/rest": "^18.0.6",
|
||||||
"moment": "^2.29.1"
|
"@types/semver": "^7.3.4",
|
||||||
|
"moment": "^2.29.1",
|
||||||
|
"semver": "^7.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^26.0.14",
|
"@types/jest": "^26.0.14",
|
||||||
"@types/node": "^14.11.8",
|
"@types/node": "^14.11.10",
|
||||||
"@typescript-eslint/parser": "^4.4.1",
|
"@typescript-eslint/parser": "^4.4.1",
|
||||||
"@vercel/ncc": "^0.24.1",
|
"@vercel/ncc": "^0.24.1",
|
||||||
"eslint": "^7.11.0",
|
"eslint": "^7.11.0",
|
||||||
|
|||||||
+9
-62
@@ -1,13 +1,7 @@
|
|||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {
|
import {retrieveRepositoryPath, resolveConfiguration} from './utils'
|
||||||
failOrError,
|
|
||||||
retrieveRepositoryPath,
|
|
||||||
resolveConfiguration
|
|
||||||
} from './utils'
|
|
||||||
import {ReleaseNotes} from './releaseNotes'
|
|
||||||
import {createCommandManager} from './gitHelper'
|
|
||||||
import * as github from '@actions/github'
|
import * as github from '@actions/github'
|
||||||
import {DefaultConfiguration} from './configuration'
|
import {ReleaseNotesBuilder} from './releaseNotesBuilder'
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
core.setOutput('failed', false) // mark the action not failed by default
|
core.setOutput('failed', false) // mark the action not failed by default
|
||||||
@@ -31,71 +25,24 @@ async function run(): Promise<void> {
|
|||||||
const repo = core.getInput('repo') || github.context.repo.repo
|
const repo = core.getInput('repo') || github.context.repo.repo
|
||||||
// read in from, to tag inputs
|
// read in from, to tag inputs
|
||||||
const fromTag = core.getInput('fromTag')
|
const fromTag = core.getInput('fromTag')
|
||||||
let toTag = core.getInput('toTag')
|
const toTag = core.getInput('toTag')
|
||||||
// read in flags
|
// read in flags
|
||||||
const ignorePreReleases = core.getInput('ignorePreReleases') === 'true'
|
const ignorePreReleases = core.getInput('ignorePreReleases') === 'true'
|
||||||
const failOnError = core.getInput('failOnError') === 'true'
|
const failOnError = core.getInput('failOnError') === 'true'
|
||||||
|
|
||||||
// ensure to resolve the toTag if it was not provided
|
const result = await new ReleaseNotesBuilder(
|
||||||
if (!toTag) {
|
token,
|
||||||
// if not specified try to retrieve tag from github.context.ref
|
repositoryPath,
|
||||||
if (github.context.ref.startsWith('refs/tags/')) {
|
|
||||||
toTag = github.context.ref.replace('refs/tags/', '')
|
|
||||||
core.info(
|
|
||||||
`🔖 Resolved current tag (${toTag}) from the 'github.context.ref'`
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// if not specified try to retrieve tag from git
|
|
||||||
const gitHelper = await createCommandManager(repositoryPath)
|
|
||||||
const latestTag = await gitHelper.latestTag()
|
|
||||||
toTag = latestTag
|
|
||||||
core.info(
|
|
||||||
`🔖 Resolved current tag (${toTag}) from 'git rev-list --tags --skip=0 --max-count=1'`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!owner) {
|
|
||||||
failOrError(`💥 Missing or couldn't resolve 'owner'`, failOnError)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
core.setOutput('owner', owner)
|
|
||||||
core.debug(`Resolved 'owner' as ${owner}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!repo) {
|
|
||||||
failOrError(`💥 Missing or couldn't resolve 'owner'`, failOnError)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
core.setOutput('repo', repo)
|
|
||||||
core.debug(`Resolved 'repo' as ${repo}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!toTag) {
|
|
||||||
failOrError(`💥 Missing or couldn't resolve 'toTag'`, failOnError)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
core.setOutput('toTag', toTag)
|
|
||||||
core.debug(`Resolved 'toTag' as ${toTag}`)
|
|
||||||
}
|
|
||||||
core.endGroup()
|
|
||||||
|
|
||||||
const releaseNotes = new ReleaseNotes({
|
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
fromTag,
|
fromTag,
|
||||||
toTag,
|
toTag,
|
||||||
ignorePreReleases,
|
|
||||||
failOnError,
|
failOnError,
|
||||||
|
ignorePreReleases,
|
||||||
configuration
|
configuration
|
||||||
})
|
).build()
|
||||||
|
|
||||||
core.setOutput(
|
core.setOutput('changelog', result)
|
||||||
'changelog',
|
|
||||||
(await releaseNotes.pull(token)) ||
|
|
||||||
configuration.empty_template ||
|
|
||||||
DefaultConfiguration.empty_template
|
|
||||||
)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(error.message)
|
core.setFailed(error.message)
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-52
@@ -3,71 +3,26 @@ import {Commits, CommitInfo} from './commits'
|
|||||||
import {PullRequestInfo, PullRequests} from './pullRequests'
|
import {PullRequestInfo, PullRequests} from './pullRequests'
|
||||||
import {buildChangelog} from './transform'
|
import {buildChangelog} from './transform'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {Tags} from './tags'
|
|
||||||
import {Configuration, DefaultConfiguration} from './configuration'
|
import {Configuration, DefaultConfiguration} from './configuration'
|
||||||
import {failOrError} from './utils'
|
import {failOrError} from './utils'
|
||||||
|
|
||||||
export interface ReleaseNotesOptions {
|
export interface ReleaseNotesOptions {
|
||||||
owner: string // the owner of the repository
|
owner: string // the owner of the repository
|
||||||
repo: string // the repository
|
repo: string // the repository
|
||||||
fromTag: string | null // the tag/ref to start from
|
fromTag: string // the tag/ref to start from
|
||||||
toTag: string // the tag/ref up to
|
toTag: string // the tag/ref up to
|
||||||
ignorePreReleases: boolean // defines if we should ignore any pre-releases for matching, only relevant if fromTag is null
|
|
||||||
failOnError: boolean // defines if we should fail the action in case of an error
|
failOnError: boolean // defines if we should fail the action in case of an error
|
||||||
configuration: Configuration // the configuration as defined in `configuration.ts`
|
configuration: Configuration // the configuration as defined in `configuration.ts`
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ReleaseNotes {
|
export class ReleaseNotes {
|
||||||
constructor(private options: ReleaseNotesOptions) {}
|
constructor(private octokit: Octokit, private options: ReleaseNotesOptions) {}
|
||||||
|
|
||||||
async pull(token?: string): Promise<string | null> {
|
async pull(): Promise<string | null> {
|
||||||
const octokit = new Octokit({
|
const {configuration} = this.options
|
||||||
auth: `token ${token || process.env.GITHUB_TOKEN}`
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
toTag,
|
|
||||||
ignorePreReleases,
|
|
||||||
failOnError,
|
|
||||||
configuration
|
|
||||||
} = this.options
|
|
||||||
|
|
||||||
if (!this.options.fromTag) {
|
|
||||||
core.startGroup(`🔖 Resolve previous tag`)
|
|
||||||
core.debug(`fromTag undefined, trying to resolve via API`)
|
|
||||||
const tagsApi = new Tags(octokit)
|
|
||||||
|
|
||||||
const previousTag = await tagsApi.findPredecessorTag(
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
toTag,
|
|
||||||
ignorePreReleases,
|
|
||||||
configuration.max_tags_to_fetch ||
|
|
||||||
DefaultConfiguration.max_tags_to_fetch
|
|
||||||
)
|
|
||||||
if (previousTag == null) {
|
|
||||||
failOrError(
|
|
||||||
`💥 Unable to retrieve previous tag given ${toTag}`,
|
|
||||||
failOnError
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
this.options.fromTag = previousTag.name
|
|
||||||
core.debug(`fromTag resolved via previousTag as: ${previousTag.name}`)
|
|
||||||
core.endGroup()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.options.fromTag) {
|
|
||||||
failOrError(`💥 Missing or couldn't resolve 'fromTag'`, failOnError)
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
core.setOutput('fromTag', this.options.fromTag)
|
|
||||||
}
|
|
||||||
|
|
||||||
core.startGroup(`🚀 Load pull requests`)
|
core.startGroup(`🚀 Load pull requests`)
|
||||||
const mergedPullRequests = await this.getMergedPullRequests(octokit)
|
const mergedPullRequests = await this.getMergedPullRequests(this.octokit)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
if (mergedPullRequests.length === 0) {
|
if (mergedPullRequests.length === 0) {
|
||||||
@@ -76,7 +31,11 @@ export class ReleaseNotes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
core.startGroup('📦 Build changelog')
|
core.startGroup('📦 Build changelog')
|
||||||
const resultChangelog = buildChangelog(mergedPullRequests, configuration)
|
const resultChangelog = buildChangelog(
|
||||||
|
mergedPullRequests,
|
||||||
|
configuration,
|
||||||
|
this.options
|
||||||
|
)
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
return resultChangelog
|
return resultChangelog
|
||||||
}
|
}
|
||||||
@@ -97,7 +56,7 @@ export class ReleaseNotes {
|
|||||||
const commitsApi = new Commits(octokit)
|
const commitsApi = new Commits(octokit)
|
||||||
let commits: CommitInfo[]
|
let commits: CommitInfo[]
|
||||||
try {
|
try {
|
||||||
commits = await commitsApi.getDiff(owner, repo, fromTag!!, toTag)
|
commits = await commitsApi.getDiff(owner, repo, fromTag, toTag)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
failOrError(
|
failOrError(
|
||||||
`💥 Failed to retrieve - Invalid tag? - Because of: ${error}`,
|
`💥 Failed to retrieve - Invalid tag? - Because of: ${error}`,
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import {Configuration, DefaultConfiguration} from './configuration'
|
||||||
|
import * as github from '@actions/github'
|
||||||
|
import * as core from '@actions/core'
|
||||||
|
import {createCommandManager} from './gitHelper'
|
||||||
|
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(
|
||||||
|
private token: string | null,
|
||||||
|
private repositoryPath: string,
|
||||||
|
private owner: string | null,
|
||||||
|
private repo: string | null,
|
||||||
|
private fromTag: string | null,
|
||||||
|
private toTag: string | null,
|
||||||
|
private failOnError: boolean,
|
||||||
|
private ignorePreReleases: boolean,
|
||||||
|
private configuration: Configuration
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async build(): Promise<string | null> {
|
||||||
|
// ensure to resolve the toTag if it was not provided
|
||||||
|
if (!this.toTag) {
|
||||||
|
// if not specified try to retrieve tag from github.context.ref
|
||||||
|
if (github.context.ref.startsWith('refs/tags/')) {
|
||||||
|
this.toTag = github.context.ref.replace('refs/tags/', '')
|
||||||
|
core.info(
|
||||||
|
`🔖 Resolved current tag (${this.toTag}) from the 'github.context.ref'`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// if not specified try to retrieve tag from git
|
||||||
|
const gitHelper = await createCommandManager(this.repositoryPath)
|
||||||
|
const latestTag = await gitHelper.latestTag()
|
||||||
|
this.toTag = latestTag
|
||||||
|
core.info(
|
||||||
|
`🔖 Resolved current tag (${this.toTag}) from 'git rev-list --tags --skip=0 --max-count=1'`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.owner) {
|
||||||
|
failOrError(`💥 Missing or couldn't resolve 'owner'`, this.failOnError)
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
core.setOutput('owner', this.owner)
|
||||||
|
core.debug(`Resolved 'owner' as ${this.owner}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.repo) {
|
||||||
|
failOrError(`💥 Missing or couldn't resolve 'owner'`, this.failOnError)
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
core.setOutput('repo', this.repo)
|
||||||
|
core.debug(`Resolved 'repo' as ${this.repo}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.toTag) {
|
||||||
|
failOrError(`💥 Missing or couldn't resolve 'toTag'`, this.failOnError)
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
core.setOutput('toTag', this.toTag)
|
||||||
|
core.debug(`Resolved 'toTag' as ${this.toTag}`)
|
||||||
|
}
|
||||||
|
core.endGroup()
|
||||||
|
|
||||||
|
// load octokit instance
|
||||||
|
const octokit = new Octokit({
|
||||||
|
auth: `token ${this.token || process.env.GITHUB_TOKEN}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// ensure to resolve the fromTag if it was not provided specifically
|
||||||
|
if (!this.fromTag) {
|
||||||
|
core.startGroup(`🔖 Resolve previous tag`)
|
||||||
|
core.debug(`fromTag undefined, trying to resolve via API`)
|
||||||
|
const tagsApi = new Tags(octokit)
|
||||||
|
|
||||||
|
const previousTag = await tagsApi.findPredecessorTag(
|
||||||
|
this.owner,
|
||||||
|
this.repo,
|
||||||
|
this.toTag,
|
||||||
|
this.ignorePreReleases,
|
||||||
|
this.configuration.max_tags_to_fetch ||
|
||||||
|
DefaultConfiguration.max_tags_to_fetch
|
||||||
|
)
|
||||||
|
if (previousTag == null) {
|
||||||
|
failOrError(
|
||||||
|
`💥 Unable to retrieve previous tag given ${this.toTag}`,
|
||||||
|
this.failOnError
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
this.fromTag = previousTag.name
|
||||||
|
core.debug(`fromTag resolved via previousTag as: ${previousTag.name}`)
|
||||||
|
core.endGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) ||
|
||||||
|
fillAdditionalPlaceholders(
|
||||||
|
this.configuration.empty_template ||
|
||||||
|
DefaultConfiguration.empty_template,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-21
@@ -1,5 +1,7 @@
|
|||||||
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
|
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
|
import * as semver from 'semver'
|
||||||
|
import {SemVer} from 'semver'
|
||||||
|
|
||||||
export interface TagInfo {
|
export interface TagInfo {
|
||||||
name: string
|
name: string
|
||||||
@@ -52,7 +54,7 @@ export class Tags {
|
|||||||
ignorePreReleases: boolean,
|
ignorePreReleases: boolean,
|
||||||
maxTagsToFetch: number
|
maxTagsToFetch: number
|
||||||
): Promise<TagInfo | null> {
|
): Promise<TagInfo | null> {
|
||||||
const tags = this.sortTags(await this.getTags(owner, repo, maxTagsToFetch))
|
const tags = sortTags(await this.getTags(owner, repo, maxTagsToFetch))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const length = tags.length
|
const length = tags.length
|
||||||
@@ -76,8 +78,9 @@ export class Tags {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Sorts an array of tags as shown below:
|
Sorts an array of tags as shown below:
|
||||||
|
|
||||||
2020.4.0
|
2020.4.0
|
||||||
@@ -91,23 +94,19 @@ export class Tags {
|
|||||||
2020.3.1-a01
|
2020.3.1-a01
|
||||||
2020.3.0
|
2020.3.0
|
||||||
*/
|
*/
|
||||||
private sortTags(commits: TagInfo[]): TagInfo[] {
|
export function sortTags(tags: TagInfo[]): TagInfo[] {
|
||||||
commits.sort((b, a) => {
|
// filter out tags which do not follow semver
|
||||||
const partsA = a.name.replace(/^v/, '').split('-')
|
const validatedTags = tags.filter(tag => {
|
||||||
const partsB = b.name.replace(/^v/, '').split('-')
|
const isValid = semver.valid(tag.name) !== null
|
||||||
const versionCompare = partsA[0].localeCompare(partsB[0])
|
if(!isValid) {
|
||||||
if (versionCompare !== 0) {
|
core.debug(`⚠️ dropped tag ${tag.name} because it is not a valid semver tag`)
|
||||||
return versionCompare
|
}
|
||||||
} else {
|
return isValid
|
||||||
if (partsA.length === 1) {
|
})
|
||||||
return 0
|
|
||||||
} else if (partsB.length === 1) {
|
// sort using semver
|
||||||
return 1
|
validatedTags.sort((b, a) => {
|
||||||
} else {
|
return new SemVer(a.name).compare(b.name)
|
||||||
return partsA[1].localeCompare(partsB[1])
|
})
|
||||||
}
|
return validatedTags
|
||||||
}
|
|
||||||
})
|
|
||||||
return commits
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-5
@@ -1,5 +1,6 @@
|
|||||||
import {PullRequestInfo, sortPullRequests} from './pullRequests'
|
import {PullRequestInfo, sortPullRequests} from './pullRequests'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
|
import {ReleaseNotesOptions} from './releaseNotes'
|
||||||
import {
|
import {
|
||||||
Category,
|
Category,
|
||||||
Configuration,
|
Configuration,
|
||||||
@@ -9,7 +10,8 @@ import {
|
|||||||
|
|
||||||
export function buildChangelog(
|
export function buildChangelog(
|
||||||
prs: PullRequestInfo[],
|
prs: PullRequestInfo[],
|
||||||
config: Configuration
|
config: Configuration,
|
||||||
|
options: ReleaseNotesOptions
|
||||||
): string {
|
): string {
|
||||||
// sort to target order
|
// sort to target order
|
||||||
const sort = config.sort || DefaultConfiguration.sort
|
const sort = config.sort || DefaultConfiguration.sort
|
||||||
@@ -26,9 +28,7 @@ export function buildChangelog(
|
|||||||
transform(
|
transform(
|
||||||
fillTemplate(
|
fillTemplate(
|
||||||
pr,
|
pr,
|
||||||
config.pr_template
|
config.pr_template || DefaultConfiguration.pr_template
|
||||||
? config.pr_template
|
|
||||||
: DefaultConfiguration.pr_template
|
|
||||||
),
|
),
|
||||||
validatedTransformers
|
validatedTransformers
|
||||||
)
|
)
|
||||||
@@ -45,6 +45,7 @@ export function buildChangelog(
|
|||||||
for (const category of categories) {
|
for (const category of categories) {
|
||||||
categorized.set(category, [])
|
categorized.set(category, [])
|
||||||
}
|
}
|
||||||
|
const categorizedPrs: string[] = []
|
||||||
const uncategorized: string[] = []
|
const uncategorized: string[] = []
|
||||||
|
|
||||||
// bring elements in order
|
// bring elements in order
|
||||||
@@ -60,6 +61,8 @@ export function buildChangelog(
|
|||||||
|
|
||||||
if (!matched) {
|
if (!matched) {
|
||||||
uncategorized.push(body)
|
uncategorized.push(body)
|
||||||
|
} else {
|
||||||
|
categorizedPrs.push(body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
core.info(`ℹ️ Ordered all pull requests into ${categories.length} categories`)
|
core.info(`ℹ️ Ordered all pull requests into ${categories.length} categories`)
|
||||||
@@ -98,10 +101,37 @@ export function buildChangelog(
|
|||||||
'${{UNCATEGORIZED}}',
|
'${{UNCATEGORIZED}}',
|
||||||
changelogUncategorized
|
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`)
|
core.info(`ℹ️ Filled template`)
|
||||||
return transformedChangelog
|
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 {
|
function haveCommonElements(arr1: string[], arr2: string[]): Boolean {
|
||||||
return arr1.some(item => arr2.includes(item))
|
return arr1.some(item => arr2.includes(item))
|
||||||
}
|
}
|
||||||
@@ -133,7 +163,9 @@ function transform(filled: string, transformers: RegexTransformer[]): string {
|
|||||||
}
|
}
|
||||||
let transformed = filled
|
let transformed = filled
|
||||||
for (const {target, pattern} of transformers) {
|
for (const {target, pattern} of transformers) {
|
||||||
transformed = transformed.replace(pattern!!, target)
|
if (pattern) {
|
||||||
|
transformed = transformed.replace(pattern, target)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return transformed
|
return transformed
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user