@@ -22,6 +22,18 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Won't support providing a configuration file, as no checkout was done
|
||||
- name: "Configuration without Checkout"
|
||||
id: without_checkout
|
||||
uses: mikepenz/release-changelog-builder-action@develop
|
||||
with:
|
||||
toTag: "v0.0.3"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Echo Configuration without Checkout Changelog
|
||||
run: echo "${{steps.without_checkout.outputs.changelog}}"
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Checkout full depth so tags can be discovered automatically if not specified
|
||||
@@ -32,31 +44,27 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Echo Minimal
|
||||
- name: Echo Minimal Configuration Changelog
|
||||
run: echo "${{steps.minimal_release.outputs.changelog}}"
|
||||
|
||||
- name: "Complex Configuration"
|
||||
id: complex_release
|
||||
uses: ./
|
||||
with:
|
||||
configuration: "configuration_complex.json"
|
||||
configuration: "configs/configuration_complex.json"
|
||||
owner: "mikepenz"
|
||||
repo: "release-changelog-builder-action"
|
||||
fromTag: "v0.0.1"
|
||||
toTag: "v0.0.3"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Echo Complex
|
||||
- name: Echo Complex Configuration Changelog
|
||||
run: echo "${{steps.complex_release.outputs.changelog}}"
|
||||
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Checkout full depth so tags can be discovered automatically if not specified
|
||||
|
||||
- name: Prepare Tag
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
id: tag_version
|
||||
@@ -66,6 +74,7 @@ jobs:
|
||||
id: github_release
|
||||
uses: mikepenz/release-changelog-builder-action@main
|
||||
with:
|
||||
configuration: "configs/configuration_repo.json"
|
||||
toTag: ${{ steps.tag_version.outputs.VERSION }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,11 +1,52 @@
|
||||
<div align="center">
|
||||
:octocat:📄🔖📦
|
||||
</div>
|
||||
<h1 align="center">
|
||||
release-changelog-builder-action
|
||||
</h1>
|
||||
|
||||
# release-changelog-builder-action
|
||||
<p align="center">
|
||||
... a github action that builds your release notes fast, easy and exactly the way you want.
|
||||
</p>
|
||||
|
||||
Builds the release notes between two tags (or refs) from pull requests merged.
|
||||
<div align="center">
|
||||
<a href="https://github.com/mikepenz/release-changelog-builder-action/actions">
|
||||
<img src="https://github.com/mikepenz/release-changelog-builder-action/workflows/CI/badge.svg"/>
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
## Action usage
|
||||
-------
|
||||
|
||||
Include this action in your build by defining the action in your workflow:
|
||||
<p align="center">
|
||||
<a href="#whats-included-">What's included 🚀</a> •
|
||||
<a href="#setup">Setup 🛠️</a> •
|
||||
<a href="#customization-%EF%B8%8F">Customization 🖍️</a> •
|
||||
<a href="#contribute-">Contribute 🧬</a> •
|
||||
<a href="#complete-sample-%EF%B8%8F">Complete Sample 🖥️</a> •
|
||||
<a href="#license">License 📓</a>
|
||||
</p>
|
||||
|
||||
-------
|
||||
|
||||
### What's included 🚀
|
||||
|
||||
- Super simple integration
|
||||
- even on huge repositories with hundreds of tags
|
||||
- Parallel releases support
|
||||
- Blazingly fast execution
|
||||
- Supports any git project
|
||||
- Highly flexible configuration
|
||||
- Lightweight
|
||||
- Supports any branch
|
||||
|
||||
-------
|
||||
|
||||
## Setup
|
||||
|
||||
### Configure the workflow
|
||||
|
||||
Specify the action as part of your GitHub actions workflow:
|
||||
|
||||
```yml
|
||||
- name: "Build Changelog"
|
||||
@@ -16,31 +57,41 @@ Include this action in your build by defining the action in your workflow:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
This will automatically pull the tag from the current commit (the latest tag), and try to resolve the tag before this.
|
||||
By default the action will try to automatically retrieve the `tag` from the current commit and automtacally resolve the `tag` before. Read more about this here.
|
||||
|
||||
## Action outputs
|
||||
### Action outputs
|
||||
|
||||
The result of this action is returned via the outputs, and can be retrieved via the `changelog` value in the step afterwards. See the `test.yml` for a sample.
|
||||
The action will succeed and return the `changelog` as a step output. Use it in any follow up step by referencing via its id. For example `build_changelog`.
|
||||
|
||||
```yml
|
||||
# ${{steps.{CHANGELOG_STEP_ID}.outputs.changelog}}
|
||||
${{steps.build_changelog.outputs.changelog}}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
## Customization 🖍️
|
||||
|
||||
By default the action will look for a file called `configuration.json` within the root of the repository to load the config from. If this file does not exist, defaults are used.
|
||||
### Changelog Configuration
|
||||
|
||||
The action supports flexible configuration options to modify vast areas of its behavior. To do so, provide the configuration file to the workflow using the `configuration` setting.
|
||||
|
||||
```yml
|
||||
- name: "Build Changelog"
|
||||
uses: mikepenz/release-changelog-builder-action@{latest-release}
|
||||
with:
|
||||
configuration: "configuration.json"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
This configuration is a `.json` file in the following format.
|
||||
|
||||
```json
|
||||
{
|
||||
"categories": [
|
||||
{
|
||||
"title": "## 🚀 Features",
|
||||
"labels": ["feature"]
|
||||
},
|
||||
{
|
||||
"title": "## 🦄 Internal Features",
|
||||
"labels": ["internal"]
|
||||
},
|
||||
{
|
||||
"title": "## 🐛 Fixes",
|
||||
"labels": ["fix"]
|
||||
@@ -69,11 +120,11 @@ By default the action will look for a file called `configuration.json` within th
|
||||
}
|
||||
```
|
||||
|
||||
Any section of the configruation can be ommited, to have defaults apply
|
||||
Any section of the configruation can be ommited to have defaults apply.
|
||||
Defaults for the configuraiton can be found in the [configuration.ts](https://github.com/mikepenz/release-changelog-builder-action/blob/develop/src/configuration.ts)
|
||||
|
||||
|
||||
## Advanced workflow specification
|
||||
### Advanced workflow specification
|
||||
|
||||
For advanced usecases additional settings can be provided to the action
|
||||
|
||||
@@ -86,32 +137,83 @@ For advanced usecases additional settings can be provided to the action
|
||||
configuration: "configuration_complex.json"
|
||||
owner: "mikepenz"
|
||||
repo: "release-changelog-builder-action"
|
||||
ignorePreReleases: "false" # allows to skip any pre releases, if `fromTag` needs to be automatically resolved (ignores 0.0.2-rc02 for example) - only relevant if `fromTag` is not provided
|
||||
ignorePreReleases: "false"
|
||||
fromTag: "0.0.2"
|
||||
toTag: "0.0.3"
|
||||
token: ${{ secrets.GITHUB_TOKEN }} # the token to use, for a different repository a PAT is required (Personal access token)
|
||||
token: ${{ secrets.PAT }}
|
||||
```
|
||||
|
||||
## PR Template placeholders
|
||||
💡 `ignorePreReleases` will be ignored, if `fromTag` is specified. `${{ secrets.GITHUB_TOKEN }}` only grants rights to the current repository, for other repos please use a PAT (Personal Access Token).
|
||||
|
||||
| Variable | Description |
|
||||
| --------- | -------------------------- |
|
||||
| `${{NUMBER}}` | Pull request number |
|
||||
| `${{TITLE}}` | The title of the pull request |
|
||||
| `${{URL}}` | The URL linking to the pull request |
|
||||
| `${{MERGED_AT}}` | The time this PR was merged |
|
||||
| `${{AUTHOR}}` | The author of the pull request |
|
||||
| `${{BODY}}` | The body / description of the pull request |
|
||||
### PR Template placeholders
|
||||
|
||||
## Template placeholdrs
|
||||
Table of supported placeholders allowed to be used in the `template` configuration.
|
||||
|
||||
| Variable | Description |
|
||||
| --------- | -------------------------- |
|
||||
| `${{CHANGELOG}}` | The contents of the main changelog, matching the labels as specified in the categories configuration |
|
||||
| `${{UNCATEGORIZED}}` | All pull requests not matching a label |
|
||||
| **Placeholder** | **Description** |
|
||||
|------------------|-------------------------------------------------------------|
|
||||
| `${{NUMBER}}` | The number referencing this pull request. E.g. 13 |
|
||||
| `${{TITLE}}` | Specified title of the merged pull request |
|
||||
| `${{URL}}` | Url linking to the pull request on GitHub |
|
||||
| `${{MERGED_AT}}` | The ISO time, the pull request was merged at |
|
||||
| `${{AUTHOR}}` | Author creating and opening the pull request |
|
||||
| `${{LABELS}}` | The labels associated with this pull request, joined by `,` |
|
||||
| `${{MILESTONE}}` | Milestone this PR was part of, as assigned on GitHub |
|
||||
| `${{BODY}}` | Description/Body of the pull request as specified on GitHub |
|
||||
| `${{ASSIGNEES}}` | Login names of assigned GitHub users, joined by `,` |
|
||||
| `${{REVIEWERS}}` | GitHub Login names of specified reviewers, joined by `,` |
|
||||
|
||||
### Template placeholders
|
||||
|
||||
Table of supported placeholders allowed to be used in the `pr_template` configuration.
|
||||
|
||||
| **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 |
|
||||
|
||||
|
||||
# Contribute
|
||||
## Complete Sample 🖥️
|
||||
|
||||
Below is a complete example showcasing how to define a build, which is executed when tagging the project. It consists of:
|
||||
- Prepare tag, via the GITHUB_REF environment variable
|
||||
- Build changelog, given the tag
|
||||
- Create release on GitHub - specifying body with constructed changelog
|
||||
|
||||
```yml
|
||||
name: 'CI'
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Retrieve tag
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
id: tag_version
|
||||
run: echo ::set-output name=VERSION::$(echo ${GITHUB_REF:10})
|
||||
|
||||
- name: Build Changelog
|
||||
id: github_release
|
||||
uses: mikepenz/release-changelog-builder-action@main
|
||||
with:
|
||||
toTag: ${{ steps.tag_version.outputs.VERSION }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create Release
|
||||
uses: actions/create-release@v1
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
body: ${{steps.github_release.outputs.changelog}}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
## Contribute 🧬
|
||||
|
||||
```bash
|
||||
# Install the dependencies
|
||||
@@ -127,29 +229,28 @@ $ npm test
|
||||
$ npm run lint -- --fix
|
||||
```
|
||||
|
||||
It's suggested to export the token to your path, before running the tests, so API calls can be done to github.
|
||||
It's suggested to export the token to your path before running the tests, so that API calls can be done to github.
|
||||
|
||||
```bash
|
||||
export GITHUB_TOKEN=your_personal_github_pat
|
||||
```
|
||||
|
||||
|
||||
# Developed By
|
||||
## Developed By
|
||||
|
||||
* Mike Penz
|
||||
* [mikepenz.com](http://mikepenz.com) - <mikepenz@gmail.com>
|
||||
* [paypal.me/mikepenz](http://paypal.me/mikepenz)
|
||||
|
||||
# Credits
|
||||
## Credits
|
||||
|
||||
Core parts of the PR fetching logic, are based on [pull-release-notes](https://github.com/nblagoev/pull-release-notes)
|
||||
Core parts of the PR fetching logic are based on [pull-release-notes](https://github.com/nblagoev/pull-release-notes)
|
||||
- Nikolay Blagoev - [GitHub](https://github.com/nblagoev/)
|
||||
|
||||
# License
|
||||
## License
|
||||
|
||||
Copyright for portions of pr-release-notes are held by Nikolay Blagoev, 2019-2020 as part of project pull-release-notes. All other copyright for project pr-release-notes are held by Mike Penz, 2020.
|
||||
|
||||
# Fork License
|
||||
## Fork License
|
||||
|
||||
All patches and changes applied to the original source are licensed under the Apache 2.0 license.
|
||||
|
||||
|
||||
+30
-12
@@ -1,12 +1,5 @@
|
||||
import {wait} from '../src/wait'
|
||||
import * as process from 'process'
|
||||
import * as cp from 'child_process'
|
||||
import * as path from 'path'
|
||||
import {ReleaseNotes} from '../src/releaseNotes'
|
||||
import {readConfiguration} from '../src/utils'
|
||||
import {createCommandManager} from '../src/git-helper'
|
||||
import * as core from '@actions/core'
|
||||
import {Tags} from '../src/tags'
|
||||
|
||||
// shows how the runner will run a javascript action with env / stdout protocol
|
||||
/*
|
||||
@@ -21,11 +14,28 @@ test('test runs', () => {
|
||||
console.log(cp.execSync(`node ${ip}`, options).toString())
|
||||
})
|
||||
*/
|
||||
it('Should have empty changelog (tags)', async () => {
|
||||
jest.setTimeout(180000)
|
||||
|
||||
const configuration = readConfiguration('configs/configuration.json')!!
|
||||
const releaseNotes = new ReleaseNotes({
|
||||
owner: 'mikepenz',
|
||||
repo: 'release-changelog-builder-action',
|
||||
fromTag: 'v0.0.1',
|
||||
toTag: 'v0.0.2',
|
||||
ignorePreReleases: false,
|
||||
configuration: configuration
|
||||
})
|
||||
|
||||
const changeLog = await releaseNotes.pull()
|
||||
console.log(changeLog)
|
||||
expect(changeLog).toStrictEqual(`- no changes`)
|
||||
})
|
||||
|
||||
it('Should match generated changelog (tags)', async () => {
|
||||
jest.setTimeout(180000)
|
||||
|
||||
const configuration = readConfiguration('configuration.json')
|
||||
const configuration = readConfiguration('configs/configuration.json')!!
|
||||
const releaseNotes = new ReleaseNotes({
|
||||
owner: 'mikepenz',
|
||||
repo: 'release-changelog-builder-action',
|
||||
@@ -48,7 +58,7 @@ it('Should match generated changelog (tags)', async () => {
|
||||
it('Should match generated changelog (unspecified fromTag)', async () => {
|
||||
jest.setTimeout(180000)
|
||||
|
||||
const configuration = readConfiguration('configuration.json')
|
||||
const configuration = readConfiguration('configs/configuration.json')!!
|
||||
const releaseNotes = new ReleaseNotes({
|
||||
owner: 'mikepenz',
|
||||
repo: 'release-changelog-builder-action',
|
||||
@@ -71,7 +81,7 @@ it('Should match generated changelog (unspecified fromTag)', async () => {
|
||||
it('Should match generated changelog (refs)', async () => {
|
||||
jest.setTimeout(180000)
|
||||
|
||||
const configuration = readConfiguration('configuration.json')
|
||||
const configuration = readConfiguration('configs/configuration_all_placeholders.json')!!
|
||||
const releaseNotes = new ReleaseNotes({
|
||||
owner: 'mikepenz',
|
||||
repo: 'release-changelog-builder-action',
|
||||
@@ -85,8 +95,16 @@ it('Should match generated changelog (refs)', async () => {
|
||||
console.log(changeLog)
|
||||
expect(changeLog).toStrictEqual(`## 🧪 Tests
|
||||
|
||||
- [CI] Specify Test Case
|
||||
- PR: #10
|
||||
[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
|
||||
|
||||
`)
|
||||
})
|
||||
|
||||
+10
-12
@@ -1,32 +1,30 @@
|
||||
name: 'Release Changelog Builder'
|
||||
description: 'Pulls all merged pull requests, and constructs the changelog for a release'
|
||||
description: 'A GitHub action that builds your release notes fast, easy and exactly the way you want.'
|
||||
author: 'Mike Penz'
|
||||
branding:
|
||||
icon: 'award'
|
||||
color: 'green'
|
||||
inputs:
|
||||
configuration:
|
||||
required: true
|
||||
description: 'path to the configuration file'
|
||||
default: "configuration.json"
|
||||
description: 'Defines the relative path to the configuration file.'
|
||||
path:
|
||||
description: 'the path to runt his action in'
|
||||
description: 'Defines the directory the repo is located in (checkout directory)'
|
||||
owner:
|
||||
description: 'the owner of the repository to create the changelog for'
|
||||
description: 'Defines the owner of the repository to create the changelog for'
|
||||
repo:
|
||||
description: 'the repository to create the changelog for'
|
||||
description: 'Defines the repository to create the changelog for'
|
||||
fromTag:
|
||||
description: 'the previous tag to compare against'
|
||||
description: 'Defines the previous tag to compare against'
|
||||
toTag:
|
||||
description: 'the new tag created'
|
||||
description: 'Defines the newly tag created'
|
||||
ignorePreReleases:
|
||||
description: 'defines if only full releases should be considered to compare against (Only used if fromTag is not defined). E.g. for 1.0.1... 1.0.0-rc02 <- ignore, 1.0.0 <- pick'
|
||||
description: 'Defines if the action will only use full releases to compare against (Only used if fromTag is not defined). E.g. for 1.0.1... 1.0.0-rc02 <- ignore, 1.0.0 <- pick'
|
||||
default: "false"
|
||||
token:
|
||||
description: 'the token to use to execute the git API requests'
|
||||
description: 'Defines the token to use to execute the git API requests with, uses `env.GITHUB_TOKEN` by default'
|
||||
outputs:
|
||||
changelog:
|
||||
description: The generated changelog as markdown.
|
||||
description: Returns the generated changelog as markdown.
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'dist/index.js'
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
"title": "## 🚀 Features",
|
||||
"labels": ["feature"]
|
||||
},
|
||||
{
|
||||
"title": "## 🦄 Internal Features",
|
||||
"labels": ["internal"]
|
||||
},
|
||||
{
|
||||
"title": "## 🐛 Fixes",
|
||||
"labels": ["fix"]
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"pr_template": "${{TITLE}}\n${{NUMBER}}\n${{URL}}\n${{MERGED_AT}}\n${{AUTHOR}}\n${{LABELS}}\n${{MILESTONE}}\n${{BODY}}\n${{ASSIGNEES}}\n${{REVIEWERS}}"
|
||||
}
|
||||
@@ -4,10 +4,6 @@
|
||||
"title": "## 🚀 Features",
|
||||
"labels": ["feature"]
|
||||
},
|
||||
{
|
||||
"title": "## 🦄 Internal Features",
|
||||
"labels": ["internal"]
|
||||
},
|
||||
{
|
||||
"title": "## 🐛 Fixes",
|
||||
"labels": ["fix"]
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"categories": [
|
||||
{
|
||||
"title": "## 🚀 Features",
|
||||
"labels": ["feature"]
|
||||
},
|
||||
{
|
||||
"title": "## 🐛 Fixes",
|
||||
"labels": ["fix"]
|
||||
},
|
||||
{
|
||||
"title": "## 🧪 Tests",
|
||||
"labels": ["test"]
|
||||
},
|
||||
{
|
||||
"title": "## 💬 Other",
|
||||
"labels": ["other"]
|
||||
}
|
||||
]
|
||||
}
|
||||
+190
-117
@@ -72,7 +72,7 @@ class Commits {
|
||||
commits = compareResult.data.commits.concat(commits);
|
||||
compareHead = `${commits[0].sha}^`;
|
||||
}
|
||||
core.info(`Found ${commits.length} commits from the GitHub API for ${owner}/${repo}`);
|
||||
core.info(`ℹ️ Found ${commits.length} commits from the GitHub API for ${owner}/${repo}`);
|
||||
return commits.map(commit => ({
|
||||
sha: commit.sha,
|
||||
summary: commit.commit.message.split('\n')[0],
|
||||
@@ -126,14 +126,27 @@ exports.DefaultConfiguration = {
|
||||
template: '${{CHANGELOG}}',
|
||||
pr_template: '- ${{TITLE}}\n - PR: #${{NUMBER}}',
|
||||
empty_template: '- no changes',
|
||||
categories: [],
|
||||
categories: [
|
||||
{
|
||||
title: '## 🚀 Features',
|
||||
labels: ['feature']
|
||||
},
|
||||
{
|
||||
title: '## 🐛 Fixes',
|
||||
labels: ['fix']
|
||||
},
|
||||
{
|
||||
title: '## 🧪 Tests',
|
||||
labels: ['test']
|
||||
}
|
||||
],
|
||||
transformers: [] // transformers to apply on the PR description according to the `pr_template`
|
||||
};
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 9621:
|
||||
/***/ 353:
|
||||
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
@@ -169,39 +182,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.createCommandManager = void 0;
|
||||
const exec = __importStar(__webpack_require__(1514));
|
||||
const fs = __importStar(__webpack_require__(5747));
|
||||
const io = __importStar(__webpack_require__(7436));
|
||||
const utils_1 = __webpack_require__(918);
|
||||
function createCommandManager(workingDirectory) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return yield GitCommandManager.createCommandManager(workingDirectory);
|
||||
});
|
||||
}
|
||||
exports.createCommandManager = createCommandManager;
|
||||
function directoryExistsSync(path, required) {
|
||||
if (!path) {
|
||||
throw new Error("Arg 'path' must not be empty");
|
||||
}
|
||||
let stats;
|
||||
try {
|
||||
stats = fs.statSync(path);
|
||||
}
|
||||
catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
if (!required) {
|
||||
return false;
|
||||
}
|
||||
throw new Error(`Directory '${path}' does not exist`);
|
||||
}
|
||||
throw new Error(`Encountered an error when checking whether path '${path}' exists: ${error.message}`);
|
||||
}
|
||||
if (stats.isDirectory()) {
|
||||
return true;
|
||||
}
|
||||
else if (!required) {
|
||||
return false;
|
||||
}
|
||||
throw new Error(`Directory '${path}' does not exist`);
|
||||
}
|
||||
class GitCommandManager {
|
||||
// Private constructor; use createCommandManager()
|
||||
constructor() {
|
||||
@@ -237,7 +225,7 @@ class GitCommandManager {
|
||||
}
|
||||
execGit(args, allowAllExitCodes = false, silent = false) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
directoryExistsSync(this.workingDirectory, true);
|
||||
utils_1.directoryExistsSync(this.workingDirectory, true);
|
||||
const result = new GitOutput();
|
||||
const stdout = [];
|
||||
const options = {
|
||||
@@ -309,11 +297,13 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const core = __importStar(__webpack_require__(2186));
|
||||
const utils_1 = __webpack_require__(918);
|
||||
const releaseNotes_1 = __webpack_require__(5882);
|
||||
const git_helper_1 = __webpack_require__(9621);
|
||||
const gitHelper_1 = __webpack_require__(353);
|
||||
const github = __importStar(__webpack_require__(5438));
|
||||
const path = __importStar(__webpack_require__(5622));
|
||||
const configuration_1 = __webpack_require__(5527);
|
||||
function run() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
core.startGroup(`📘 Reading input values`);
|
||||
try {
|
||||
let githubWorkspacePath = process.env['GITHUB_WORKSPACE'];
|
||||
if (!githubWorkspacePath) {
|
||||
@@ -325,17 +315,27 @@ function run() {
|
||||
repositoryPath = path.resolve(githubWorkspacePath, repositoryPath);
|
||||
core.debug(`repositoryPath = '${repositoryPath}'`);
|
||||
const configurationFile = core.getInput('configuration');
|
||||
const configurationPath = path.resolve(githubWorkspacePath, configurationFile);
|
||||
core.debug(`configurationPath = '${configurationPath}'`);
|
||||
const configuration = utils_1.readConfiguration(configurationPath);
|
||||
let configuration = configuration_1.DefaultConfiguration;
|
||||
if (configurationFile) {
|
||||
const configurationPath = path.resolve(githubWorkspacePath, configurationFile);
|
||||
core.debug(`configurationPath = '${configurationPath}'`);
|
||||
const providedConfiguration = utils_1.readConfiguration(configurationPath);
|
||||
if (!providedConfiguration) {
|
||||
core.info(`⚠️ Configuration provided, but it couldn't be found, or failed to parse. Fallback to Defaults`);
|
||||
}
|
||||
else {
|
||||
configuration = providedConfiguration;
|
||||
}
|
||||
}
|
||||
const token = core.getInput('token');
|
||||
let owner = core.getInput('owner');
|
||||
let repo = core.getInput('repo');
|
||||
const fromTag = core.getInput('fromTag');
|
||||
let toTag = core.getInput('toTag');
|
||||
const ignorePreReleases = core.getInput('ignorePreReleases');
|
||||
if (!toTag) {
|
||||
// if not specified try to retrieve tag from git
|
||||
const gitHelper = yield git_helper_1.createCommandManager(repositoryPath);
|
||||
const gitHelper = yield gitHelper_1.createCommandManager(repositoryPath);
|
||||
const latestTag = yield gitHelper.latestTag();
|
||||
toTag = latestTag;
|
||||
core.debug(`toTag = '${latestTag}'`);
|
||||
@@ -355,31 +355,33 @@ function run() {
|
||||
repo = splitRepository[1];
|
||||
}
|
||||
if (!owner) {
|
||||
core.error(`Missing or couldn't resolve 'owner'`);
|
||||
core.error(`💥 Missing or couldn't resolve 'owner'`);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
core.debug(`Resolved 'owner' as ${owner}`);
|
||||
}
|
||||
if (!repo) {
|
||||
core.error(`Missing or couldn't resolve 'owner'`);
|
||||
core.error(`💥 Missing or couldn't resolve 'owner'`);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
core.debug(`Resolved 'repo' as ${repo}`);
|
||||
}
|
||||
if (!toTag) {
|
||||
core.error(`Missing or couldn't resolve 'toTag'`);
|
||||
core.error(`💥 Missing or couldn't resolve 'toTag'`);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
core.debug(`Resolved 'toTag' as ${toTag}`);
|
||||
}
|
||||
core.endGroup();
|
||||
const releaseNotes = new releaseNotes_1.ReleaseNotes({
|
||||
owner,
|
||||
repo,
|
||||
fromTag,
|
||||
toTag,
|
||||
ignorePreReleases: ignorePreReleases === 'true',
|
||||
configuration
|
||||
});
|
||||
core.setOutput('changelog', yield releaseNotes.pull(token));
|
||||
@@ -446,6 +448,7 @@ class PullRequests {
|
||||
this.octokit = octokit;
|
||||
}
|
||||
getSingle(owner, repo, prNumber) {
|
||||
var _a, _b, _c;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
const pr = yield this.octokit.pulls.get({
|
||||
@@ -463,17 +466,25 @@ class PullRequests {
|
||||
labels: pr.data.labels.map(function (label) {
|
||||
return label.name;
|
||||
}),
|
||||
body: pr.data.body
|
||||
milestone: (_a = pr.data.milestone) === null || _a === void 0 ? void 0 : _a.title,
|
||||
body: pr.data.body,
|
||||
assignees: (_b = pr.data.assignees) === null || _b === void 0 ? void 0 : _b.map(function (asignee) {
|
||||
return asignee.login;
|
||||
}),
|
||||
requestedReviewers: (_c = pr.data.requested_reviewers) === null || _c === void 0 ? void 0 : _c.map(function (reviewer) {
|
||||
return reviewer.login;
|
||||
})
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
core.warning(`Cannot find PR ${owner}/${repo}#${prNumber} - ${e.message}`);
|
||||
core.warning(`⚠️ Cannot find PR ${owner}/${repo}#${prNumber} - ${e.message}`);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
getBetweenDates(owner, repo, fromDate, toDate, maxPullRequests) {
|
||||
var e_1, _a;
|
||||
var _b, _c, _d, _e;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const mergedPRs = [];
|
||||
const options = this.octokit.pulls.list.endpoint.merge({
|
||||
@@ -484,8 +495,8 @@ class PullRequests {
|
||||
direction: 'desc'
|
||||
});
|
||||
try {
|
||||
for (var _b = __asyncValues(this.octokit.paginate.iterator(options)), _c; _c = yield _b.next(), !_c.done;) {
|
||||
const response = _c.value;
|
||||
for (var _f = __asyncValues(this.octokit.paginate.iterator(options)), _g; _g = yield _f.next(), !_g.done;) {
|
||||
const response = _g.value;
|
||||
const prs = response.data;
|
||||
for (const pr of prs.filter(p => !!p.merged_at)) {
|
||||
mergedPRs.push({
|
||||
@@ -495,17 +506,24 @@ class PullRequests {
|
||||
mergedAt: moment_1.default(pr.merged_at),
|
||||
author: pr.user.login,
|
||||
repoName: pr.base.repo.full_name,
|
||||
labels: pr.labels.map(function (label) {
|
||||
labels: (_b = pr.labels) === null || _b === void 0 ? void 0 : _b.map(function (label) {
|
||||
return label.name;
|
||||
}),
|
||||
body: pr.body
|
||||
milestone: (_c = pr.milestone) === null || _c === void 0 ? void 0 : _c.title,
|
||||
body: pr.body,
|
||||
assignees: (_d = pr.assignees) === null || _d === void 0 ? void 0 : _d.map(function (asignee) {
|
||||
return asignee.login;
|
||||
}),
|
||||
requestedReviewers: (_e = pr.requested_reviewers) === null || _e === void 0 ? void 0 : _e.map(function (reviewer) {
|
||||
return reviewer.login;
|
||||
})
|
||||
});
|
||||
}
|
||||
const firstPR = prs[0];
|
||||
if ((firstPR.merged_at && fromDate.isAfter(moment_1.default(firstPR.merged_at))) ||
|
||||
mergedPRs.length >= maxPullRequests) {
|
||||
if (mergedPRs.length >= maxPullRequests) {
|
||||
core.info(`Reached 'maxPullRequests' count ${maxPullRequests}`);
|
||||
core.info(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests}`);
|
||||
}
|
||||
// bail out early to not keep iterating on PRs super old
|
||||
return sortPullRequests(mergedPRs, true);
|
||||
@@ -515,7 +533,7 @@ class PullRequests {
|
||||
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
||||
finally {
|
||||
try {
|
||||
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
|
||||
if (_g && !_g.done && (_a = _f.return)) yield _a.call(_f);
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
@@ -626,67 +644,72 @@ class ReleaseNotes {
|
||||
this.options = options;
|
||||
}
|
||||
pull(token) {
|
||||
var _a, _b, _c;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const octokit = new rest_1.Octokit({
|
||||
auth: `token ${token || process.env.GITHUB_TOKEN}`
|
||||
});
|
||||
const { owner, repo, toTag, configuration } = this.options;
|
||||
const { owner, repo, toTag, ignorePreReleases, 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_1.Tags(octokit);
|
||||
const previousTag = yield tagsApi.findPredecessorTag(owner, repo, toTag, configuration.max_tags_to_fetch
|
||||
? configuration.max_tags_to_fetch
|
||||
: configuration_1.DefaultConfiguration.max_tags_to_fetch);
|
||||
const previousTag = yield tagsApi.findPredecessorTag(owner, repo, toTag, ignorePreReleases, (_a = configuration.max_tags_to_fetch) !== null && _a !== void 0 ? _a : configuration_1.DefaultConfiguration.max_tags_to_fetch);
|
||||
if (previousTag == null) {
|
||||
core.error(`Unable to retrieve previous tag given ${toTag}`);
|
||||
return configuration.empty_template
|
||||
? configuration.empty_template
|
||||
: configuration_1.DefaultConfiguration.empty_template;
|
||||
core.error(`💥 Unable to retrieve previous tag given ${toTag}`);
|
||||
return ((_b = configuration.empty_template) !== null && _b !== void 0 ? _b : configuration_1.DefaultConfiguration.empty_template);
|
||||
}
|
||||
this.options.fromTag = previousTag.name;
|
||||
core.debug(`fromTag resolved via previousTag as: ${previousTag.name}`);
|
||||
core.endGroup();
|
||||
}
|
||||
core.startGroup(`🚀 Load pull requests`);
|
||||
const mergedPullRequests = yield this.getMergedPullRequests(octokit);
|
||||
core.endGroup();
|
||||
if (mergedPullRequests.length === 0) {
|
||||
core.warning(`No pull requests found for between ${this.options.fromTag}...${toTag}`);
|
||||
return configuration.empty_template
|
||||
? configuration.empty_template
|
||||
: configuration_1.DefaultConfiguration.empty_template;
|
||||
core.warning(`⚠️ No pull requests found`);
|
||||
return (_c = configuration.empty_template) !== null && _c !== void 0 ? _c : configuration_1.DefaultConfiguration.empty_template;
|
||||
}
|
||||
return transform_1.buildChangelog(mergedPullRequests, configuration);
|
||||
core.startGroup('📦 Build changelog');
|
||||
const resultChangelog = transform_1.buildChangelog(mergedPullRequests, configuration);
|
||||
core.endGroup();
|
||||
return resultChangelog;
|
||||
});
|
||||
}
|
||||
getMergedPullRequests(octokit) {
|
||||
var _a, _b, _c;
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const { owner, repo, fromTag, toTag, configuration } = this.options;
|
||||
core.info(`Comparing ${owner}/${repo} - ${fromTag}...${toTag}`);
|
||||
core.info(`ℹ️ Comparing ${owner}/${repo} - '${fromTag}...${toTag}'`);
|
||||
const commitsApi = new commits_1.Commits(octokit);
|
||||
const commits = yield commitsApi.getDiff(owner, repo, fromTag, toTag);
|
||||
let commits;
|
||||
try {
|
||||
commits = yield commitsApi.getDiff(owner, repo, fromTag, toTag);
|
||||
}
|
||||
catch (error) {
|
||||
core.error(`💥 Failed to retrieve - Invalid tag? - Because of: ${error}`);
|
||||
return [];
|
||||
}
|
||||
if (commits.length === 0) {
|
||||
core.warning(`💥 No commits found between - ${fromTag}...${toTag}`);
|
||||
return [];
|
||||
}
|
||||
const firstCommit = commits[0];
|
||||
const lastCommit = commits[commits.length - 1];
|
||||
let fromDate = firstCommit.date;
|
||||
const toDate = lastCommit.date;
|
||||
const maxDays = configuration.max_back_track_time_days
|
||||
? configuration.max_back_track_time_days
|
||||
: configuration_1.DefaultConfiguration.max_back_track_time_days;
|
||||
const maxDays = (_a = configuration.max_back_track_time_days) !== null && _a !== void 0 ? _a : configuration_1.DefaultConfiguration.max_back_track_time_days;
|
||||
const maxFromDate = toDate.clone().subtract(maxDays, 'days');
|
||||
if (maxFromDate.isAfter(fromDate)) {
|
||||
core.info(`Adjusted 'fromDate' to go max ${maxDays} back`);
|
||||
core.info(`⚠️ Adjusted 'fromDate' to go max ${maxDays} back`);
|
||||
fromDate = maxFromDate;
|
||||
}
|
||||
core.info(`Fetching PRs between dates ${fromDate.toISOString()} to ${toDate.toISOString()} for ${owner}/${repo}`);
|
||||
core.info(`ℹ️ Fetching PRs between dates ${fromDate.toISOString()} to ${toDate.toISOString()} for ${owner}/${repo}`);
|
||||
const pullRequestsApi = new pullRequests_1.PullRequests(octokit);
|
||||
const pullRequests = yield pullRequestsApi.getBetweenDates(owner, repo, fromDate, toDate, configuration.max_pull_requests
|
||||
? configuration.max_pull_requests
|
||||
: configuration_1.DefaultConfiguration.max_pull_requests);
|
||||
core.info(`Retrieved ${pullRequests.length} merged PRs for ${owner}/${repo}`);
|
||||
const prCommits = pullRequestsApi.filterCommits(commits, configuration.exclude_merge_branches
|
||||
? configuration.exclude_merge_branches
|
||||
: configuration_1.DefaultConfiguration.exclude_merge_branches);
|
||||
core.info(`Retrieved ${prCommits.length} PR merge commits for ${owner}/${repo}`);
|
||||
const pullRequests = yield pullRequestsApi.getBetweenDates(owner, repo, fromDate, toDate, (_b = configuration.max_pull_requests) !== null && _b !== void 0 ? _b : configuration_1.DefaultConfiguration.max_pull_requests);
|
||||
core.info(`ℹ️ Retrieved ${pullRequests.length} merged PRs for ${owner}/${repo}`);
|
||||
const prCommits = pullRequestsApi.filterCommits(commits, (_c = configuration.exclude_merge_branches) !== null && _c !== void 0 ? _c : configuration_1.DefaultConfiguration.exclude_merge_branches);
|
||||
core.info(`ℹ️ Retrieved ${prCommits.length} PR merge commits for ${owner}/${repo}`);
|
||||
const filteredPullRequests = [];
|
||||
const pullRequestsByNumber = {};
|
||||
for (const pr of pullRequests) {
|
||||
@@ -706,11 +729,11 @@ class ReleaseNotes {
|
||||
filteredPullRequests.push(pullRequest);
|
||||
}
|
||||
else {
|
||||
core.warning(`${prRef} not found! Commit text: ${commit.summary}`);
|
||||
core.warning(`⚠️ ${prRef} not found! Commit text: ${commit.summary}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
core.info(`${prRef} not in date range, excluding from changelog`);
|
||||
core.info(`ℹ️ ${prRef} not in date range, excluding from changelog`);
|
||||
}
|
||||
}
|
||||
return filteredPullRequests;
|
||||
@@ -802,23 +825,49 @@ class Tags {
|
||||
}
|
||||
finally { if (e_1) throw e_1.error; }
|
||||
}
|
||||
core.info(`Found ${tagsInfo.length} (fetching max: ${maxTagsToFetch}) tags from the GitHub API for ${owner}/${repo}`);
|
||||
core.info(`ℹ️ Found ${tagsInfo.length} (fetching max: ${maxTagsToFetch}) tags from the GitHub API for ${owner}/${repo}`);
|
||||
return tagsInfo;
|
||||
});
|
||||
}
|
||||
findPredecessorTag(owner, repo, tag, maxTagsToFetch) {
|
||||
findPredecessorTag(owner, repo, tag, ignorePreReleases, maxTagsToFetch) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const tags = this.sortTags(yield this.getTags(owner, repo, maxTagsToFetch));
|
||||
const length = tags.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (tags[i].name.toLowerCase() === tag.toLowerCase()) {
|
||||
return tags[i + 1];
|
||||
try {
|
||||
const length = tags.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (tags[i].name.toLowerCase() === tag.toLowerCase()) {
|
||||
if (ignorePreReleases) {
|
||||
core.info(`ℹ️ Enabled 'ignorePreReleases', searching for the closest release`);
|
||||
for (let ii = i + 1; ii < length; ii++) {
|
||||
if (!tags[ii].name.includes('-')) {
|
||||
return tags[ii];
|
||||
}
|
||||
}
|
||||
}
|
||||
return tags[i + 1];
|
||||
}
|
||||
}
|
||||
return tags[0];
|
||||
}
|
||||
catch (error) {
|
||||
return null;
|
||||
}
|
||||
// not found, throw exception?
|
||||
return tags[0];
|
||||
});
|
||||
}
|
||||
/*
|
||||
Sorts an array of tags as shown below:
|
||||
|
||||
2020.4.0
|
||||
2020.4.0-rc02
|
||||
2020.3.2
|
||||
2020.3.1
|
||||
2020.3.1-rc03
|
||||
2020.3.1-rc02
|
||||
2020.3.1-rc01
|
||||
2020.3.1-b01
|
||||
2020.3.1-a01
|
||||
2020.3.0
|
||||
*/
|
||||
sortTags(commits) {
|
||||
commits.sort((b, a) => {
|
||||
const partsA = a.name.replace(/^v/, '').split('-');
|
||||
@@ -843,22 +892,6 @@ class Tags {
|
||||
}
|
||||
}
|
||||
exports.Tags = Tags;
|
||||
/*
|
||||
|
||||
2020.3.2 ( should resolve 2020.3.1 )
|
||||
|
||||
2020.4.0
|
||||
2020.4.0-rc02
|
||||
|
||||
2020.3.1
|
||||
2020.3.1-rc03
|
||||
2020.3.1-rc02
|
||||
2020.3.1-rc01
|
||||
2020.3.1-b01
|
||||
2020.3.1-a01
|
||||
|
||||
2020.3.0
|
||||
*/
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -893,9 +926,12 @@ const pullRequests_1 = __webpack_require__(4217);
|
||||
const core = __importStar(__webpack_require__(2186));
|
||||
const configuration_1 = __webpack_require__(5527);
|
||||
function buildChangelog(prs, config) {
|
||||
var _a, _b, _c;
|
||||
// sort to target order
|
||||
prs = pullRequests_1.sortPullRequests(prs, (config.sort ? config.sort : configuration_1.DefaultConfiguration.sort).toUpperCase() ===
|
||||
'ASC');
|
||||
const sort = (_a = config.sort) !== null && _a !== void 0 ? _a : configuration_1.DefaultConfiguration.sort;
|
||||
const sortAsc = sort.toUpperCase() === 'ASC';
|
||||
prs = pullRequests_1.sortPullRequests(prs, sortAsc);
|
||||
core.info(`ℹ️ Sorted all pull requests ascending: ${sort}`);
|
||||
const validatedTransformers = validateTransfomers(config.transformers);
|
||||
const transformedMap = new Map();
|
||||
// convert PRs to their text representation
|
||||
@@ -904,12 +940,13 @@ function buildChangelog(prs, config) {
|
||||
? config.pr_template
|
||||
: configuration_1.DefaultConfiguration.pr_template), validatedTransformers));
|
||||
}
|
||||
core.info(`ℹ️ Used ${validateTransfomers.length} transformers to adjust message`);
|
||||
core.info(`✒️ Wrote messages for ${prs.length} pull requests`);
|
||||
// bring PRs into the order of categories
|
||||
const categorized = new Map();
|
||||
if (config.categories) {
|
||||
for (const category of config.categories) {
|
||||
categorized.set(category, []);
|
||||
}
|
||||
const categories = (_b = config.categories) !== null && _b !== void 0 ? _b : configuration_1.DefaultConfiguration.categories;
|
||||
for (const category of categories) {
|
||||
categorized.set(category, []);
|
||||
}
|
||||
const uncategorized = [];
|
||||
// bring elements in order
|
||||
@@ -925,6 +962,7 @@ function buildChangelog(prs, config) {
|
||||
uncategorized.push(body);
|
||||
}
|
||||
}
|
||||
core.info(`ℹ️ Ordered all pull requests into ${categories.length} categories`);
|
||||
// construct final changelog
|
||||
let changelog = '';
|
||||
for (const [category, pullRequests] of categorized) {
|
||||
@@ -937,16 +975,17 @@ function buildChangelog(prs, config) {
|
||||
changelog = `${changelog}\n`;
|
||||
}
|
||||
}
|
||||
core.info(`✒️ Wrote ${categorized.size} categorized pull requests down`);
|
||||
let changelogUncategorized = '';
|
||||
for (const pr of uncategorized) {
|
||||
changelogUncategorized = `${changelogUncategorized + pr}\n`;
|
||||
}
|
||||
core.info(`✒️ Wrote ${changelogUncategorized.length} non categorized pull requests down`);
|
||||
// fill template
|
||||
let transformedChangelog = config.template
|
||||
? config.template
|
||||
: configuration_1.DefaultConfiguration.template;
|
||||
let transformedChangelog = (_c = config.template) !== null && _c !== void 0 ? _c : configuration_1.DefaultConfiguration.template;
|
||||
transformedChangelog = transformedChangelog.replace('${{CHANGELOG}}', changelog);
|
||||
transformedChangelog = transformedChangelog.replace('${{UNCATEGORIZED}}', changelogUncategorized);
|
||||
core.info(`ℹ️ Filled template`);
|
||||
return transformedChangelog;
|
||||
}
|
||||
exports.buildChangelog = buildChangelog;
|
||||
@@ -954,13 +993,18 @@ function haveCommonElements(arr1, arr2) {
|
||||
return arr1.some(item => arr2.includes(item));
|
||||
}
|
||||
function fillTemplate(pr, template) {
|
||||
var _a, _b, _c, _d, _e, _f, _g;
|
||||
let transformed = template;
|
||||
transformed = transformed.replace('${{NUMBER}}', pr.number.toString());
|
||||
transformed = transformed.replace('${{TITLE}}', pr.title);
|
||||
transformed = transformed.replace('${{URL}}', pr.htmlURL);
|
||||
transformed = transformed.replace('${{MERGED_AT}}', pr.mergedAt.toISOString());
|
||||
transformed = transformed.replace('${{AUTHOR}}', pr.author);
|
||||
transformed = transformed.replace('${{LABELS}}', (_b = (_a = pr.labels) === null || _a === void 0 ? void 0 : _a.join(', ')) !== null && _b !== void 0 ? _b : '');
|
||||
transformed = transformed.replace('${{MILESTONE}}', (_c = pr.milestone) !== null && _c !== void 0 ? _c : '');
|
||||
transformed = transformed.replace('${{BODY}}', pr.body);
|
||||
transformed = transformed.replace('${{ASSIGNEES}}', (_e = (_d = pr.assignees) === null || _d === void 0 ? void 0 : _d.join(', ')) !== null && _e !== void 0 ? _e : '');
|
||||
transformed = transformed.replace('${{REVIEWERS}}', (_g = (_f = pr.requestedReviewers) === null || _f === void 0 ? void 0 : _f.join(', ')) !== null && _g !== void 0 ? _g : '');
|
||||
return transformed;
|
||||
}
|
||||
function transform(filled, transformers) {
|
||||
@@ -974,9 +1018,7 @@ function transform(filled, transformers) {
|
||||
return transformed;
|
||||
}
|
||||
function validateTransfomers(specifiedTransformers) {
|
||||
const transformers = specifiedTransformers
|
||||
? specifiedTransformers
|
||||
: configuration_1.DefaultConfiguration.transformers;
|
||||
const transformers = specifiedTransformers !== null && specifiedTransformers !== void 0 ? specifiedTransformers : configuration_1.DefaultConfiguration.transformers;
|
||||
return transformers
|
||||
.map(transformer => {
|
||||
try {
|
||||
@@ -986,7 +1028,7 @@ function validateTransfomers(specifiedTransformers) {
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
core.warning(`Bad replacer regex: ${transformer.pattern}`);
|
||||
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`);
|
||||
return {
|
||||
pattern: null,
|
||||
target: ''
|
||||
@@ -1024,14 +1066,45 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.readConfiguration = void 0;
|
||||
exports.directoryExistsSync = exports.readConfiguration = void 0;
|
||||
const fs = __importStar(__webpack_require__(5747));
|
||||
function readConfiguration(filename) {
|
||||
const rawdata = fs.readFileSync(filename, 'utf8');
|
||||
const configurationJSON = JSON.parse(rawdata);
|
||||
return configurationJSON;
|
||||
try {
|
||||
const rawdata = fs.readFileSync(filename, 'utf8');
|
||||
const configurationJSON = JSON.parse(rawdata);
|
||||
return configurationJSON;
|
||||
}
|
||||
catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
exports.readConfiguration = readConfiguration;
|
||||
function directoryExistsSync(path, required) {
|
||||
if (!path) {
|
||||
throw new Error("Arg 'path' must not be empty");
|
||||
}
|
||||
let stats;
|
||||
try {
|
||||
stats = fs.statSync(path);
|
||||
}
|
||||
catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
if (!required) {
|
||||
return false;
|
||||
}
|
||||
throw new Error(`Directory '${path}' does not exist`);
|
||||
}
|
||||
throw new Error(`Encountered an error when checking whether path '${path}' exists: ${error.message}`);
|
||||
}
|
||||
if (stats.isDirectory()) {
|
||||
return true;
|
||||
}
|
||||
else if (!required) {
|
||||
return false;
|
||||
}
|
||||
throw new Error(`Directory '${path}' does not exist`);
|
||||
}
|
||||
exports.directoryExistsSync = directoryExistsSync;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -55,7 +55,7 @@ export class Commits {
|
||||
}
|
||||
|
||||
core.info(
|
||||
`Found ${commits.length} commits from the GitHub API for ${owner}/${repo}`
|
||||
`ℹ️ Found ${commits.length} commits from the GitHub API for ${owner}/${repo}`
|
||||
)
|
||||
return commits.map(commit => ({
|
||||
sha: commit.sha,
|
||||
|
||||
+14
-1
@@ -30,6 +30,19 @@ export const DefaultConfiguration: Configuration = {
|
||||
template: '${{CHANGELOG}}', // the global template to host the changelog
|
||||
pr_template: '- ${{TITLE}}\n - PR: #${{NUMBER}}', // the per PR template to pick
|
||||
empty_template: '- no changes', // the template to use if no pull requests are found
|
||||
categories: [], // the categories to support for the ordering
|
||||
categories: [
|
||||
{
|
||||
title: '## 🚀 Features',
|
||||
labels: ['feature']
|
||||
},
|
||||
{
|
||||
title: '## 🐛 Fixes',
|
||||
labels: ['fix']
|
||||
},
|
||||
{
|
||||
title: '## 🧪 Tests',
|
||||
labels: ['test']
|
||||
}
|
||||
], // the categories to support for the ordering
|
||||
transformers: [] // transformers to apply on the PR description according to the `pr_template`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as exec from '@actions/exec'
|
||||
import * as fs from 'fs'
|
||||
import * as io from '@actions/io'
|
||||
import {directoryExistsSync} from './utils'
|
||||
|
||||
export async function createCommandManager(
|
||||
workingDirectory: string
|
||||
@@ -8,37 +8,6 @@ export async function createCommandManager(
|
||||
return await GitCommandManager.createCommandManager(workingDirectory)
|
||||
}
|
||||
|
||||
function directoryExistsSync(path: string, required?: boolean): boolean {
|
||||
if (!path) {
|
||||
throw new Error("Arg 'path' must not be empty")
|
||||
}
|
||||
|
||||
let stats: fs.Stats
|
||||
try {
|
||||
stats = fs.statSync(path)
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
if (!required) {
|
||||
return false
|
||||
}
|
||||
|
||||
throw new Error(`Directory '${path}' does not exist`)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Encountered an error when checking whether path '${path}' exists: ${error.message}`
|
||||
)
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return true
|
||||
} else if (!required) {
|
||||
return false
|
||||
}
|
||||
|
||||
throw new Error(`Directory '${path}' does not exist`)
|
||||
}
|
||||
|
||||
class GitCommandManager {
|
||||
private gitPath = ''
|
||||
private workingDirectory = ''
|
||||
+23
-10
@@ -1,11 +1,13 @@
|
||||
import * as core from '@actions/core'
|
||||
import {readConfiguration} from './utils'
|
||||
import {ReleaseNotes} from './releaseNotes'
|
||||
import {createCommandManager} from './git-helper'
|
||||
import {createCommandManager} from './gitHelper'
|
||||
import * as github from '@actions/github'
|
||||
import * as path from 'path'
|
||||
import {DefaultConfiguration} from './configuration'
|
||||
|
||||
async function run(): Promise<void> {
|
||||
core.startGroup(`📘 Reading input values`)
|
||||
try {
|
||||
let githubWorkspacePath = process.env['GITHUB_WORKSPACE']
|
||||
if (!githubWorkspacePath) {
|
||||
@@ -19,12 +21,22 @@ async function run(): Promise<void> {
|
||||
core.debug(`repositoryPath = '${repositoryPath}'`)
|
||||
|
||||
const configurationFile: string = core.getInput('configuration')
|
||||
const configurationPath = path.resolve(
|
||||
githubWorkspacePath,
|
||||
configurationFile
|
||||
)
|
||||
core.debug(`configurationPath = '${configurationPath}'`)
|
||||
const configuration = readConfiguration(configurationPath)
|
||||
let configuration = DefaultConfiguration
|
||||
if (configurationFile) {
|
||||
const configurationPath = path.resolve(
|
||||
githubWorkspacePath,
|
||||
configurationFile
|
||||
)
|
||||
core.debug(`configurationPath = '${configurationPath}'`)
|
||||
const providedConfiguration = readConfiguration(configurationPath)
|
||||
if (!providedConfiguration) {
|
||||
core.info(
|
||||
`⚠️ Configuration provided, but it couldn't be found, or failed to parse. Fallback to Defaults`
|
||||
)
|
||||
} else {
|
||||
configuration = providedConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
const token = core.getInput('token')
|
||||
let owner = core.getInput('owner')
|
||||
@@ -64,25 +76,26 @@ async function run(): Promise<void> {
|
||||
}
|
||||
|
||||
if (!owner) {
|
||||
core.error(`Missing or couldn't resolve 'owner'`)
|
||||
core.error(`💥 Missing or couldn't resolve 'owner'`)
|
||||
return
|
||||
} else {
|
||||
core.debug(`Resolved 'owner' as ${owner}`)
|
||||
}
|
||||
|
||||
if (!repo) {
|
||||
core.error(`Missing or couldn't resolve 'owner'`)
|
||||
core.error(`💥 Missing or couldn't resolve 'owner'`)
|
||||
return
|
||||
} else {
|
||||
core.debug(`Resolved 'repo' as ${repo}`)
|
||||
}
|
||||
|
||||
if (!toTag) {
|
||||
core.error(`Missing or couldn't resolve 'toTag'`)
|
||||
core.error(`💥 Missing or couldn't resolve 'toTag'`)
|
||||
return
|
||||
} else {
|
||||
core.debug(`Resolved 'toTag' as ${toTag}`)
|
||||
}
|
||||
core.endGroup()
|
||||
|
||||
const releaseNotes = new ReleaseNotes({
|
||||
owner,
|
||||
|
||||
+26
-5
@@ -12,7 +12,10 @@ export interface PullRequestInfo {
|
||||
author: string
|
||||
repoName: string
|
||||
labels: string[]
|
||||
milestone: string
|
||||
body: string
|
||||
assignees: string[]
|
||||
requestedReviewers: string[]
|
||||
}
|
||||
|
||||
export class PullRequests {
|
||||
@@ -40,10 +43,21 @@ export class PullRequests {
|
||||
labels: pr.data.labels.map(function (label) {
|
||||
return label.name
|
||||
}),
|
||||
body: pr.data.body
|
||||
milestone: pr.data.milestone?.title,
|
||||
body: pr.data.body,
|
||||
assignees: pr.data.assignees?.map(function (asignee) {
|
||||
return asignee.login
|
||||
}),
|
||||
requestedReviewers: pr.data.requested_reviewers?.map(function (
|
||||
reviewer
|
||||
) {
|
||||
return reviewer.login
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
core.warning(`Cannot find PR ${owner}/${repo}#${prNumber} - ${e.message}`)
|
||||
core.warning(
|
||||
`⚠️ Cannot find PR ${owner}/${repo}#${prNumber} - ${e.message}`
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -76,10 +90,17 @@ export class PullRequests {
|
||||
mergedAt: moment(pr.merged_at),
|
||||
author: pr.user.login,
|
||||
repoName: pr.base.repo.full_name,
|
||||
labels: pr.labels.map(function (label) {
|
||||
labels: pr.labels?.map(function (label) {
|
||||
return label.name
|
||||
}),
|
||||
body: pr.body
|
||||
milestone: pr.milestone?.title,
|
||||
body: pr.body,
|
||||
assignees: pr.assignees?.map(function (asignee) {
|
||||
return asignee.login
|
||||
}),
|
||||
requestedReviewers: pr.requested_reviewers?.map(function (reviewer) {
|
||||
return reviewer.login
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -89,7 +110,7 @@ export class PullRequests {
|
||||
mergedPRs.length >= maxPullRequests
|
||||
) {
|
||||
if (mergedPRs.length >= maxPullRequests) {
|
||||
core.info(`Reached 'maxPullRequests' count ${maxPullRequests}`)
|
||||
core.info(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests}`)
|
||||
}
|
||||
|
||||
// bail out early to not keep iterating on PRs super old
|
||||
|
||||
+38
-34
@@ -1,5 +1,5 @@
|
||||
import {Octokit} from '@octokit/rest'
|
||||
import {Commits} from './commits'
|
||||
import {Commits, CommitInfo} from './commits'
|
||||
import {PullRequestInfo, PullRequests} from './pullRequests'
|
||||
import {buildChangelog} from './transform'
|
||||
import * as core from '@actions/core'
|
||||
@@ -26,6 +26,7 @@ export class ReleaseNotes {
|
||||
const {owner, repo, toTag, ignorePreReleases, 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)
|
||||
|
||||
@@ -34,45 +35,51 @@ export class ReleaseNotes {
|
||||
repo,
|
||||
toTag,
|
||||
ignorePreReleases,
|
||||
configuration.max_tags_to_fetch
|
||||
? configuration.max_tags_to_fetch
|
||||
: DefaultConfiguration.max_tags_to_fetch
|
||||
configuration.max_tags_to_fetch ??
|
||||
DefaultConfiguration.max_tags_to_fetch
|
||||
)
|
||||
if (previousTag == null) {
|
||||
core.error(`Unable to retrieve previous tag given ${toTag}`)
|
||||
return configuration.empty_template
|
||||
? configuration.empty_template
|
||||
: DefaultConfiguration.empty_template
|
||||
core.error(`💥 Unable to retrieve previous tag given ${toTag}`)
|
||||
return (
|
||||
configuration.empty_template ?? DefaultConfiguration.empty_template
|
||||
)
|
||||
}
|
||||
|
||||
this.options.fromTag = previousTag.name
|
||||
core.debug(`fromTag resolved via previousTag as: ${previousTag.name}`)
|
||||
core.endGroup()
|
||||
}
|
||||
|
||||
core.startGroup(`🚀 Load pull requests`)
|
||||
const mergedPullRequests = await this.getMergedPullRequests(octokit)
|
||||
core.endGroup()
|
||||
|
||||
if (mergedPullRequests.length === 0) {
|
||||
core.warning(
|
||||
`No pull requests found for between ${this.options.fromTag}...${toTag}`
|
||||
)
|
||||
return configuration.empty_template
|
||||
? configuration.empty_template
|
||||
: DefaultConfiguration.empty_template
|
||||
core.warning(`⚠️ No pull requests found`)
|
||||
return configuration.empty_template ?? DefaultConfiguration.empty_template
|
||||
}
|
||||
|
||||
return buildChangelog(mergedPullRequests, configuration)
|
||||
core.startGroup('📦 Build changelog')
|
||||
const resultChangelog = buildChangelog(mergedPullRequests, configuration)
|
||||
core.endGroup()
|
||||
return resultChangelog
|
||||
}
|
||||
|
||||
private async getMergedPullRequests(
|
||||
octokit: Octokit
|
||||
): Promise<PullRequestInfo[]> {
|
||||
const {owner, repo, fromTag, toTag, configuration} = this.options
|
||||
core.info(`Comparing ${owner}/${repo} - ${fromTag}...${toTag}`)
|
||||
core.info(`ℹ️ Comparing ${owner}/${repo} - '${fromTag}...${toTag}'`)
|
||||
|
||||
const commitsApi = new Commits(octokit)
|
||||
const commits = await commitsApi.getDiff(owner, repo, fromTag!!, toTag)
|
||||
|
||||
let commits: CommitInfo[]
|
||||
try {
|
||||
commits = await commitsApi.getDiff(owner, repo, fromTag!!, toTag)
|
||||
} catch (error) {
|
||||
core.error(`💥 Failed to retrieve - Invalid tag? - Because of: ${error}`)
|
||||
return []
|
||||
}
|
||||
if (commits.length === 0) {
|
||||
core.warning(`💥 No commits found between - ${fromTag}...${toTag}`)
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -81,17 +88,17 @@ export class ReleaseNotes {
|
||||
let fromDate = firstCommit.date
|
||||
const toDate = lastCommit.date
|
||||
|
||||
const maxDays = configuration.max_back_track_time_days
|
||||
? configuration.max_back_track_time_days
|
||||
: DefaultConfiguration.max_back_track_time_days
|
||||
const maxDays =
|
||||
configuration.max_back_track_time_days ??
|
||||
DefaultConfiguration.max_back_track_time_days
|
||||
const maxFromDate = toDate.clone().subtract(maxDays, 'days')
|
||||
if (maxFromDate.isAfter(fromDate)) {
|
||||
core.info(`Adjusted 'fromDate' to go max ${maxDays} back`)
|
||||
core.info(`⚠️ Adjusted 'fromDate' to go max ${maxDays} back`)
|
||||
fromDate = maxFromDate
|
||||
}
|
||||
|
||||
core.info(
|
||||
`Fetching PRs between dates ${fromDate.toISOString()} to ${toDate.toISOString()} for ${owner}/${repo}`
|
||||
`ℹ️ Fetching PRs between dates ${fromDate.toISOString()} to ${toDate.toISOString()} for ${owner}/${repo}`
|
||||
)
|
||||
|
||||
const pullRequestsApi = new PullRequests(octokit)
|
||||
@@ -100,24 +107,21 @@ export class ReleaseNotes {
|
||||
repo,
|
||||
fromDate,
|
||||
toDate,
|
||||
configuration.max_pull_requests
|
||||
? configuration.max_pull_requests
|
||||
: DefaultConfiguration.max_pull_requests
|
||||
configuration.max_pull_requests ?? DefaultConfiguration.max_pull_requests
|
||||
)
|
||||
|
||||
core.info(
|
||||
`Retrieved ${pullRequests.length} merged PRs for ${owner}/${repo}`
|
||||
`ℹ️ Retrieved ${pullRequests.length} merged PRs for ${owner}/${repo}`
|
||||
)
|
||||
|
||||
const prCommits = pullRequestsApi.filterCommits(
|
||||
commits,
|
||||
configuration.exclude_merge_branches
|
||||
? configuration.exclude_merge_branches
|
||||
: DefaultConfiguration.exclude_merge_branches
|
||||
configuration.exclude_merge_branches ??
|
||||
DefaultConfiguration.exclude_merge_branches
|
||||
)
|
||||
|
||||
core.info(
|
||||
`Retrieved ${prCommits.length} PR merge commits for ${owner}/${repo}`
|
||||
`ℹ️ Retrieved ${prCommits.length} PR merge commits for ${owner}/${repo}`
|
||||
)
|
||||
|
||||
const filteredPullRequests = []
|
||||
@@ -146,10 +150,10 @@ export class ReleaseNotes {
|
||||
if (pullRequest) {
|
||||
filteredPullRequests.push(pullRequest)
|
||||
} else {
|
||||
core.warning(`${prRef} not found! Commit text: ${commit.summary}`)
|
||||
core.warning(`⚠️ ${prRef} not found! Commit text: ${commit.summary}`)
|
||||
}
|
||||
} else {
|
||||
core.info(`${prRef} not in date range, excluding from changelog`)
|
||||
core.info(`ℹ️ ${prRef} not in date range, excluding from changelog`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-19
@@ -40,7 +40,7 @@ export class Tags {
|
||||
}
|
||||
|
||||
core.info(
|
||||
`Found ${tagsInfo.length} (fetching max: ${maxTagsToFetch}) tags from the GitHub API for ${owner}/${repo}`
|
||||
`ℹ️ Found ${tagsInfo.length} (fetching max: ${maxTagsToFetch}) tags from the GitHub API for ${owner}/${repo}`
|
||||
)
|
||||
return tagsInfo
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export class Tags {
|
||||
if (tags[i].name.toLowerCase() === tag.toLowerCase()) {
|
||||
if (ignorePreReleases) {
|
||||
core.info(
|
||||
`Enabled 'ignorePreReleases', searching for the closest release`
|
||||
`ℹ️ Enabled 'ignorePreReleases', searching for the closest release`
|
||||
)
|
||||
for (let ii = i + 1; ii < length; ii++) {
|
||||
if (!tags[ii].name.includes('-')) {
|
||||
@@ -77,6 +77,20 @@ export class Tags {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Sorts an array of tags as shown below:
|
||||
|
||||
2020.4.0
|
||||
2020.4.0-rc02
|
||||
2020.3.2
|
||||
2020.3.1
|
||||
2020.3.1-rc03
|
||||
2020.3.1-rc02
|
||||
2020.3.1-rc01
|
||||
2020.3.1-b01
|
||||
2020.3.1-a01
|
||||
2020.3.0
|
||||
*/
|
||||
private sortTags(commits: TagInfo[]): TagInfo[] {
|
||||
commits.sort((b, a) => {
|
||||
const partsA = a.name.replace(/^v/, '').split('-')
|
||||
@@ -97,20 +111,3 @@ export class Tags {
|
||||
return commits
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
2020.3.2 ( should resolve 2020.3.1 )
|
||||
|
||||
2020.4.0
|
||||
2020.4.0-rc02
|
||||
|
||||
2020.3.1
|
||||
2020.3.1-rc03
|
||||
2020.3.1-rc02
|
||||
2020.3.1-rc01
|
||||
2020.3.1-b01
|
||||
2020.3.1-a01
|
||||
|
||||
2020.3.0
|
||||
*/
|
||||
|
||||
+31
-17
@@ -12,11 +12,10 @@ export function buildChangelog(
|
||||
config: Configuration
|
||||
): string {
|
||||
// sort to target order
|
||||
prs = sortPullRequests(
|
||||
prs,
|
||||
(config.sort ? config.sort : DefaultConfiguration.sort).toUpperCase() ===
|
||||
'ASC'
|
||||
)
|
||||
const sort = config.sort ?? DefaultConfiguration.sort
|
||||
const sortAsc = sort.toUpperCase() === 'ASC'
|
||||
prs = sortPullRequests(prs, sortAsc)
|
||||
core.info(`ℹ️ Sorted all pull requests ascending: ${sort}`)
|
||||
|
||||
const validatedTransformers = validateTransfomers(config.transformers)
|
||||
const transformedMap = new Map<PullRequestInfo, string>()
|
||||
@@ -35,13 +34,16 @@ export function buildChangelog(
|
||||
)
|
||||
)
|
||||
}
|
||||
core.info(
|
||||
`ℹ️ Used ${validateTransfomers.length} transformers to adjust message`
|
||||
)
|
||||
core.info(`✒️ Wrote messages for ${prs.length} pull requests`)
|
||||
|
||||
// bring PRs into the order of categories
|
||||
const categorized = new Map<Category, string[]>()
|
||||
if (config.categories) {
|
||||
for (const category of config.categories) {
|
||||
categorized.set(category, [])
|
||||
}
|
||||
const categories = config.categories ?? DefaultConfiguration.categories
|
||||
for (const category of categories) {
|
||||
categorized.set(category, [])
|
||||
}
|
||||
const uncategorized: string[] = []
|
||||
|
||||
@@ -60,6 +62,7 @@ export function buildChangelog(
|
||||
uncategorized.push(body)
|
||||
}
|
||||
}
|
||||
core.info(`ℹ️ Ordered all pull requests into ${categories.length} categories`)
|
||||
|
||||
// construct final changelog
|
||||
let changelog = ''
|
||||
@@ -75,16 +78,18 @@ export function buildChangelog(
|
||||
changelog = `${changelog}\n`
|
||||
}
|
||||
}
|
||||
core.info(`✒️ Wrote ${categorized.size} categorized pull requests down`)
|
||||
|
||||
let changelogUncategorized = ''
|
||||
for (const pr of uncategorized) {
|
||||
changelogUncategorized = `${changelogUncategorized + pr}\n`
|
||||
}
|
||||
core.info(
|
||||
`✒️ Wrote ${changelogUncategorized.length} non categorized pull requests down`
|
||||
)
|
||||
|
||||
// fill template
|
||||
let transformedChangelog = config.template
|
||||
? config.template
|
||||
: DefaultConfiguration.template
|
||||
let transformedChangelog = config.template ?? DefaultConfiguration.template
|
||||
transformedChangelog = transformedChangelog.replace(
|
||||
'${{CHANGELOG}}',
|
||||
changelog
|
||||
@@ -93,6 +98,7 @@ export function buildChangelog(
|
||||
'${{UNCATEGORIZED}}',
|
||||
changelogUncategorized
|
||||
)
|
||||
core.info(`ℹ️ Filled template`)
|
||||
return transformedChangelog
|
||||
}
|
||||
|
||||
@@ -107,7 +113,17 @@ function fillTemplate(pr: PullRequestInfo, template: string): string {
|
||||
transformed = transformed.replace('${{URL}}', pr.htmlURL)
|
||||
transformed = transformed.replace('${{MERGED_AT}}', pr.mergedAt.toISOString())
|
||||
transformed = transformed.replace('${{AUTHOR}}', pr.author)
|
||||
transformed = transformed.replace('${{LABELS}}', pr.labels?.join(', ') ?? '')
|
||||
transformed = transformed.replace('${{MILESTONE}}', pr.milestone ?? '')
|
||||
transformed = transformed.replace('${{BODY}}', pr.body)
|
||||
transformed = transformed.replace(
|
||||
'${{ASSIGNEES}}',
|
||||
pr.assignees?.join(', ') ?? ''
|
||||
)
|
||||
transformed = transformed.replace(
|
||||
'${{REVIEWERS}}',
|
||||
pr.requestedReviewers?.join(', ') ?? ''
|
||||
)
|
||||
return transformed
|
||||
}
|
||||
|
||||
@@ -125,10 +141,8 @@ function transform(filled: string, transformers: RegexTransformer[]): string {
|
||||
function validateTransfomers(
|
||||
specifiedTransformers: Transformer[]
|
||||
): RegexTransformer[] {
|
||||
const transformers = specifiedTransformers
|
||||
? specifiedTransformers
|
||||
: DefaultConfiguration.transformers
|
||||
|
||||
const transformers =
|
||||
specifiedTransformers ?? DefaultConfiguration.transformers
|
||||
return transformers
|
||||
.map(transformer => {
|
||||
try {
|
||||
@@ -137,7 +151,7 @@ function validateTransfomers(
|
||||
target: transformer.target
|
||||
}
|
||||
} catch (e) {
|
||||
core.warning(`Bad replacer regex: ${transformer.pattern}`)
|
||||
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`)
|
||||
return {
|
||||
pattern: null,
|
||||
target: ''
|
||||
|
||||
+39
-4
@@ -1,8 +1,43 @@
|
||||
import * as fs from 'fs'
|
||||
import {Configuration} from './configuration'
|
||||
|
||||
export function readConfiguration(filename: string): Configuration {
|
||||
const rawdata = fs.readFileSync(filename, 'utf8')
|
||||
const configurationJSON: Configuration = JSON.parse(rawdata)
|
||||
return configurationJSON
|
||||
export function readConfiguration(filename: string): Configuration | null {
|
||||
try {
|
||||
const rawdata = fs.readFileSync(filename, 'utf8')
|
||||
const configurationJSON: Configuration = JSON.parse(rawdata)
|
||||
return configurationJSON
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function directoryExistsSync(path: string, required?: boolean): boolean {
|
||||
if (!path) {
|
||||
throw new Error("Arg 'path' must not be empty")
|
||||
}
|
||||
|
||||
let stats: fs.Stats
|
||||
try {
|
||||
stats = fs.statSync(path)
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
if (!required) {
|
||||
return false
|
||||
}
|
||||
|
||||
throw new Error(`Directory '${path}' does not exist`)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Encountered an error when checking whether path '${path}' exists: ${error.message}`
|
||||
)
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return true
|
||||
} else if (!required) {
|
||||
return false
|
||||
}
|
||||
|
||||
throw new Error(`Directory '${path}' does not exist`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user