Merge pull request #40 from mikepenz/develop

dev -> main
This commit is contained in:
Mike Penz
2020-10-18 12:04:46 +02:00
committed by GitHub
10 changed files with 346 additions and 148 deletions
+26 -10
View File
@@ -10,10 +10,13 @@ jobs:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: |
- name: Checkout
uses: actions/checkout@v2
- name: Install NPM
run: |
npm install
- run: |
- name: Run NPM
run: |
npm run all
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -22,6 +25,8 @@ jobs:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
# the below actions use the local state of the action. please replace `./` with `mikepenz/release-changelog-builder-action@{latest-release}`
# Showcases how to use the action without a prior checkout
# Won't support providing a configuration file, as no checkout was done
- name: "Configuration without Checkout"
id: without_checkout
@@ -37,8 +42,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0 # Checkout full depth so tags can be discovered automatically if not specified
fetch-depth: 0
# Showcase the most minimal configuration possible
- name: "Minimal Configuration"
id: minimal_release
uses: ./
@@ -48,6 +54,7 @@ jobs:
- name: Echo Minimal Configuration Changelog
run: echo "${{steps.minimal_release.outputs.changelog}}"
# Showcases a more complex configuration, providing a configuration, and specifically referencing owner, repo, from and to tag
- name: "Complex Configuration"
id: complex_release
uses: ./
@@ -62,6 +69,21 @@ jobs:
- name: Echo Complex Configuration Changelog
run: echo "${{steps.complex_release.outputs.changelog}}"
# Showcases the capability to generate the changelog for an external repository provided
- name: "External Repo Configuration"
id: external_changelog
uses: ./
with:
configuration: "configs/configuration_complex.json"
owner: "mikepenz"
repo: "MaterialDrawer"
fromTag: "v8.1.0"
toTag: "v8.1.6"
token: ${{ secrets.PERSONAL_TOKEN }}
- name: Echo External Repo Configuration Changelog
run: echo "${{steps.external_changelog.outputs.changelog}}"
release:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
@@ -69,17 +91,11 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
- name: Prepare 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:
configuration: "configs/configuration_repo.json"
toTag: ${{ steps.tag_version.outputs.VERSION }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+97 -54
View File
@@ -6,7 +6,7 @@
</h1>
<p align="center">
... a github action that builds your release notes fast, easy and exactly the way you want.
... a GitHub action that builds your release notes fast, easy and exactly the way you want.
</p>
<div align="center">
@@ -21,9 +21,9 @@
<p align="center">
<a href="#whats-included-">What's included 🚀</a> &bull;
<a href="#setup">Setup 🛠️</a> &bull;
<a href="#full-sample-%EF%B8%8F">Full Sample 🖥️</a> &bull;
<a href="#customization-%EF%B8%8F">Customization 🖍️</a> &bull;
<a href="#contribute-">Contribute 🧬</a> &bull;
<a href="#complete-sample-%EF%B8%8F">Complete Sample 🖥️</a> &bull;
<a href="#license">License 📓</a>
</p>
@@ -32,13 +32,14 @@
### What's included 🚀
- Super simple integration
- even on huge repositories with hundreds of tags
- ...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
- Rich build log output
-------
@@ -51,26 +52,72 @@ Specify the action as part of your GitHub actions workflow:
```yml
- name: "Build Changelog"
id: build_changelog
if: startsWith(github.ref, 'refs/tags/')
uses: mikepenz/release-changelog-builder-action@{latest-release}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
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.
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.
### Action outputs
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`.
After action execution it will return the `changelog` and additional information as step output. You can use it in any follow-up step by referencing the output by referencing it via the id of the step. For example `build_changelog`.
```yml
# ${{steps.{CHANGELOG_STEP_ID}.outputs.changelog}}
${{steps.build_changelog.outputs.changelog}}
```
A full set list of possible output values for this action.
| **Output** | **Description** |
|---------------------|---------------------------------------------------------------------------------------------------------------------------|
| `outputs.changelog` | The built release changelog built from the merged pull requests |
| `outputs.owner` | Specifies the owner of the repository processed |
| `outputs.repo` | Describes the repository name, which was processed |
| `outputs.fromTag` | Defines the `fromTag` which describes the lower bound to process pull requests for |
| `outputs.toTag` | Defines the `toTag` which describes the upper bound to process pull request for |
| `outputs.failed` | Defines if there was an issue with the action run, and the changelog may not have been generated correctly. [true, false] |
## Full 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: Build Changelog
id: github_release
uses: mikepenz/release-changelog-builder-action@main
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 }}
```
## Customization 🖍️
### Changelog Configuration
### 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.
@@ -120,13 +167,15 @@ This configuration is a `.json` file in the following format.
}
```
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)
Any section of the configuration can be omitted to have defaults apply.
Defaults for the configuration can be found in the [configuration.ts](https://github.com/mikepenz/release-changelog-builder-action/blob/develop/src/configuration.ts)
Please see the [Configuration Specification](#configuration-specification) for detailed descriptions on the offered configuration options.
### Advanced workflow specification
For advanced usecases additional settings can be provided to the action
For advanced use cases additional settings can be provided to the action
```yml
- name: "Complex Configuration"
@@ -138,12 +187,26 @@ For advanced usecases additional settings can be provided to the action
owner: "mikepenz"
repo: "release-changelog-builder-action"
ignorePreReleases: "false"
fromTag: "0.0.2"
toTag: "0.0.3"
fromTag: "0.3.0"
toTag: "0.5.0"
token: ${{ secrets.PAT }}
```
💡 `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).
💡 All input values are optional. It is only required to provide the `token` either via the input, or as `env` variable.
| **Input** | **Description** |
|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `configuration` | Relative path, to the `configuration.json` file, providing additional configurations |
| `owner` | The owner of the repository to generate the changelog for |
| `repo` | Name of the repository we want to process |
| `fromTag` | Defines the 'start' from where the changelog will consider merged pull requests |
| `toTag` | Defines until which tag the changelog will consider merged pull requests |
| `path` | Allows to specify an alternative sub directory, to use as base |
| `token` | Alternative config to specify token. You should prefer `env.GITHUB_TOKEN` instead though |
| `ignorePreReleases` | Allows to ignore pre-releases for changelog generation (E.g. for 1.0.1... 1.0.0-rc02 <- ignore, 1.0.0 <- pick). Only used if `fromTag` was not specified. Default: false |
| `failOnError` | Defines if the action will result in a build failure if problems occurred. Default: false |
💡 `${{ secrets.GITHUB_TOKEN }}` only grants rights to the current repository, for other repositories please use a PAT (Personal Access Token).
### PR Template placeholders
@@ -171,47 +234,26 @@ Table of supported placeholders allowed to be used in the `pr_template` configur
| `${{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
## Complete Sample 🖥️
Table of descriptions for the `configuration.json` options.
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 }}
```
| **Input** | **Description** |
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| categories | An array of `category` specifications, offering a flexible way to group changes into categories |
| category.title | The display name of a category in the changelog |
| category.labels | An array of labels, to match pull request labels against. If any PR label, matches any category label, the pull request will show up under this category |
| sort | The sort order of pull requests. [ASC, DESC] |
| template | Specifies the global template to pick for creating the changelog. See [Template placeholders](#template-placeholders) for possible values |
| pr_template | Defines the per pull request template. See [PR Template placeholders](#pr-template-placeholders) for possible values |
| empty_template | Template to pick if no changes are detected. Does not support placeholders |
| transformers | An array of `transform` specifications, offering a flexible API to modify the text per pull request. This is applied on the change text created with `pr_template`. `transformers` are executed per change, in the order specified |
| transformer.pattern | A `regex` pattern, extracting values of the change message. |
| transformer.target | The result pattern, the regex groups will be filled into. Allows for full transformation of a pull request message. Including potentially specified texts |
| max_tags_to_fetch | The maximum amount of tags to load from the API to find the previous tag. Loaded paginated with 100 per page |
| max_pull_requests | The maximum amount of pull requests to load from the API. Loaded paginated with 30 per page |
| max_back_track_time_days | Defines the max amount of days to go back in time per changelog |
| exclude_merge_branches | An array of branches to be ignored from processing as merge commits |
## Contribute 🧬
@@ -229,7 +271,7 @@ $ npm test
$ npm run lint -- --fix
```
It's suggested to export the token to your path before running the tests, so that 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
@@ -248,7 +290,8 @@ Core parts of the PR fetching logic are based on [pull-release-notes](https://gi
## 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.
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
+48 -6
View File
@@ -1,5 +1,5 @@
import {ReleaseNotes} from '../src/releaseNotes'
import {readConfiguration} from '../src/utils'
import { resolveConfiguration } from '../src/utils';
// shows how the runner will run a javascript action with env / stdout protocol
/*
@@ -17,31 +17,33 @@ test('test runs', () => {
it('Should have empty changelog (tags)', async () => {
jest.setTimeout(180000)
const configuration = readConfiguration('configs/configuration.json')!!
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(`- no changes`)
expect(changeLog).toStrictEqual(null)
})
it('Should match generated changelog (tags)', async () => {
jest.setTimeout(180000)
const configuration = readConfiguration('configs/configuration.json')!!
const configuration = resolveConfiguration('', 'configs/configuration.json')
const releaseNotes = new ReleaseNotes({
owner: 'mikepenz',
repo: 'release-changelog-builder-action',
fromTag: 'v0.0.1',
toTag: 'v0.0.3',
ignorePreReleases: false,
failOnError: false,
configuration: configuration
})
@@ -58,13 +60,14 @@ it('Should match generated changelog (tags)', async () => {
it('Should match generated changelog (unspecified fromTag)', async () => {
jest.setTimeout(180000)
const configuration = readConfiguration('configs/configuration.json')!!
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
})
@@ -81,13 +84,14 @@ it('Should match generated changelog (unspecified fromTag)', async () => {
it('Should match generated changelog (refs)', async () => {
jest.setTimeout(180000)
const configuration = readConfiguration('configs/configuration_all_placeholders.json')!!
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
})
@@ -108,3 +112,41 @@ 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`)
})
+3
View File
@@ -20,6 +20,9 @@ inputs:
ignorePreReleases:
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"
failOnError:
description: 'Defines if the action should result in a build failure, if an error was discovered'
default: "false"
token:
description: 'Defines the token to use to execute the git API requests with, uses `env.GITHUB_TOKEN` by default'
outputs:
+4
View File
@@ -0,0 +1,4 @@
{
"sort": "ASC",
"pr_template": "${{NUMBER}}"
}
+4
View File
@@ -0,0 +1,4 @@
{
"sort": "DESC",
"pr_template": "${{NUMBER}}"
}
+50 -61
View File
@@ -1,98 +1,81 @@
import * as core from '@actions/core'
import {readConfiguration} from './utils'
import {
failOrError,
retrieveRepositoryPath,
resolveConfiguration
} from './utils'
import {ReleaseNotes} from './releaseNotes'
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.setOutput('failed', false) // mark the action not failed by default
core.startGroup(`📘 Reading input values`)
try {
let githubWorkspacePath = process.env['GITHUB_WORKSPACE']
if (!githubWorkspacePath) {
throw new Error('GITHUB_WORKSPACE not defined')
}
githubWorkspacePath = path.resolve(githubWorkspacePath)
core.debug(`GITHUB_WORKSPACE = '${githubWorkspacePath}'`)
let repositoryPath = core.getInput('path') || '.'
repositoryPath = path.resolve(githubWorkspacePath, repositoryPath)
core.debug(`repositoryPath = '${repositoryPath}'`)
// read in path specification, resolve github workspace, and repo path
const inputPath = core.getInput('path')
const repositoryPath = retrieveRepositoryPath(inputPath)
// read in configuration file if possible
const configurationFile: string = core.getInput('configuration')
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 configuration = resolveConfiguration(
repositoryPath,
configurationFile
)
// read in repository inputs
const token = core.getInput('token')
let owner = core.getInput('owner')
let repo = core.getInput('repo')
const owner = core.getInput('owner') ?? github.context.repo.owner
const repo = core.getInput('repo') ?? github.context.repo.repo
// read in from, to tag inputs
const fromTag = core.getInput('fromTag')
let toTag = core.getInput('toTag')
// read in flags
const ignorePreReleases = core.getInput('ignorePreReleases') === 'true'
const failOnError = core.getInput('failOnError') === 'true'
const ignorePreReleases = core.getInput('ignorePreReleases')
// ensure to resolve the toTag if it was not provided
if (!toTag) {
// if not specified try to retrieve tag from git
const gitHelper = await createCommandManager(repositoryPath)
const latestTag = await gitHelper.latestTag()
toTag = latestTag
core.debug(`toTag = '${latestTag}'`)
}
if (!owner || !repo) {
// Qualified repository
const qualifiedRepository =
core.getInput('repository') ||
`${github.context.repo.owner}/${github.context.repo.repo}`
core.debug(`qualified repository = '${qualifiedRepository}'`)
const splitRepository = qualifiedRepository.split('/')
if (
splitRepository.length !== 2 ||
!splitRepository[0] ||
!splitRepository[1]
) {
throw new Error(
`Invalid repository '${qualifiedRepository}'. Expected format {owner}/{repo}.`
// if not specified try to retrieve tag from github.context.ref
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'`
)
}
owner = splitRepository[0]
repo = splitRepository[1]
}
if (!owner) {
core.error(`💥 Missing or couldn't resolve 'owner'`)
failOrError(`💥 Missing or couldn't resolve 'owner'`, failOnError)
return
} else {
core.setOutput('owner', owner)
core.debug(`Resolved 'owner' as ${owner}`)
}
if (!repo) {
core.error(`💥 Missing or couldn't resolve 'owner'`)
failOrError(`💥 Missing or couldn't resolve 'owner'`, failOnError)
return
} else {
core.setOutput('repo', repo)
core.debug(`Resolved 'repo' as ${repo}`)
}
if (!toTag) {
core.error(`💥 Missing or couldn't resolve 'toTag'`)
failOrError(`💥 Missing or couldn't resolve 'toTag'`, failOnError)
return
} else {
core.setOutput('toTag', toTag)
core.debug(`Resolved 'toTag' as ${toTag}`)
}
core.endGroup()
@@ -102,11 +85,17 @@ async function run(): Promise<void> {
repo,
fromTag,
toTag,
ignorePreReleases: ignorePreReleases === 'true',
ignorePreReleases,
failOnError,
configuration
})
core.setOutput('changelog', await releaseNotes.pull(token))
core.setOutput(
'changelog',
(await releaseNotes.pull(token)) ??
configuration.empty_template ??
DefaultConfiguration.empty_template
)
} catch (error) {
core.setFailed(error.message)
}
+36 -9
View File
@@ -5,6 +5,7 @@ import {buildChangelog} from './transform'
import * as core from '@actions/core'
import {Tags} from './tags'
import {Configuration, DefaultConfiguration} from './configuration'
import {failOrError} from './utils'
export interface ReleaseNotesOptions {
owner: string // the owner of the repository
@@ -12,18 +13,26 @@ export interface ReleaseNotesOptions {
fromTag: string | null // the tag/ref to start from
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
configuration: Configuration // the configuration as defined in `configuration.ts`
}
export class ReleaseNotes {
constructor(private options: ReleaseNotesOptions) {}
async pull(token?: string): Promise<string> {
async pull(token?: string): Promise<string | null> {
const octokit = new Octokit({
auth: `token ${token || process.env.GITHUB_TOKEN}`
})
const {owner, repo, toTag, ignorePreReleases, configuration} = this.options
const {
owner,
repo,
toTag,
ignorePreReleases,
failOnError,
configuration
} = this.options
if (!this.options.fromTag) {
core.startGroup(`🔖 Resolve previous tag`)
@@ -39,23 +48,31 @@ export class ReleaseNotes {
DefaultConfiguration.max_tags_to_fetch
)
if (previousTag == null) {
core.error(`💥 Unable to retrieve previous tag given ${toTag}`)
return (
configuration.empty_template ?? DefaultConfiguration.empty_template
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`)
const mergedPullRequests = await this.getMergedPullRequests(octokit)
core.endGroup()
if (mergedPullRequests.length === 0) {
core.warning(`⚠️ No pull requests found`)
return configuration.empty_template ?? DefaultConfiguration.empty_template
return null
}
core.startGroup('📦 Build changelog')
@@ -67,7 +84,14 @@ export class ReleaseNotes {
private async getMergedPullRequests(
octokit: Octokit
): Promise<PullRequestInfo[]> {
const {owner, repo, fromTag, toTag, configuration} = this.options
const {
owner,
repo,
fromTag,
toTag,
failOnError,
configuration
} = this.options
core.info(`️ Comparing ${owner}/${repo} - '${fromTag}...${toTag}'`)
const commitsApi = new Commits(octokit)
@@ -75,11 +99,14 @@ export class ReleaseNotes {
try {
commits = await commitsApi.getDiff(owner, repo, fromTag!!, toTag)
} catch (error) {
core.error(`💥 Failed to retrieve - Invalid tag? - Because of: ${error}`)
failOrError(
`💥 Failed to retrieve - Invalid tag? - Because of: ${error}`,
failOnError
)
return []
}
if (commits.length === 0) {
core.warning(`💥 No commits found between - ${fromTag}...${toTag}`)
core.warning(`⚠️ No commits found between - ${fromTag}...${toTag}`)
return []
}
+78 -8
View File
@@ -1,7 +1,71 @@
import * as fs from 'fs'
import {Configuration} from './configuration'
import {Configuration, DefaultConfiguration} from './configuration'
import * as core from '@actions/core'
import * as path from 'path'
export function readConfiguration(filename: string): Configuration | null {
/**
* Resolves the repository path, relatively to the GITHUB_WORKSPACE
*/
export function retrieveRepositoryPath(providedPath: string): string {
let githubWorkspacePath = process.env['GITHUB_WORKSPACE']
if (!githubWorkspacePath) {
throw new Error('GITHUB_WORKSPACE not defined')
}
githubWorkspacePath = path.resolve(githubWorkspacePath)
core.debug(`GITHUB_WORKSPACE = '${githubWorkspacePath}'`)
let repositoryPath = providedPath || '.'
repositoryPath = path.resolve(githubWorkspacePath, repositoryPath)
core.debug(`repositoryPath = '${repositoryPath}'`)
return repositoryPath
}
/**
* Will automatically either report the message to the log, or mark the action as failed. Additionally defining the output failed, allowing it to be read in by other actions
*/
export function failOrError(
message: string | Error,
failOnError: boolean
): void {
// if we report any failure, consider the action to have failed, may not make the build fail
core.setOutput('failed', true)
if (failOnError) {
core.setFailed(message)
} else {
core.error(message)
}
}
/**
* Retrieves the configuration given the file path, if not found it will fallback to the `DefaultConfiguration`
*/
export function resolveConfiguration(
githubWorkspacePath: string,
configurationFile: string
): Configuration {
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
}
}
return configuration
}
/**
* Reads in the configuration from the JSON file
*/
function readConfiguration(filename: string): Configuration | null {
try {
const rawdata = fs.readFileSync(filename, 'utf8')
const configurationJSON: Configuration = JSON.parse(rawdata)
@@ -11,25 +75,31 @@ export function readConfiguration(filename: string): Configuration | null {
}
}
export function directoryExistsSync(path: string, required?: boolean): boolean {
if (!path) {
/**
* Checks if a given directory exists
*/
export function directoryExistsSync(
inputPath: string,
required?: boolean
): boolean {
if (!inputPath) {
throw new Error("Arg 'path' must not be empty")
}
let stats: fs.Stats
try {
stats = fs.statSync(path)
stats = fs.statSync(inputPath)
} catch (error) {
if (error.code === 'ENOENT') {
if (!required) {
return false
}
throw new Error(`Directory '${path}' does not exist`)
throw new Error(`Directory '${inputPath}' does not exist`)
}
throw new Error(
`Encountered an error when checking whether path '${path}' exists: ${error.message}`
`Encountered an error when checking whether path '${inputPath}' exists: ${error.message}`
)
}
@@ -39,5 +109,5 @@ export function directoryExistsSync(path: string, required?: boolean): boolean {
return false
}
throw new Error(`Directory '${path}' does not exist`)
throw new Error(`Directory '${inputPath}' does not exist`)
}