Merge pull request #28 from mikepenz/feature/enhance_logging_outputs

Enhanced action logs
This commit is contained in:
Mike Penz
2020-10-17 19:57:36 +02:00
committed by GitHub
13 changed files with 219 additions and 199 deletions
+13 -2
View File
@@ -1,5 +1,5 @@
<div align="center">
:octocat:
:octocat:📄🔖📦
</div>
<h1 align="center">
release-changelog-builder-action
@@ -71,9 +71,20 @@ ${{steps.build_changelog.outputs.changelog}}
### Changelog Configuration
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.
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": [
{
-7
View File
@@ -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
/*
+10 -12
View File
@@ -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'
Generated Vendored
+94 -86
View File
@@ -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],
@@ -146,7 +146,7 @@ exports.DefaultConfiguration = {
/***/ }),
/***/ 9621:
/***/ 353:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
"use strict";
@@ -182,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() {
@@ -250,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 = {
@@ -322,12 +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) {
@@ -345,7 +321,7 @@ function run() {
core.debug(`configurationPath = '${configurationPath}'`);
const providedConfiguration = utils_1.readConfiguration(configurationPath);
if (!providedConfiguration) {
core.error(`Configuration provided, but it couldn't be found, or failed to parse`);
core.info(`⚠️ Configuration provided, but it couldn't be found, or failed to parse. Fallback to Defaults`);
}
else {
configuration = providedConfiguration;
@@ -359,7 +335,7 @@ function run() {
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}'`);
@@ -379,26 +355,27 @@ 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,
@@ -500,7 +477,7 @@ class PullRequests {
};
}
catch (e) {
core.warning(`Cannot find PR ${owner}/${repo}#${prNumber} - ${e.message}`);
core.warning(`⚠️ Cannot find PR ${owner}/${repo}#${prNumber} - ${e.message}`);
return null;
}
});
@@ -546,7 +523,7 @@ class PullRequests {
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);
@@ -674,40 +651,47 @@ 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_1.Tags(octokit);
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}`);
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`);
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;
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);
let commits;
try {
commits = yield commitsApi.getDiff(owner, repo, fromTag, toTag);
}
catch (error) {
core.error(`Failed to retrieve - Invalid tag? - Because of: ${error}`);
core.error(`💥 Failed to retrieve - Invalid tag? - Because of: ${error}`);
return [];
}
if (commits.length === 0) {
core.warning(`No commits found between - ${fromTag}...${toTag}`);
core.warning(`💥 No commits found between - ${fromTag}...${toTag}`);
return [];
}
const firstCommit = commits[0];
@@ -717,19 +701,15 @@ class ReleaseNotes {
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) {
@@ -749,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;
@@ -845,7 +825,7 @@ 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;
});
}
@@ -857,7 +837,7 @@ class Tags {
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`);
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];
@@ -874,6 +854,20 @@ 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
*/
sortTags(commits) {
commits.sort((b, a) => {
const partsA = a.name.replace(/^v/, '').split('-');
@@ -898,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
*/
/***/ }),
@@ -948,10 +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;
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
@@ -960,9 +940,11 @@ 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();
const categories = (_a = config.categories) !== null && _a !== void 0 ? _a : configuration_1.DefaultConfiguration.categories;
const categories = (_b = config.categories) !== null && _b !== void 0 ? _b : configuration_1.DefaultConfiguration.categories;
for (const category of categories) {
categorized.set(category, []);
}
@@ -980,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) {
@@ -992,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;
@@ -1034,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 {
@@ -1046,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: ''
@@ -1084,7 +1066,7 @@ 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) {
try {
@@ -1097,6 +1079,32 @@ function readConfiguration(filename) {
}
}
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;
/***/ }),
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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,
+1 -32
View File
@@ -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 = ''
+8 -6
View File
@@ -1,12 +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) {
@@ -29,8 +30,8 @@ async function run(): Promise<void> {
core.debug(`configurationPath = '${configurationPath}'`)
const providedConfiguration = readConfiguration(configurationPath)
if (!providedConfiguration) {
core.error(
`Configuration provided, but it couldn't be found, or failed to parse`
core.info(
`⚠️ Configuration provided, but it couldn't be found, or failed to parse. Fallback to Defaults`
)
} else {
configuration = providedConfiguration
@@ -75,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,
+4 -2
View File
@@ -55,7 +55,9 @@ export class PullRequests {
})
}
} catch (e) {
core.warning(`Cannot find PR ${owner}/${repo}#${prNumber} - ${e.message}`)
core.warning(
`⚠️ Cannot find PR ${owner}/${repo}#${prNumber} - ${e.message}`
)
return null
}
}
@@ -108,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
+22 -18
View File
@@ -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)
@@ -38,41 +39,47 @@ export class ReleaseNotes {
DefaultConfiguration.max_tags_to_fetch
)
if (previousTag == null) {
core.error(`Unable to retrieve previous tag given ${toTag}`)
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`)
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)
let commits: CommitInfo[]
try {
commits = await commitsApi.getDiff(owner, repo, fromTag!!, toTag)
} catch (error) {
core.error(`Failed to retrieve - Invalid tag? - Because of: ${error}`)
core.error(`💥 Failed to retrieve - Invalid tag? - Because of: ${error}`)
return []
}
if (commits.length === 0) {
core.warning(`No commits found between - ${fromTag}...${toTag}`)
core.warning(`💥 No commits found between - ${fromTag}...${toTag}`)
return []
}
@@ -86,12 +93,12 @@ export class ReleaseNotes {
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
View File
@@ -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
*/
+18 -13
View File
@@ -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,6 +34,10 @@ 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[]>()
@@ -59,6 +62,7 @@ export function buildChangelog(
uncategorized.push(body)
}
}
core.info(`️ Ordered all pull requests into ${categories.length} categories`)
// construct final changelog
let changelog = ''
@@ -74,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
@@ -92,6 +98,7 @@ export function buildChangelog(
'${{UNCATEGORIZED}}',
changelogUncategorized
)
core.info(`️ Filled template`)
return transformedChangelog
}
@@ -134,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 {
@@ -146,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: ''
+31
View File
@@ -10,3 +10,34 @@ export function readConfiguration(filename: string): Configuration | null {
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`)
}