Merge pull request #823 from mikepenz/feature/diff_info_placeholders

Introduce new placeholders - Changed files, Additions, Deletions, Changes, Commits
This commit is contained in:
Mike Penz
2022-07-26 14:21:48 +02:00
committed by GitHub
11 changed files with 245 additions and 108 deletions
+11 -1
View File
@@ -86,6 +86,11 @@ A full set list of possible output values for this action.
| `outputs.categorized_prs` | Count of PRs which were successfully categorized as part of the action. |
| `outputs.open_prs` | Count of open PRs. Only fetched if `includeOpen` is enabled. |
| `outputs.uncategorized_prs` | Count of PRs which were not categorized as part of the action. |
| `outputs.changed_files` | Count of changed files in this release. |
| `outputs.additions` | Count of code additions in this release (lines). |
| `outputs.deletions` | Count of code deletions in this release (lines). |
| `outputs.changes` | Total count of changes in this release (lines). |
| `outputs.commits` | Count of commits which have been added in this release. |
## Full Sample 🖥️
@@ -309,6 +314,11 @@ Table of supported placeholders allowed to be used in the `template` and `empty_
| `${{FROM_TAG}}` | Defines the 'start' from where the changelog did consider merged pull requests | x |
| `${{TO_TAG}}` | Defines until which tag the changelog did consider merged pull requests | x |
| `${{RELEASE_DIFF}}` | Introduces a link to the full diff between from tag and to tag releases | x |
| `${{CHANGED_FILES}}` | The count of changed files. | |
| `${{ADDITIONS}}` | The count of code additions (lines). | |
| `${{DELETIONS}}` | The count of code deletions (lines). | |
| `${{CHANGES}}` | The count of total changes (lines). | |
| `${{COMMITS}}` | The count of commits in this release. | |
| `${{CATEGORIZED_COUNT}}` | The count of PRs which were categorized | |
| `${{UNCATEGORIZED_COUNT}}` | The count of PRs and changes which were not categorized. No label overlapping with category labels | |
| `${{OPEN_COUNT}}` | The count of open PRs. Will only be fetched if `includeOpen` is configured. | |
@@ -404,7 +414,7 @@ export GITHUB_TOKEN=your_personal_github_pat
All patches and changes applied to the original source are licensed under the Apache 2.0 license.
Copyright 2021 Mike Penz
Copyright 2022 Mike Penz
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+1 -1
View File
@@ -25,7 +25,7 @@ it('Should have empty changelog (tags)', async () => {
const changeLog = await releaseNotes.pull()
console.log(changeLog)
expect(changeLog).toStrictEqual(null)
expect(changeLog).toStrictEqual("- no changes")
})
it('Should match generated changelog (tags)', async () => {
+3 -3
View File
@@ -103,7 +103,7 @@ it('Should fill empty placeholders', async () => {
const changeLog = await releaseNotesBuilder.build()
console.log(changeLog)
expect(changeLog).toStrictEqual(
`mikepenz\nrelease-changelog-builder-action\nv0.0.2\nv0.0.3`
`mikepenz\nrelease-changelog-builder-action\nv0.0.2\nv0.0.3\nhttps://github.com/mikepenz/release-changelog-builder-action/compare/v0.0.2...v0.0.3`
)
})
@@ -131,7 +131,7 @@ it('Should fill `template` placeholders', async () => {
const changeLog = await releaseNotesBuilder.build()
console.log(changeLog)
expect(changeLog).toStrictEqual(
`## 🧪 Tests\n\n- [CI] Specify Test Case\n - PR: #10\n\n\n\n\nmikepenz\nrelease-changelog-builder-action\nv0.0.1\nv0.0.3\n1\n0\n0`
`## 🧪 Tests\n\n- [CI] Specify Test Case\n - PR: #10\n\n\n\n\nmikepenz\nrelease-changelog-builder-action\nv0.0.1\nv0.0.3\nhttps://github.com/mikepenz/release-changelog-builder-action/compare/v0.0.1...v0.0.3\n1\n0\n0\n19\n14827\n444\n15271\n3`
)
})
@@ -159,7 +159,7 @@ it('Should fill `template` placeholders, ignore', async () => {
const changeLog = await releaseNotesBuilder.build()
console.log(changeLog)
expect(changeLog).toStrictEqual(
`## 🚀 Features\n\n- Enhance sorting by using proper semver\n - PR: #51\n\n## 🧪 Tests\n\n- Improve test cases\n - PR: #49\n\n\n- Bump @types/node from 14.11.8 to 14.11.10\n - PR: #47\n- Adjust code to move fromTag resolving to main.ts\n - PR: #48\n- dev -> main\n - PR: #52\n- Update package.json to updated description\n - PR: #53\n- dev -> main\n - PR: #54\n\n- New additional placeholders for \`template\` and \`empty_template\`\n - PR: #50\n\nmikepenz\nrelease-changelog-builder-action\nv0.9.1\nv0.9.5\n2\n5\n1`
`## 🚀 Features\n\n- Enhance sorting by using proper semver\n - PR: #51\n\n## 🧪 Tests\n\n- Improve test cases\n - PR: #49\n\n\n- Bump @types/node from 14.11.8 to 14.11.10\n - PR: #47\n- Adjust code to move fromTag resolving to main.ts\n - PR: #48\n- dev -> main\n - PR: #52\n- Update package.json to updated description\n - PR: #53\n- dev -> main\n - PR: #54\n\n- New additional placeholders for \`template\` and \`empty_template\`\n - PR: #50\n\nmikepenz\nrelease-changelog-builder-action\nv0.9.1\nv0.9.5\nhttps://github.com/mikepenz/release-changelog-builder-action/compare/v0.9.1...v0.9.5\n2\n5\n1\n16\n2931\n450\n3381\n26`
)
})
+14 -13
View File
@@ -2,6 +2,7 @@ import {buildChangelog} from '../src/transform'
import {PullRequestInfo} from '../src/pullRequests'
import moment from 'moment'
import { DefaultConfiguration, Configuration } from '../src/configuration';
import { DefaultDiffInfo } from '../src/commits';
jest.setTimeout(180000)
@@ -130,7 +131,7 @@ it('Extract label from title, combined regex', async () => {
}
]
const resultChangelog = buildChangelog(mergedPullRequests, {
const resultChangelog = buildChangelog(DefaultDiffInfo, mergedPullRequests, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
@@ -159,7 +160,7 @@ it('Extract label from title and body, combined regex', async () => {
let prs = Array.from(mergedPullRequests)
prs.push(pullRequestWithLabelInBody)
const resultChangelog = buildChangelog(prs, {
const resultChangelog = buildChangelog(DefaultDiffInfo, prs, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
@@ -190,7 +191,7 @@ it('Extract label from title, split regex', async () => {
}
]
const resultChangelog = buildChangelog(mergedPullRequests, {
const resultChangelog = buildChangelog(DefaultDiffInfo, mergedPullRequests, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
@@ -221,7 +222,7 @@ it('Extract label from title, match', async () => {
}
]
const resultChangelog = buildChangelog(mergedPullRequests, {
const resultChangelog = buildChangelog(DefaultDiffInfo, mergedPullRequests, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
@@ -247,7 +248,7 @@ it('Extract label from title, match multiple', async () => {
}
]
const resultChangelog = buildChangelog(mergedPullRequests, {
const resultChangelog = buildChangelog(DefaultDiffInfo, mergedPullRequests, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
@@ -274,7 +275,7 @@ it('Extract label from title, match multiple, custon non matching label', async
}
]
const resultChangelog = buildChangelog(mergedPullRequests, {
const resultChangelog = buildChangelog(DefaultDiffInfo, mergedPullRequests, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
@@ -388,7 +389,7 @@ it('Match multiple labels exhaustive for category', async () => {
}
]
const resultChangelog = buildChangelog(pullRequestsWithLabels, {
const resultChangelog = buildChangelog(DefaultDiffInfo, pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
@@ -413,7 +414,7 @@ it('Deduplicate duplicated PRs', async () => {
method: 'match'
}
const resultChangelog = buildChangelog(pullRequestsWithLabels, {
const resultChangelog = buildChangelog(DefaultDiffInfo, pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
@@ -439,7 +440,7 @@ it('Deduplicate duplicated PRs DESC', async () => {
method: 'match'
}
const resultChangelog = buildChangelog(pullRequestsWithLabels, {
const resultChangelog = buildChangelog(DefaultDiffInfo, pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
@@ -471,7 +472,7 @@ it('Use empty_content for empty category', async () => {
}
]
const resultChangelog = buildChangelog(pullRequestsWithLabels, {
const resultChangelog = buildChangelog(DefaultDiffInfo, pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
@@ -493,7 +494,7 @@ it('Commit SHA-1 in commitMode', async () => {
customConfig.sort = "DESC"
customConfig.pr_template = "${{MERGE_SHA}}"
const resultChangelog = buildChangelog(pullRequestsWithLabels, {
const resultChangelog = buildChangelog(DefaultDiffInfo, pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
@@ -514,7 +515,7 @@ it('Release Diff', async () => {
const customConfig = Object.assign({}, DefaultConfiguration)
customConfig.template = "${{RELEASE_DIFF}}"
const resultChangelog = buildChangelog(pullRequestsWithLabels, {
const resultChangelog = buildChangelog(DefaultDiffInfo, pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'release-changelog-builder-action',
fromTag: 'v2.8.0',
@@ -553,7 +554,7 @@ it('Use exclude labels to not include a PR within a category.', async () => {
}
]
const resultChangelog = buildChangelog(pullRequestsWithLabels, {
const resultChangelog = buildChangelog(DefaultDiffInfo, pullRequestsWithLabels, {
owner: 'mikepenz',
repo: 'test-repo',
fromTag: '1.0.0',
@@ -1,6 +1,6 @@
{
"template": "${{CHANGELOG}}\n${{UNCATEGORIZED}}\n${{IGNORED}}\n${{OWNER}}\n${{REPO}}\n${{FROM_TAG}}\n${{TO_TAG}}\n${{CATEGORIZED_COUNT}}\n${{UNCATEGORIZED_COUNT}}\n${{IGNORED_COUNT}}",
"empty_template": "${{OWNER}}\n${{REPO}}\n${{FROM_TAG}}\n${{TO_TAG}}",
"template": "${{CHANGELOG}}\n${{UNCATEGORIZED}}\n${{IGNORED}}\n${{OWNER}}\n${{REPO}}\n${{FROM_TAG}}\n${{TO_TAG}}\n${{RELEASE_DIFF}}\n${{CATEGORIZED_COUNT}}\n${{UNCATEGORIZED_COUNT}}\n${{IGNORED_COUNT}}\n${{CHANGED_FILES}}\n${{ADDITIONS}}\n${{DELETIONS}}\n${{CHANGES}}\n${{COMMITS}}",
"empty_template": "${{OWNER}}\n${{REPO}}\n${{FROM_TAG}}\n${{TO_TAG}}\n${{RELEASE_DIFF}}",
"max_pull_requests": 1000,
"max_back_track_time_days": 1000
}
Generated Vendored
+87 -37
View File
@@ -42,21 +42,36 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.filterCommits = exports.Commits = void 0;
exports.filterCommits = exports.Commits = exports.DefaultDiffInfo = void 0;
const core = __importStar(__nccwpck_require__(2186));
const moment_1 = __importDefault(__nccwpck_require__(9623));
exports.DefaultDiffInfo = {
changedFiles: 0,
additions: 0,
deletions: 0,
changes: 0,
commits: 0,
commitInfo: []
};
class Commits {
constructor(octokit) {
this.octokit = octokit;
}
getDiff(owner, repo, base, head) {
return __awaiter(this, void 0, void 0, function* () {
const commits = yield this.getDiffRemote(owner, repo, base, head);
return this.sortCommits(commits);
const diff = yield this.getDiffRemote(owner, repo, base, head);
diff.commitInfo = this.sortCommits(diff.commitInfo);
return diff;
});
}
getDiffRemote(owner, repo, base, head) {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
let changedFilesCount = 0;
let additionCount = 0;
let deletionCount = 0;
let changeCount = 0;
let commitCount = 0;
// Fetch comparisons recursively until we don't find any commits
// This is because the GitHub API limits the number of commits returned in a single response.
let commits = [];
@@ -72,23 +87,40 @@ class Commits {
if (compareResult.data.total_commits === 0) {
break;
}
changedFilesCount += (_b = (_a = compareResult.data.files) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
const files = compareResult.data.files;
if (files !== undefined) {
for (const file of files) {
additionCount += file.additions;
deletionCount += file.deletions;
changeCount += file.changes;
}
}
commitCount += compareResult.data.commits.length;
commits = compareResult.data.commits.concat(commits);
compareHead = `${commits[0].sha}^`;
}
core.info(`️ Found ${commits.length} commits from the GitHub API for ${owner}/${repo}`);
return commits
.filter(commit => commit.sha)
.map(commit => {
var _a, _b;
return ({
sha: commit.sha || '',
summary: commit.commit.message.split('\n')[0],
message: commit.commit.message,
date: (0, moment_1.default)((_a = commit.commit.committer) === null || _a === void 0 ? void 0 : _a.date),
author: ((_b = commit.commit.author) === null || _b === void 0 ? void 0 : _b.name) || '',
prNumber: undefined
});
});
return {
changedFiles: changedFilesCount,
additions: additionCount,
deletions: deletionCount,
changes: changeCount,
commits: commitCount,
commitInfo: commits
.filter(commit => commit.sha)
.map(commit => {
var _a, _b;
return ({
sha: commit.sha || '',
summary: commit.commit.message.split('\n')[0],
message: commit.commit.message,
date: (0, moment_1.default)((_a = commit.commit.committer) === null || _a === void 0 ? void 0 : _a.date),
author: ((_b = commit.commit.author) === null || _b === void 0 ? void 0 : _b.name) || '',
prNumber: undefined
});
})
};
});
}
sortCommits(commits) {
@@ -718,29 +750,40 @@ class ReleaseNotes {
pull() {
return __awaiter(this, void 0, void 0, function* () {
let mergedPullRequests;
let diffInfo;
if (!this.options.commitMode) {
core.startGroup(`🚀 Load pull requests`);
mergedPullRequests = yield this.getMergedPullRequests(this.octokit);
const [info, prs] = yield this.getMergedPullRequests(this.octokit);
mergedPullRequests = prs;
diffInfo = info;
// define the included PRs within this release as output
core.setOutput('pull_requests', mergedPullRequests
.map(pr => {
return pr.number;
})
.join(','));
core.setOutput('changed_files', diffInfo.changedFiles);
core.setOutput('additions', diffInfo.additions);
core.setOutput('deletions', diffInfo.deletions);
core.setOutput('changes', diffInfo.changes);
core.setOutput('commits', diffInfo.commits);
core.endGroup();
}
else {
core.startGroup(`🚀 Load commit history`);
core.info(`⚠️ Executing experimental commit mode`);
mergedPullRequests = yield this.generateCommitPRs(this.octokit);
const [info, prs] = yield this.generateCommitPRs(this.octokit);
mergedPullRequests = prs;
diffInfo = info;
core.endGroup();
}
if (mergedPullRequests.length === 0) {
core.warning(`⚠️ No pull requests found`);
return null;
return (0, transform_1.fillAdditionalPlaceholders)(this.options.configuration.empty_template ||
configuration_1.DefaultConfiguration.empty_template, this.options);
}
core.startGroup('📦 Build changelog');
const resultChangelog = (0, transform_1.buildChangelog)(mergedPullRequests, this.options);
const resultChangelog = (0, transform_1.buildChangelog)(diffInfo, mergedPullRequests, this.options);
core.endGroup();
return resultChangelog;
});
@@ -750,27 +793,28 @@ class ReleaseNotes {
const { owner, repo, fromTag, toTag, failOnError } = this.options;
core.info(`️ Comparing ${owner}/${repo} - '${fromTag}...${toTag}'`);
const commitsApi = new commits_1.Commits(octokit);
let commits;
let diffInfo;
try {
commits = yield commitsApi.getDiff(owner, repo, fromTag, toTag);
diffInfo = yield commitsApi.getDiff(owner, repo, fromTag, toTag);
}
catch (error) {
(0, utils_1.failOrError)(`💥 Failed to retrieve - Invalid tag? - Because of: ${error}`, failOnError);
return [];
return commits_1.DefaultDiffInfo;
}
if (commits.length === 0) {
if (diffInfo.commitInfo.length === 0) {
core.warning(`⚠️ No commits found between - ${fromTag}...${toTag}`);
return [];
return commits_1.DefaultDiffInfo;
}
return commits;
return diffInfo;
});
}
getMergedPullRequests(octokit) {
return __awaiter(this, void 0, void 0, function* () {
const { owner, repo, includeOpen, fetchReviewers, configuration } = this.options;
const commits = yield this.getCommitHistory(octokit);
const diffInfo = yield this.getCommitHistory(octokit);
const commits = diffInfo.commitInfo;
if (commits.length === 0) {
return [];
return [diffInfo, []];
}
const firstCommit = commits[0];
const lastCommit = commits[commits.length - 1];
@@ -832,20 +876,21 @@ class ReleaseNotes {
}
}
}
return finalPrs;
return [diffInfo, finalPrs];
});
}
generateCommitPRs(octokit) {
return __awaiter(this, void 0, void 0, function* () {
const { owner, repo, configuration } = this.options;
const commits = yield this.getCommitHistory(octokit);
const diffInfo = yield this.getCommitHistory(octokit);
const commits = diffInfo.commitInfo;
if (commits.length === 0) {
return [];
return [diffInfo, []];
}
const prCommits = (0, commits_1.filterCommits)(commits, configuration.exclude_merge_branches ||
configuration_1.DefaultConfiguration.exclude_merge_branches);
core.info(`️ Retrieved ${prCommits.length} commits for ${owner}/${repo}`);
return prCommits.map(function (commit) {
const prs = prCommits.map(function (commit) {
return {
number: 0,
title: commit.summary,
@@ -865,6 +910,7 @@ class ReleaseNotes {
status: 'merged'
};
});
return [diffInfo, prs];
});
}
}
@@ -918,7 +964,6 @@ const rest_1 = __nccwpck_require__(5375);
const releaseNotes_1 = __nccwpck_require__(5882);
const tags_1 = __nccwpck_require__(7532);
const utils_1 = __nccwpck_require__(918);
const transform_1 = __nccwpck_require__(1644);
class ReleaseNotesBuilder {
constructor(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen = false, failOnError, ignorePreReleases, fetchReviewers = false, commitMode, configuration) {
this.baseUrl = baseUrl;
@@ -996,9 +1041,7 @@ class ReleaseNotesBuilder {
configuration: this.configuration
};
const releaseNotes = new releaseNotes_1.ReleaseNotes(octokit, options);
return ((yield releaseNotes.pull()) ||
(0, transform_1.fillAdditionalPlaceholders)(this.configuration.empty_template ||
configuration_1.DefaultConfiguration.empty_template, options));
return yield releaseNotes.pull();
});
}
}
@@ -1364,7 +1407,7 @@ exports.validateTransformer = exports.fillAdditionalPlaceholders = exports.build
const core = __importStar(__nccwpck_require__(2186));
const configuration_1 = __nccwpck_require__(5527);
const pullRequests_1 = __nccwpck_require__(4217);
function buildChangelog(prs, options) {
function buildChangelog(diffInfo, prs, options) {
// sort to target order
const config = options.configuration;
const sort = config.sort || configuration_1.DefaultConfiguration.sort;
@@ -1546,6 +1589,12 @@ function buildChangelog(prs, options) {
transformedChangelog = transformedChangelog.replace(/\${{UNCATEGORIZED_COUNT}}/g, uncategorizedPrs.length.toString());
transformedChangelog = transformedChangelog.replace(/\${{OPEN_COUNT}}/g, openPrs.length.toString());
transformedChangelog = transformedChangelog.replace(/\${{IGNORED_COUNT}}/g, ignoredPrs.length.toString());
// code change placeholders
transformedChangelog = transformedChangelog.replace(/\${{CHANGED_FILES}}/g, diffInfo.changedFiles.toString());
transformedChangelog = transformedChangelog.replace(/\${{ADDITIONS}}/g, diffInfo.additions.toString());
transformedChangelog = transformedChangelog.replace(/\${{DELETIONS}}/g, diffInfo.deletions.toString());
transformedChangelog = transformedChangelog.replace(/\${{CHANGES}}/g, diffInfo.changes.toString());
transformedChangelog = transformedChangelog.replace(/\${{COMMITS}}/g, diffInfo.commits.toString());
transformedChangelog = fillAdditionalPlaceholders(transformedChangelog, options);
core.info(`️ Filled template`);
return transformedChangelog;
@@ -1553,6 +1602,7 @@ function buildChangelog(prs, options) {
exports.buildChangelog = buildChangelog;
function fillAdditionalPlaceholders(text, options) {
let transformed = text;
// repository placeholders
transformed = transformed.replace(/\${{OWNER}}/g, options.owner);
transformed = transformed.replace(/\${{REPO}}/g, options.repo);
transformed = transformed.replace(/\${{FROM_TAG}}/g, options.fromTag);
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+56 -19
View File
@@ -2,6 +2,24 @@ import * as core from '@actions/core'
import {Octokit, RestEndpointMethodTypes} from '@octokit/rest'
import moment from 'moment'
export interface DiffInfo {
changedFiles: number
additions: number
deletions: number
changes: number
commits: number
commitInfo: CommitInfo[]
}
export const DefaultDiffInfo: DiffInfo = {
changedFiles: 0,
additions: 0,
deletions: 0,
changes: 0,
commits: 0,
commitInfo: []
}
export interface CommitInfo {
sha: string
summary: string
@@ -18,14 +36,10 @@ export class Commits {
repo: string,
base: string,
head: string
): Promise<CommitInfo[]> {
const commits: CommitInfo[] = await this.getDiffRemote(
owner,
repo,
base,
head
)
return this.sortCommits(commits)
): Promise<DiffInfo> {
const diff: DiffInfo = await this.getDiffRemote(owner, repo, base, head)
diff.commitInfo = this.sortCommits(diff.commitInfo)
return diff
}
private async getDiffRemote(
@@ -33,7 +47,13 @@ export class Commits {
repo: string,
base: string,
head: string
): Promise<CommitInfo[]> {
): Promise<DiffInfo> {
let changedFilesCount = 0
let additionCount = 0
let deletionCount = 0
let changeCount = 0
let commitCount = 0
// Fetch comparisons recursively until we don't find any commits
// This is because the GitHub API limits the number of commits returned in a single response.
let commits: RestEndpointMethodTypes['repos']['compareCommits']['response']['data']['commits'] =
@@ -50,6 +70,16 @@ export class Commits {
if (compareResult.data.total_commits === 0) {
break
}
changedFilesCount += compareResult.data.files?.length ?? 0
const files = compareResult.data.files
if (files !== undefined) {
for (const file of files) {
additionCount += file.additions
deletionCount += file.deletions
changeCount += file.changes
}
}
commitCount += compareResult.data.commits.length
commits = compareResult.data.commits.concat(commits)
compareHead = `${commits[0].sha}^`
}
@@ -58,16 +88,23 @@ export class Commits {
`️ Found ${commits.length} commits from the GitHub API for ${owner}/${repo}`
)
return commits
.filter(commit => commit.sha)
.map(commit => ({
sha: commit.sha || '',
summary: commit.commit.message.split('\n')[0],
message: commit.commit.message,
date: moment(commit.commit.committer?.date),
author: commit.commit.author?.name || '',
prNumber: undefined
}))
return {
changedFiles: changedFilesCount,
additions: additionCount,
deletions: deletionCount,
changes: changeCount,
commits: commitCount,
commitInfo: commits
.filter(commit => commit.sha)
.map(commit => ({
sha: commit.sha || '',
summary: commit.commit.message.split('\n')[0],
message: commit.commit.message,
date: moment(commit.commit.committer?.date),
author: commit.commit.author?.name || '',
prNumber: undefined
}))
}
}
private sortCommits(commits: CommitInfo[]): CommitInfo[] {
+45 -22
View File
@@ -1,9 +1,9 @@
import * as core from '@actions/core'
import {CommitInfo, Commits, filterCommits} from './commits'
import {Commits, filterCommits, DiffInfo, DefaultDiffInfo} from './commits'
import {Configuration, DefaultConfiguration} from './configuration'
import {PullRequestInfo, PullRequests} from './pullRequests'
import {Octokit} from '@octokit/rest'
import {buildChangelog} from './transform'
import {buildChangelog, fillAdditionalPlaceholders} from './transform'
import {failOrError} from './utils'
export interface ReleaseNotesOptions {
@@ -21,11 +21,15 @@ export interface ReleaseNotesOptions {
export class ReleaseNotes {
constructor(private octokit: Octokit, private options: ReleaseNotesOptions) {}
async pull(): Promise<string | null> {
async pull(): Promise<string> {
let mergedPullRequests: PullRequestInfo[]
let diffInfo: DiffInfo
if (!this.options.commitMode) {
core.startGroup(`🚀 Load pull requests`)
mergedPullRequests = await this.getMergedPullRequests(this.octokit)
const [info, prs] = await this.getMergedPullRequests(this.octokit)
mergedPullRequests = prs
diffInfo = info
// define the included PRs within this release as output
core.setOutput(
@@ -37,57 +41,74 @@ export class ReleaseNotes {
.join(',')
)
core.setOutput('changed_files', diffInfo.changedFiles)
core.setOutput('additions', diffInfo.additions)
core.setOutput('deletions', diffInfo.deletions)
core.setOutput('changes', diffInfo.changes)
core.setOutput('commits', diffInfo.commits)
core.endGroup()
} else {
core.startGroup(`🚀 Load commit history`)
core.info(`⚠️ Executing experimental commit mode`)
mergedPullRequests = await this.generateCommitPRs(this.octokit)
const [info, prs] = await this.generateCommitPRs(this.octokit)
mergedPullRequests = prs
diffInfo = info
core.endGroup()
}
if (mergedPullRequests.length === 0) {
core.warning(`⚠️ No pull requests found`)
return null
return fillAdditionalPlaceholders(
this.options.configuration.empty_template ||
DefaultConfiguration.empty_template,
this.options
)
}
core.startGroup('📦 Build changelog')
const resultChangelog = buildChangelog(mergedPullRequests, this.options)
const resultChangelog = buildChangelog(
diffInfo,
mergedPullRequests,
this.options
)
core.endGroup()
return resultChangelog
}
private async getCommitHistory(octokit: Octokit): Promise<CommitInfo[]> {
private async getCommitHistory(octokit: Octokit): Promise<DiffInfo> {
const {owner, repo, fromTag, toTag, failOnError} = this.options
core.info(`️ Comparing ${owner}/${repo} - '${fromTag}...${toTag}'`)
const commitsApi = new Commits(octokit)
let commits: CommitInfo[]
let diffInfo: DiffInfo
try {
commits = await commitsApi.getDiff(owner, repo, fromTag, toTag)
diffInfo = await commitsApi.getDiff(owner, repo, fromTag, toTag)
} catch (error) {
failOrError(
`💥 Failed to retrieve - Invalid tag? - Because of: ${error}`,
failOnError
)
return []
return DefaultDiffInfo
}
if (commits.length === 0) {
if (diffInfo.commitInfo.length === 0) {
core.warning(`⚠️ No commits found between - ${fromTag}...${toTag}`)
return []
return DefaultDiffInfo
}
return commits
return diffInfo
}
private async getMergedPullRequests(
octokit: Octokit
): Promise<PullRequestInfo[]> {
): Promise<[DiffInfo, PullRequestInfo[]]> {
const {owner, repo, includeOpen, fetchReviewers, configuration} =
this.options
const commits = await this.getCommitHistory(octokit)
const diffInfo = await this.getCommitHistory(octokit)
const commits = diffInfo.commitInfo
if (commits.length === 0) {
return []
return [diffInfo, []]
}
const firstCommit = commits[0]
@@ -193,17 +214,18 @@ export class ReleaseNotes {
}
}
return finalPrs
return [diffInfo, finalPrs]
}
private async generateCommitPRs(
octokit: Octokit
): Promise<PullRequestInfo[]> {
): Promise<[DiffInfo, PullRequestInfo[]]> {
const {owner, repo, configuration} = this.options
const commits = await this.getCommitHistory(octokit)
const diffInfo = await this.getCommitHistory(octokit)
const commits = diffInfo.commitInfo
if (commits.length === 0) {
return []
return [diffInfo, []]
}
const prCommits = filterCommits(
@@ -214,7 +236,7 @@ export class ReleaseNotes {
core.info(`️ Retrieved ${prCommits.length} commits for ${owner}/${repo}`)
return prCommits.map(function (commit): PullRequestInfo {
const prs = prCommits.map(function (commit): PullRequestInfo {
return {
number: 0,
title: commit.summary,
@@ -234,5 +256,6 @@ export class ReleaseNotes {
status: 'merged'
}
})
return [diffInfo, prs]
}
}
+1 -9
View File
@@ -4,7 +4,6 @@ import {Octokit} from '@octokit/rest'
import {ReleaseNotes} from './releaseNotes'
import {Tags} from './tags'
import {failOrError} from './utils'
import {fillAdditionalPlaceholders} from './transform'
export class ReleaseNotesBuilder {
constructor(
@@ -98,13 +97,6 @@ export class ReleaseNotesBuilder {
}
const releaseNotes = new ReleaseNotes(octokit, options)
return (
(await releaseNotes.pull()) ||
fillAdditionalPlaceholders(
this.configuration.empty_template ||
DefaultConfiguration.empty_template,
options
)
)
return await releaseNotes.pull()
}
}
+24
View File
@@ -7,8 +7,10 @@ import {
} from './configuration'
import {PullRequestInfo, sortPullRequests} from './pullRequests'
import {ReleaseNotesOptions} from './releaseNotes'
import {DiffInfo} from './commits'
export function buildChangelog(
diffInfo: DiffInfo,
prs: PullRequestInfo[],
options: ReleaseNotesOptions
): string {
@@ -272,6 +274,27 @@ export function buildChangelog(
/\${{IGNORED_COUNT}}/g,
ignoredPrs.length.toString()
)
// code change placeholders
transformedChangelog = transformedChangelog.replace(
/\${{CHANGED_FILES}}/g,
diffInfo.changedFiles.toString()
)
transformedChangelog = transformedChangelog.replace(
/\${{ADDITIONS}}/g,
diffInfo.additions.toString()
)
transformedChangelog = transformedChangelog.replace(
/\${{DELETIONS}}/g,
diffInfo.deletions.toString()
)
transformedChangelog = transformedChangelog.replace(
/\${{CHANGES}}/g,
diffInfo.changes.toString()
)
transformedChangelog = transformedChangelog.replace(
/\${{COMMITS}}/g,
diffInfo.commits.toString()
)
transformedChangelog = fillAdditionalPlaceholders(
transformedChangelog,
options
@@ -286,6 +309,7 @@ export function fillAdditionalPlaceholders(
options: ReleaseNotesOptions
): string {
let transformed = text
// repository placeholders
transformed = transformed.replace(/\${{OWNER}}/g, options.owner)
transformed = transformed.replace(/\${{REPO}}/g, options.repo)
transformed = transformed.replace(/\${{FROM_TAG}}/g, options.fromTag)