Merge pull request #28 from mikepenz/feature/enhance_logging_outputs
Enhanced action logs
This commit is contained in:
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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
@@ -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'
|
||||
|
||||
+94
-86
@@ -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;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
+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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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: ''
|
||||
|
||||
@@ -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`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user