- introduce better more stable fallbacks in case the configuration is missing

- move configurations into subfolder for easier discovery
- simplify missing value fallback specification
This commit is contained in:
Mike Penz
2020-10-17 18:02:08 +02:00
parent 619d3f460e
commit b464ff088a
8 changed files with 140 additions and 68 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
id: complex_release id: complex_release
uses: ./ uses: ./
with: with:
configuration: "configuration_complex.json" configuration: "configs/configuration_complex.json"
owner: "mikepenz" owner: "mikepenz"
repo: "release-changelog-builder-action" repo: "release-changelog-builder-action"
fromTag: "v0.0.1" fromTag: "v0.0.1"
+3 -3
View File
@@ -24,7 +24,7 @@ test('test runs', () => {
it('Should have empty changelog (tags)', async () => { it('Should have empty changelog (tags)', async () => {
jest.setTimeout(180000) jest.setTimeout(180000)
const configuration = readConfiguration('configs/configuration.json') const configuration = readConfiguration('configs/configuration.json')!!
const releaseNotes = new ReleaseNotes({ const releaseNotes = new ReleaseNotes({
owner: 'mikepenz', owner: 'mikepenz',
repo: 'release-changelog-builder-action', repo: 'release-changelog-builder-action',
@@ -42,7 +42,7 @@ it('Should have empty changelog (tags)', async () => {
it('Should match generated changelog (tags)', async () => { it('Should match generated changelog (tags)', async () => {
jest.setTimeout(180000) jest.setTimeout(180000)
const configuration = readConfiguration('configuration.json') const configuration = readConfiguration('configs/configuration.json')!!
const releaseNotes = new ReleaseNotes({ const releaseNotes = new ReleaseNotes({
owner: 'mikepenz', owner: 'mikepenz',
repo: 'release-changelog-builder-action', repo: 'release-changelog-builder-action',
@@ -65,7 +65,7 @@ it('Should match generated changelog (tags)', async () => {
it('Should match generated changelog (unspecified fromTag)', async () => { it('Should match generated changelog (unspecified fromTag)', async () => {
jest.setTimeout(180000) jest.setTimeout(180000)
const configuration = readConfiguration('configuration.json') const configuration = readConfiguration('configs/configuration.json')!!
const releaseNotes = new ReleaseNotes({ const releaseNotes = new ReleaseNotes({
owner: 'mikepenz', owner: 'mikepenz',
repo: 'release-changelog-builder-action', repo: 'release-changelog-builder-action',
@@ -4,10 +4,6 @@
"title": "## 🚀 Features", "title": "## 🚀 Features",
"labels": ["feature"] "labels": ["feature"]
}, },
{
"title": "## 🦄 Internal Features",
"labels": ["internal"]
},
{ {
"title": "## 🐛 Fixes", "title": "## 🐛 Fixes",
"labels": ["fix"] "labels": ["fix"]
Generated Vendored
+90 -25
View File
@@ -126,7 +126,20 @@ exports.DefaultConfiguration = {
template: '${{CHANGELOG}}', template: '${{CHANGELOG}}',
pr_template: '- ${{TITLE}}\n - PR: #${{NUMBER}}', pr_template: '- ${{TITLE}}\n - PR: #${{NUMBER}}',
empty_template: '- no changes', empty_template: '- no changes',
categories: [], categories: [
{
title: '## 🚀 Features',
labels: ['feature']
},
{
title: '## 🐛 Fixes',
labels: ['fix']
},
{
title: '## 🧪 Tests',
labels: ['test']
}
],
transformers: [] // transformers to apply on the PR description according to the `pr_template` transformers: [] // transformers to apply on the PR description according to the `pr_template`
}; };
@@ -312,6 +325,7 @@ const releaseNotes_1 = __webpack_require__(5882);
const git_helper_1 = __webpack_require__(9621); const git_helper_1 = __webpack_require__(9621);
const github = __importStar(__webpack_require__(5438)); const github = __importStar(__webpack_require__(5438));
const path = __importStar(__webpack_require__(5622)); const path = __importStar(__webpack_require__(5622));
const configuration_1 = __webpack_require__(5527);
function run() { function run() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
@@ -325,14 +339,24 @@ function run() {
repositoryPath = path.resolve(githubWorkspacePath, repositoryPath); repositoryPath = path.resolve(githubWorkspacePath, repositoryPath);
core.debug(`repositoryPath = '${repositoryPath}'`); core.debug(`repositoryPath = '${repositoryPath}'`);
const configurationFile = core.getInput('configuration'); const configurationFile = core.getInput('configuration');
let configuration = configuration_1.DefaultConfiguration;
if (configurationFile) {
const configurationPath = path.resolve(githubWorkspacePath, configurationFile); const configurationPath = path.resolve(githubWorkspacePath, configurationFile);
core.debug(`configurationPath = '${configurationPath}'`); core.debug(`configurationPath = '${configurationPath}'`);
const configuration = utils_1.readConfiguration(configurationPath); const providedConfiguration = utils_1.readConfiguration(configurationPath);
if (!providedConfiguration) {
core.error(`Configuration provided, but it couldn't be found, or failed to parse`);
}
else {
configuration = providedConfiguration;
}
}
const token = core.getInput('token'); const token = core.getInput('token');
let owner = core.getInput('owner'); let owner = core.getInput('owner');
let repo = core.getInput('repo'); let repo = core.getInput('repo');
const fromTag = core.getInput('fromTag'); const fromTag = core.getInput('fromTag');
let toTag = core.getInput('toTag'); let toTag = core.getInput('toTag');
const ignorePreReleases = core.getInput('ignorePreReleases');
if (!toTag) { if (!toTag) {
// if not specified try to retrieve tag from git // if not specified try to retrieve tag from git
const gitHelper = yield git_helper_1.createCommandManager(repositoryPath); const gitHelper = yield git_helper_1.createCommandManager(repositoryPath);
@@ -380,6 +404,7 @@ function run() {
repo, repo,
fromTag, fromTag,
toTag, toTag,
ignorePreReleases: ignorePreReleases === 'true',
configuration configuration
}); });
core.setOutput('changelog', yield releaseNotes.pull(token)); core.setOutput('changelog', yield releaseNotes.pull(token));
@@ -446,6 +471,7 @@ class PullRequests {
this.octokit = octokit; this.octokit = octokit;
} }
getSingle(owner, repo, prNumber) { getSingle(owner, repo, prNumber) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
try { try {
const pr = yield this.octokit.pulls.get({ const pr = yield this.octokit.pulls.get({
@@ -463,7 +489,14 @@ class PullRequests {
labels: pr.data.labels.map(function (label) { labels: pr.data.labels.map(function (label) {
return label.name; return label.name;
}), }),
body: pr.data.body milestone: (_a = pr.data.milestone) === null || _a === void 0 ? void 0 : _a.title,
body: pr.data.body,
assignees: (_b = pr.data.assignees) === null || _b === void 0 ? void 0 : _b.map(function (asignee) {
return asignee.login;
}),
requestedReviewers: (_c = pr.data.requested_reviewers) === null || _c === void 0 ? void 0 : _c.map(function (reviewer) {
return reviewer.login;
})
}; };
} }
catch (e) { catch (e) {
@@ -474,6 +507,7 @@ class PullRequests {
} }
getBetweenDates(owner, repo, fromDate, toDate, maxPullRequests) { getBetweenDates(owner, repo, fromDate, toDate, maxPullRequests) {
var e_1, _a; var e_1, _a;
var _b, _c, _d, _e;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const mergedPRs = []; const mergedPRs = [];
const options = this.octokit.pulls.list.endpoint.merge({ const options = this.octokit.pulls.list.endpoint.merge({
@@ -484,8 +518,8 @@ class PullRequests {
direction: 'desc' direction: 'desc'
}); });
try { try {
for (var _b = __asyncValues(this.octokit.paginate.iterator(options)), _c; _c = yield _b.next(), !_c.done;) { for (var _f = __asyncValues(this.octokit.paginate.iterator(options)), _g; _g = yield _f.next(), !_g.done;) {
const response = _c.value; const response = _g.value;
const prs = response.data; const prs = response.data;
for (const pr of prs.filter(p => !!p.merged_at)) { for (const pr of prs.filter(p => !!p.merged_at)) {
mergedPRs.push({ mergedPRs.push({
@@ -495,10 +529,17 @@ class PullRequests {
mergedAt: moment_1.default(pr.merged_at), mergedAt: moment_1.default(pr.merged_at),
author: pr.user.login, author: pr.user.login,
repoName: pr.base.repo.full_name, repoName: pr.base.repo.full_name,
labels: pr.labels.map(function (label) { labels: (_b = pr.labels) === null || _b === void 0 ? void 0 : _b.map(function (label) {
return label.name; return label.name;
}), }),
body: pr.body milestone: (_c = pr.milestone) === null || _c === void 0 ? void 0 : _c.title,
body: pr.body,
assignees: (_d = pr.assignees) === null || _d === void 0 ? void 0 : _d.map(function (asignee) {
return asignee.login;
}),
requestedReviewers: (_e = pr.requested_reviewers) === null || _e === void 0 ? void 0 : _e.map(function (reviewer) {
return reviewer.login;
})
}); });
} }
const firstPR = prs[0]; const firstPR = prs[0];
@@ -515,7 +556,7 @@ class PullRequests {
catch (e_1_1) { e_1 = { error: e_1_1 }; } catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally { finally {
try { try {
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b); if (_g && !_g.done && (_a = _f.return)) yield _a.call(_f);
} }
finally { if (e_1) throw e_1.error; } finally { if (e_1) throw e_1.error; }
} }
@@ -626,52 +667,54 @@ class ReleaseNotes {
this.options = options; this.options = options;
} }
pull(token) { pull(token) {
var _a, _b, _c;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const octokit = new rest_1.Octokit({ const octokit = new rest_1.Octokit({
auth: `token ${token || process.env.GITHUB_TOKEN}` auth: `token ${token || process.env.GITHUB_TOKEN}`
}); });
const { owner, repo, toTag, configuration } = this.options; const { owner, repo, toTag, ignorePreReleases, configuration } = this.options;
if (!this.options.fromTag) { if (!this.options.fromTag) {
core.debug(`fromTag undefined, trying to resolve via API`); core.debug(`fromTag undefined, trying to resolve via API`);
const tagsApi = new tags_1.Tags(octokit); const tagsApi = new tags_1.Tags(octokit);
const previousTag = yield tagsApi.findPredecessorTag(owner, repo, toTag, configuration.max_tags_to_fetch const previousTag = yield tagsApi.findPredecessorTag(owner, repo, toTag, ignorePreReleases, (_a = configuration.max_tags_to_fetch) !== null && _a !== void 0 ? _a : configuration_1.DefaultConfiguration.max_tags_to_fetch);
? configuration.max_tags_to_fetch
: configuration_1.DefaultConfiguration.max_tags_to_fetch);
if (previousTag == null) { 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 return ((_b = configuration.empty_template) !== null && _b !== void 0 ? _b : configuration_1.DefaultConfiguration.empty_template);
? configuration.empty_template
: configuration_1.DefaultConfiguration.empty_template;
} }
this.options.fromTag = previousTag.name; this.options.fromTag = previousTag.name;
core.debug(`fromTag resolved via previousTag as: ${previousTag.name}`); core.debug(`fromTag resolved via previousTag as: ${previousTag.name}`);
} }
const mergedPullRequests = yield this.getMergedPullRequests(octokit); const mergedPullRequests = yield this.getMergedPullRequests(octokit);
if (mergedPullRequests.length === 0) { if (mergedPullRequests.length === 0) {
core.warning(`No pull requests found for between ${this.options.fromTag}...${toTag}`); core.warning(`No pull requests found`);
return configuration.empty_template return (_c = configuration.empty_template) !== null && _c !== void 0 ? _c : configuration_1.DefaultConfiguration.empty_template;
? configuration.empty_template
: configuration_1.DefaultConfiguration.empty_template;
} }
return transform_1.buildChangelog(mergedPullRequests, configuration); return transform_1.buildChangelog(mergedPullRequests, configuration);
}); });
} }
getMergedPullRequests(octokit) { getMergedPullRequests(octokit) {
var _a;
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const { owner, repo, fromTag, toTag, configuration } = this.options; const { owner, repo, fromTag, toTag, configuration } = this.options;
core.info(`Comparing ${owner}/${repo} - ${fromTag}...${toTag}`); core.info(`Comparing ${owner}/${repo} - ${fromTag}...${toTag}`);
const commitsApi = new commits_1.Commits(octokit); const commitsApi = new commits_1.Commits(octokit);
const commits = yield commitsApi.getDiff(owner, repo, fromTag, toTag); let commits;
try {
commits = yield commitsApi.getDiff(owner, repo, fromTag, toTag);
}
catch (error) {
core.error(`Failed to retrieve - Invalid tag? - Because of: ${error}`);
return [];
}
if (commits.length === 0) { if (commits.length === 0) {
core.warning(`No commits found between - ${fromTag}...${toTag}`);
return []; return [];
} }
const firstCommit = commits[0]; const firstCommit = commits[0];
const lastCommit = commits[commits.length - 1]; const lastCommit = commits[commits.length - 1];
let fromDate = firstCommit.date; let fromDate = firstCommit.date;
const toDate = lastCommit.date; const toDate = lastCommit.date;
const maxDays = configuration.max_back_track_time_days const maxDays = (_a = configuration.max_back_track_time_days) !== null && _a !== void 0 ? _a : configuration_1.DefaultConfiguration.max_back_track_time_days;
? configuration.max_back_track_time_days
: configuration_1.DefaultConfiguration.max_back_track_time_days;
const maxFromDate = toDate.clone().subtract(maxDays, 'days'); const maxFromDate = toDate.clone().subtract(maxDays, 'days');
if (maxFromDate.isAfter(fromDate)) { if (maxFromDate.isAfter(fromDate)) {
core.info(`Adjusted 'fromDate' to go max ${maxDays} back`); core.info(`Adjusted 'fromDate' to go max ${maxDays} back`);
@@ -806,17 +849,29 @@ class Tags {
return tagsInfo; return tagsInfo;
}); });
} }
findPredecessorTag(owner, repo, tag, maxTagsToFetch) { findPredecessorTag(owner, repo, tag, ignorePreReleases, maxTagsToFetch) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const tags = this.sortTags(yield this.getTags(owner, repo, maxTagsToFetch)); const tags = this.sortTags(yield this.getTags(owner, repo, maxTagsToFetch));
try {
const length = tags.length; const length = tags.length;
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
if (tags[i].name.toLowerCase() === tag.toLowerCase()) { if (tags[i].name.toLowerCase() === tag.toLowerCase()) {
if (ignorePreReleases) {
core.info(`Enabled 'ignorePreReleases', searching for the closest release`);
for (let ii = i + 1; ii < length; ii++) {
if (!tags[ii].name.includes('-')) {
return tags[ii];
}
}
}
return tags[i + 1]; return tags[i + 1];
} }
} }
// not found, throw exception?
return tags[0]; return tags[0];
}
catch (error) {
return null;
}
}); });
} }
sortTags(commits) { sortTags(commits) {
@@ -954,13 +1009,18 @@ function haveCommonElements(arr1, arr2) {
return arr1.some(item => arr2.includes(item)); return arr1.some(item => arr2.includes(item));
} }
function fillTemplate(pr, template) { function fillTemplate(pr, template) {
var _a, _b, _c, _d, _e, _f, _g;
let transformed = template; let transformed = template;
transformed = transformed.replace('${{NUMBER}}', pr.number.toString()); transformed = transformed.replace('${{NUMBER}}', pr.number.toString());
transformed = transformed.replace('${{TITLE}}', pr.title); transformed = transformed.replace('${{TITLE}}', pr.title);
transformed = transformed.replace('${{URL}}', pr.htmlURL); transformed = transformed.replace('${{URL}}', pr.htmlURL);
transformed = transformed.replace('${{MERGED_AT}}', pr.mergedAt.toISOString()); transformed = transformed.replace('${{MERGED_AT}}', pr.mergedAt.toISOString());
transformed = transformed.replace('${{AUTHOR}}', pr.author); transformed = transformed.replace('${{AUTHOR}}', pr.author);
transformed = transformed.replace('${{LABELS}}', (_b = (_a = pr.labels) === null || _a === void 0 ? void 0 : _a.join(', ')) !== null && _b !== void 0 ? _b : '');
transformed = transformed.replace('${{MILESTONE}}', (_c = pr.milestone) !== null && _c !== void 0 ? _c : '');
transformed = transformed.replace('${{BODY}}', pr.body); transformed = transformed.replace('${{BODY}}', pr.body);
transformed = transformed.replace('${{ASIGNEES}}', (_e = (_d = pr.assignees) === null || _d === void 0 ? void 0 : _d.join(', ')) !== null && _e !== void 0 ? _e : '');
transformed = transformed.replace('${{REVIEWERS}}', (_g = (_f = pr.requestedReviewers) === null || _f === void 0 ? void 0 : _f.join(', ')) !== null && _g !== void 0 ? _g : '');
return transformed; return transformed;
} }
function transform(filled, transformers) { function transform(filled, transformers) {
@@ -1027,9 +1087,14 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.readConfiguration = void 0; exports.readConfiguration = void 0;
const fs = __importStar(__webpack_require__(5747)); const fs = __importStar(__webpack_require__(5747));
function readConfiguration(filename) { function readConfiguration(filename) {
try {
const rawdata = fs.readFileSync(filename, 'utf8'); const rawdata = fs.readFileSync(filename, 'utf8');
const configurationJSON = JSON.parse(rawdata); const configurationJSON = JSON.parse(rawdata);
return configurationJSON; return configurationJSON;
}
catch (error) {
return null;
}
} }
exports.readConfiguration = readConfiguration; exports.readConfiguration = readConfiguration;
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+12 -1
View File
@@ -4,6 +4,7 @@ import {ReleaseNotes} from './releaseNotes'
import {createCommandManager} from './git-helper' import {createCommandManager} from './git-helper'
import * as github from '@actions/github' import * as github from '@actions/github'
import * as path from 'path' import * as path from 'path'
import {DefaultConfiguration} from './configuration'
async function run(): Promise<void> { async function run(): Promise<void> {
try { try {
@@ -19,12 +20,22 @@ async function run(): Promise<void> {
core.debug(`repositoryPath = '${repositoryPath}'`) core.debug(`repositoryPath = '${repositoryPath}'`)
const configurationFile: string = core.getInput('configuration') const configurationFile: string = core.getInput('configuration')
let configuration = DefaultConfiguration
if (configurationFile) {
const configurationPath = path.resolve( const configurationPath = path.resolve(
githubWorkspacePath, githubWorkspacePath,
configurationFile configurationFile
) )
core.debug(`configurationPath = '${configurationPath}'`) core.debug(`configurationPath = '${configurationPath}'`)
const configuration = readConfiguration(configurationPath) const providedConfiguration = readConfiguration(configurationPath)
if (!providedConfiguration) {
core.error(
`Configuration provided, but it couldn't be found, or failed to parse`
)
} else {
configuration = providedConfiguration
}
}
const token = core.getInput('token') const token = core.getInput('token')
let owner = core.getInput('owner') let owner = core.getInput('owner')
+10 -14
View File
@@ -1,5 +1,5 @@
import {Octokit} from '@octokit/rest' import {Octokit} from '@octokit/rest'
import { Commits, CommitInfo } from './commits'; import {Commits, CommitInfo} from './commits'
import {PullRequestInfo, PullRequests} from './pullRequests' import {PullRequestInfo, PullRequests} from './pullRequests'
import {buildChangelog} from './transform' import {buildChangelog} from './transform'
import * as core from '@actions/core' import * as core from '@actions/core'
@@ -34,17 +34,15 @@ export class ReleaseNotes {
repo, repo,
toTag, toTag,
ignorePreReleases, ignorePreReleases,
configuration.max_tags_to_fetch configuration.max_tags_to_fetch ??
? configuration.max_tags_to_fetch DefaultConfiguration.max_tags_to_fetch
: DefaultConfiguration.max_tags_to_fetch
) )
if (previousTag == null) { 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 return (
? configuration.empty_template configuration.empty_template ?? DefaultConfiguration.empty_template
: DefaultConfiguration.empty_template )
} }
this.options.fromTag = previousTag.name this.options.fromTag = previousTag.name
core.debug(`fromTag resolved via previousTag as: ${previousTag.name}`) core.debug(`fromTag resolved via previousTag as: ${previousTag.name}`)
} }
@@ -53,9 +51,7 @@ export class ReleaseNotes {
if (mergedPullRequests.length === 0) { if (mergedPullRequests.length === 0) {
core.warning(`No pull requests found`) core.warning(`No pull requests found`)
return configuration.empty_template return configuration.empty_template ?? DefaultConfiguration.empty_template
? configuration.empty_template
: DefaultConfiguration.empty_template
} }
return buildChangelog(mergedPullRequests, configuration) return buildChangelog(mergedPullRequests, configuration)
@@ -85,9 +81,9 @@ export class ReleaseNotes {
let fromDate = firstCommit.date let fromDate = firstCommit.date
const toDate = lastCommit.date const toDate = lastCommit.date
const maxDays = configuration.max_back_track_time_days const maxDays =
? configuration.max_back_track_time_days configuration.max_back_track_time_days ??
: DefaultConfiguration.max_back_track_time_days DefaultConfiguration.max_back_track_time_days
const maxFromDate = toDate.clone().subtract(maxDays, 'days') const maxFromDate = toDate.clone().subtract(maxDays, 'days')
if (maxFromDate.isAfter(fromDate)) { if (maxFromDate.isAfter(fromDate)) {
core.info(`Adjusted 'fromDate' to go max ${maxDays} back`) core.info(`Adjusted 'fromDate' to go max ${maxDays} back`)
+5 -1
View File
@@ -1,8 +1,12 @@
import * as fs from 'fs' import * as fs from 'fs'
import {Configuration} from './configuration' import {Configuration} from './configuration'
export function readConfiguration(filename: string): Configuration { export function readConfiguration(filename: string): Configuration | null {
try {
const rawdata = fs.readFileSync(filename, 'utf8') const rawdata = fs.readFileSync(filename, 'utf8')
const configurationJSON: Configuration = JSON.parse(rawdata) const configurationJSON: Configuration = JSON.parse(rawdata)
return configurationJSON return configurationJSON
} catch (error) {
return null
}
} }