- introduce new flag to include open PRs into the changelog

- only fetch open with this flag
- expand pull request spec with
  - status, created at timestamp
- expand definition for placeholder patterns in the changelog
This commit is contained in:
Mike Penz
2022-04-08 11:00:39 +02:00
parent 7a59e9f4b4
commit 85af6a8181
10 changed files with 333 additions and 58 deletions
+8
View File
@@ -16,6 +16,7 @@ it('Should have empty changelog (tags)', async () => {
repo: 'release-changelog-builder-action',
fromTag: 'v0.0.1',
toTag: 'v0.0.2',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -33,6 +34,7 @@ it('Should match generated changelog (tags)', async () => {
repo: 'release-changelog-builder-action',
fromTag: 'v0.0.1',
toTag: 'v0.0.3',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -58,6 +60,7 @@ it('Should match generated changelog (refs)', async () => {
repo: 'release-changelog-builder-action',
fromTag: '5ec7a2d86fe9f43fdd38d5e254a1117c8a51b4c3',
toTag: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -91,6 +94,7 @@ it('Should match generated changelog and replace all occurrences (refs)', async
repo: 'release-changelog-builder-action',
fromTag: '5ec7a2d86fe9f43fdd38d5e254a1117c8a51b4c3',
toTag: 'fa3788c8c4b3373ef8424ce3eb008a5cd07cc5aa',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -126,6 +130,7 @@ it('Should match ordered ASC', async () => {
repo: 'release-changelog-builder-action',
fromTag: 'v0.3.0',
toTag: 'v0.5.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -148,6 +153,7 @@ it('Should match ordered DESC', async () => {
repo: 'release-changelog-builder-action',
fromTag: 'v0.3.0',
toTag: 'v0.5.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -170,6 +176,7 @@ it('Should ignore PRs not merged into develop branch', async () => {
repo: 'release-changelog-builder-action',
fromTag: 'v1.3.1',
toTag: 'v1.4.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -190,6 +197,7 @@ it('Should ignore PRs not merged into main branch', async () => {
repo: 'release-changelog-builder-action',
fromTag: 'v1.3.1',
toTag: 'v1.4.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
+37
View File
@@ -16,6 +16,7 @@ it('Should match generated changelog (unspecified fromTag)', async () => {
false,
false,
false,
false,
configuration
)
@@ -42,6 +43,7 @@ it('Should match generated changelog (unspecified tags)', async () => {
false,
false,
false,
false,
configuration
)
@@ -65,6 +67,7 @@ it('Should use empty placeholder', async () => {
false,
false,
false,
false,
configuration
)
@@ -89,6 +92,7 @@ it('Should fill empty placeholders', async () => {
false,
false,
false,
false,
configuration
)
@@ -115,6 +119,7 @@ it('Should fill `template` placeholders', async () => {
false,
false,
false,
false,
configuration
)
@@ -141,6 +146,7 @@ it('Should fill `template` placeholders, ignore', async () => {
false,
false,
false,
false,
configuration
)
@@ -167,6 +173,7 @@ it('Uncategorized category', async () => {
false,
false,
false,
false,
configuration
)
@@ -192,6 +199,7 @@ it('Verify commit based changelog', async () => {
'v0.0.3',
false,
false,
false,
true,
configuration
)
@@ -218,6 +226,7 @@ it('Verify commit based changelog, with emoji categorisation', async () => {
'17a9e4dfaedcabe6a6eff2754bebb715e1c58ec4',
false,
false,
false,
true,
configuration
)
@@ -228,3 +237,31 @@ it('Verify commit based changelog, with emoji categorisation', async () => {
`## 🚀 Features\n\n- add dynamic merging\n- add auto-cleaning\n- add built-in adb support\n- add adb fallback (thanks to @mikepenz ;))\n- add install note\n- add @mikepenz to credits\n\n## 🐛 Fixes\n\n- fix dynamic lib replacement\n- fix apostrophe issue with app name\n- fix java.util.logger error\n\n## 💬 Other\n\n- update screenshot with truecaller stack\n\n`
)
})
it('Verify inclusion of open PRs', async () => {
const configuration = resolveConfiguration(
'',
'configs_test/configuration_commits_emoji.json'
)
const releaseNotesBuilder = new ReleaseNotesBuilder(
null, // baseUrl
null, // token
'.', // repoPath
'mikepenz', // user
'release-changelog-builder-action-playground', // repo
'1.5.0', // fromTag
'2.0.0', // toTag
true, // includeOpen
false, // failOnError
false, // ignorePrePrelease
true, // commitMode
configuration // configuration
)
const changeLog = await releaseNotesBuilder.build()
console.log(changeLog)
expect(changeLog).toStrictEqual(
``
)
})
+39 -9
View File
@@ -33,6 +33,7 @@ mergedPullRequests.push(
title: '[Feature][AB-1234] - this is a PR 1 title message',
htmlURL: '',
baseBranch: '',
createdAt: moment(),
mergedAt: moment(),
mergeCommitSha: 'sha1',
author: 'Mike',
@@ -41,13 +42,15 @@ mergedPullRequests.push(
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
requestedReviewers: [],
status: "merged"
},
{
number: 2,
title: '[Issue][AB-4321] - this is a PR 2 title message',
htmlURL: '',
baseBranch: '',
createdAt: moment(),
mergedAt: moment(),
mergeCommitSha: 'sha1',
author: 'Mike',
@@ -56,13 +59,15 @@ mergedPullRequests.push(
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
requestedReviewers: [],
status: "merged"
},
{
number: 3,
title: '[Issue][Feature][AB-1234321] - this is a PR 3 title message',
htmlURL: '',
baseBranch: '',
createdAt: moment(),
mergedAt: moment(),
mergeCommitSha: 'sha1',
author: 'Mike',
@@ -71,13 +76,15 @@ mergedPullRequests.push(
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
requestedReviewers: [],
status: "merged"
},
{
number: 4,
title: '[AB-404] - not found label',
htmlURL: '',
baseBranch: '',
createdAt: moment(),
mergedAt: moment(),
mergeCommitSha: 'sha1',
author: 'Mike',
@@ -86,7 +93,8 @@ mergedPullRequests.push(
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
requestedReviewers: [],
status: "merged"
}
)
@@ -95,6 +103,7 @@ const pullRequestWithLabelInBody: PullRequestInfo = {
title: 'label in body',
htmlURL: '',
baseBranch: '',
createdAt: moment(),
mergedAt: moment(),
mergeCommitSha: 'sha1',
author: 'Mike',
@@ -103,7 +112,8 @@ const pullRequestWithLabelInBody: PullRequestInfo = {
milestone: '',
body: '[Issue][Feature][AB-1234321] - no magic body for this matter',
assignees: [],
requestedReviewers: []
requestedReviewers: [],
status: "merged"
}
it('Extract label from title, combined regex', async () => {
@@ -120,6 +130,7 @@ it('Extract label from title, combined regex', async () => {
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -147,6 +158,7 @@ it('Extract label from title and body, combined regex', async () => {
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -176,6 +188,7 @@ it('Extract label from title, split regex', async () => {
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -205,6 +218,7 @@ it('Extract label from title, match', async () => {
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -229,6 +243,7 @@ it('Extract label from title, match multiple', async () => {
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -254,6 +269,7 @@ it('Extract label from title, match multiple, custon non matching label', async
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration
@@ -272,6 +288,7 @@ pullRequestsWithLabels.push(
title: '[ABC-1234] - this is a PR 1 title message',
htmlURL: '',
baseBranch: '',
createdAt: moment(),
mergedAt: moment(),
mergeCommitSha: 'sha1-1',
author: 'Mike',
@@ -280,13 +297,15 @@ pullRequestsWithLabels.push(
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
requestedReviewers: [],
status: "merged"
},
{
number: 2,
title: '[ABC-4321] - this is a PR 2 title message',
htmlURL: '',
baseBranch: '',
createdAt: moment(),
mergedAt: moment(),
mergeCommitSha: 'sha1-2',
author: 'Mike',
@@ -295,13 +314,15 @@ pullRequestsWithLabels.push(
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
requestedReviewers: [],
status: "merged"
},
{
number: 3,
title: '[ABC-1234] - this is a PR 3 title message',
htmlURL: '',
baseBranch: '',
createdAt: moment(),
mergedAt: moment().add(1, 'days'),
mergeCommitSha: 'sha1-3',
author: 'Mike',
@@ -310,13 +331,15 @@ pullRequestsWithLabels.push(
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
requestedReviewers: [],
status: "merged"
},
{
number: 4,
title: '[AB-404] - not found label',
htmlURL: '',
baseBranch: '',
createdAt: moment(),
mergedAt: moment(),
mergeCommitSha: 'sha1-4',
author: 'Mike',
@@ -325,7 +348,8 @@ pullRequestsWithLabels.push(
milestone: '',
body: 'no magic body for this matter',
assignees: [],
requestedReviewers: []
requestedReviewers: [],
status: "merged"
}
)
@@ -354,6 +378,7 @@ it('Match multiple labels exhaustive for category', async () => {
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration: customConfig
@@ -377,6 +402,7 @@ it('Deduplicate duplicated PRs', async () => {
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration: customConfig
@@ -401,6 +427,7 @@ it('Deduplicate duplicated PRs DESC', async () => {
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration: customConfig
@@ -421,6 +448,7 @@ it('Commit SHA-1 in commitMode', async () => {
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
includeOpen: false,
failOnError: false,
commitMode: true,
configuration: customConfig
@@ -440,6 +468,7 @@ it('Release Diff', async () => {
repo: 'release-changelog-builder-action',
fromTag: 'v2.8.0',
toTag: 'v2.8.1',
includeOpen: false,
failOnError: false,
commitMode: true,
configuration: customConfig
@@ -477,6 +506,7 @@ it('Use exclude labels to not include a PR within a category.', async () => {
repo: 'test-repo',
fromTag: '1.0.0',
toTag: '2.0.0',
includeOpen: false,
failOnError: false,
commitMode: false,
configuration: customConfig
Generated Vendored
+112 -25
View File
@@ -377,10 +377,11 @@ function run() {
const fromTag = core.getInput('fromTag');
const toTag = core.getInput('toTag');
// read in flags
const includeOpen = core.getInput('includeOpen') === 'true';
const ignorePreReleases = core.getInput('ignorePreReleases') === 'true';
const failOnError = core.getInput('failOnError') === 'true';
const commitMode = core.getInput('commitMode') === 'true';
const result = yield new releaseNotesBuilder_1.ReleaseNotesBuilder(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, failOnError, ignorePreReleases, commitMode, configuration).build();
const result = yield new releaseNotesBuilder_1.ReleaseNotesBuilder(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen, failOnError, ignorePreReleases, commitMode, configuration).build();
core.setOutput('changelog', result);
// write the result in changelog to file if possible
const outputFile = core.getInput('outputFile');
@@ -487,7 +488,7 @@ class PullRequests {
const response = _c.value;
const prs = response.data;
for (const pr of prs.filter(p => !!p.merged_at)) {
mergedPRs.push(mapPullRequest(pr));
mergedPRs.push(mapPullRequest(pr, 'merged'));
}
const firstPR = prs[0];
if (firstPR === undefined ||
@@ -511,15 +512,56 @@ class PullRequests {
return sortPullRequests(mergedPRs, true);
});
}
getOpen(owner, repo, maxPullRequests) {
var e_2, _a;
return __awaiter(this, void 0, void 0, function* () {
const mergedPRs = [];
const options = this.octokit.pulls.list.endpoint.merge({
owner,
repo,
state: 'open',
sort: 'created',
per_page: '100',
direction: 'desc'
});
try {
for (var _b = __asyncValues(this.octokit.paginate.iterator(options)), _c; _c = yield _b.next(), !_c.done;) {
const response = _c.value;
const prs = response.data;
for (const pr of prs) {
mergedPRs.push(mapPullRequest(pr, 'open'));
}
const firstPR = prs[0];
if (firstPR === undefined || mergedPRs.length >= maxPullRequests) {
if (mergedPRs.length >= maxPullRequests) {
core.warning(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests}`);
}
// bail out early to not keep iterating on PRs super old
return sortPullRequests(mergedPRs, true);
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
}
finally { if (e_2) throw e_2.error; }
}
return sortPullRequests(mergedPRs, true);
});
}
}
exports.PullRequests = PullRequests;
function sortPullRequests(pullRequests, ascending) {
if (ascending) {
pullRequests.sort((a, b) => {
if (a.mergedAt.isBefore(b.mergedAt)) {
const aa = a.mergedAt || a.createdAt;
const bb = b.mergedAt || b.createdAt;
if (aa.isBefore(bb)) {
return -1;
}
else if (b.mergedAt.isBefore(a.mergedAt)) {
else if (bb.isBefore(aa)) {
return 1;
}
return 0;
@@ -527,10 +569,12 @@ function sortPullRequests(pullRequests, ascending) {
}
else {
pullRequests.sort((b, a) => {
if (a.mergedAt.isBefore(b.mergedAt)) {
const aa = a.mergedAt || a.createdAt;
const bb = b.mergedAt || b.createdAt;
if (aa.isBefore(bb)) {
return -1;
}
else if (b.mergedAt.isBefore(a.mergedAt)) {
else if (bb.isBefore(aa)) {
return 1;
}
return 0;
@@ -539,22 +583,29 @@ function sortPullRequests(pullRequests, ascending) {
return pullRequests;
}
exports.sortPullRequests = sortPullRequests;
const mapPullRequest = (pr) => {
// helper function to add a special open label to prs not merged.
function addOpenLabel(labels) {
labels.add('##rcba-open');
return labels;
}
const mapPullRequest = (pr, status = 'open') => {
var _a, _b, _c, _d, _e;
return ({
number: pr.number,
title: pr.title,
htmlURL: pr.html_url,
baseBranch: pr.base.ref,
mergedAt: (0, moment_1.default)(pr.merged_at),
createdAt: (0, moment_1.default)(pr.created_at),
mergedAt: pr.merged_at ? (0, moment_1.default)(pr.merged_at) : null,
mergeCommitSha: pr.merge_commit_sha || '',
author: ((_a = pr.user) === null || _a === void 0 ? void 0 : _a.login) || '',
repoName: pr.base.repo.full_name,
labels: new Set(((_b = pr.labels) === null || _b === void 0 ? void 0 : _b.map(lbl => { var _a; return ((_a = lbl.name) === null || _a === void 0 ? void 0 : _a.toLocaleLowerCase('en')) || ''; })) || []),
labels: addOpenLabel(new Set(((_b = pr.labels) === null || _b === void 0 ? void 0 : _b.map(lbl => { var _a; return ((_a = lbl.name) === null || _a === void 0 ? void 0 : _a.toLocaleLowerCase('en')) || ''; })) || [])),
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(asignee => (asignee === null || asignee === void 0 ? void 0 : asignee.login) || '')) || [],
requestedReviewers: ((_e = pr.requested_reviewers) === null || _e === void 0 ? void 0 : _e.map(reviewer => (reviewer === null || reviewer === void 0 ? void 0 : reviewer.login) || '')) || []
requestedReviewers: ((_e = pr.requested_reviewers) === null || _e === void 0 ? void 0 : _e.map(reviewer => (reviewer === null || reviewer === void 0 ? void 0 : reviewer.login) || '')) || [],
status
});
};
@@ -663,7 +714,7 @@ class ReleaseNotes {
}
getMergedPullRequests(octokit) {
return __awaiter(this, void 0, void 0, function* () {
const { owner, repo, configuration } = this.options;
const { owner, repo, includeOpen, configuration } = this.options;
const commits = yield this.getCommitHistory(octokit);
if (commits.length === 0) {
return [];
@@ -690,21 +741,32 @@ class ReleaseNotes {
const releaseCommitHashes = prCommits.map(commmit => {
return commmit.sha;
});
// filter out pull requests not associated with this release
const mergedPullRequests = pullRequests.filter(pr => {
return releaseCommitHashes.includes(pr.mergeCommitSha);
});
let allPullRequests = mergedPullRequests;
if (includeOpen) {
// retrieve all open pull requests
const openPullRequests = yield pullRequestsApi.getOpen(owner, repo, configuration.max_pull_requests ||
configuration_1.DefaultConfiguration.max_pull_requests);
core.info(`️ Retrieved ${pullRequests.length} open PRs for ${owner}/${repo}`);
// all pull requests
allPullRequests = allPullRequests.concat(openPullRequests);
}
// retrieve base branches we allow
const baseBranches = configuration.base_branches || configuration_1.DefaultConfiguration.base_branches;
const baseBranchPatterns = baseBranches.map(baseBranch => {
return new RegExp(baseBranch.replace('\\\\', '\\'), 'gu');
});
// return only the pull requests associated with this release
// and if the baseBranch is matching the configuration
return pullRequests.filter(pr => {
let keep = releaseCommitHashes.includes(pr.mergeCommitSha);
if (keep && baseBranches.length !== 0) {
keep = baseBranchPatterns.some(pattern => {
// return only prs if the baseBranch is matching the configuration
return allPullRequests.filter(pr => {
if (baseBranches.length !== 0) {
return baseBranchPatterns.some(pattern => {
return pr.baseBranch.match(pattern) !== null;
});
}
return keep;
return true;
});
});
}
@@ -724,6 +786,7 @@ class ReleaseNotes {
title: commit.summary,
htmlURL: '',
baseBranch: '',
createdAt: commit.date,
mergedAt: commit.date,
mergeCommitSha: commit.sha,
author: commit.author || '',
@@ -732,7 +795,8 @@ class ReleaseNotes {
milestone: '',
body: commit.message || '',
assignees: [],
requestedReviewers: []
requestedReviewers: [],
status: 'merged'
};
});
});
@@ -790,7 +854,7 @@ 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, failOnError, ignorePreReleases, commitMode, configuration) {
constructor(baseUrl, token, repositoryPath, owner, repo, fromTag, toTag, includeOpen, failOnError, ignorePreReleases, commitMode, configuration) {
this.baseUrl = baseUrl;
this.token = token;
this.repositoryPath = repositoryPath;
@@ -798,6 +862,7 @@ class ReleaseNotesBuilder {
this.repo = repo;
this.fromTag = fromTag;
this.toTag = toTag;
this.includeOpen = includeOpen;
this.failOnError = failOnError;
this.ignorePreReleases = ignorePreReleases;
this.commitMode = commitMode;
@@ -857,6 +922,7 @@ class ReleaseNotesBuilder {
repo: this.repo,
fromTag: this.fromTag,
toTag: this.toTag,
includeOpen: this.includeOpen,
failOnError: this.failOnError,
commitMode: this.commitMode,
configuration: this.configuration
@@ -1293,6 +1359,7 @@ function buildChangelog(prs, options) {
}
const categorizedPrs = [];
const ignoredPrs = [];
const openPrs = [];
const uncategorizedPrs = [];
// bring elements in order
for (const [pr, body] of transformedMap) {
@@ -1300,6 +1367,9 @@ function buildChangelog(prs, options) {
ignoredPrs.push(body);
continue;
}
if (pr.status === 'open') {
openPrs.push(body);
}
let matched = false;
for (const [category, pullRequests] of categorized) {
// check if any exclude label matches
@@ -1372,6 +1442,19 @@ function buildChangelog(prs, options) {
}
}
core.setOutput('uncategorized_prs', uncategorizedPrs.length);
let changelogOpen = '';
if (openPrs.length > 0) {
for (const pr of openPrs) {
changelogOpen = `${changelogOpen + pr}\n`;
}
core.info(`✒️ Wrote ${openPrs.length} open pull requests down`);
if (core.isDebug()) {
for (const pr of openPrs) {
core.debug(` ${pr}`);
}
}
core.setOutput('open_prs', openPrs.length);
}
let changelogIgnored = '';
for (const pr of ignoredPrs) {
changelogIgnored = `${changelogIgnored + pr}\n`;
@@ -1386,10 +1469,12 @@ function buildChangelog(prs, options) {
let transformedChangelog = config.template || configuration_1.DefaultConfiguration.template;
transformedChangelog = transformedChangelog.replace(/\${{CHANGELOG}}/g, changelog);
transformedChangelog = transformedChangelog.replace(/\${{UNCATEGORIZED}}/g, changelogUncategorized);
transformedChangelog = transformedChangelog.replace(/\${{OPEN}}/g, changelogOpen);
transformedChangelog = transformedChangelog.replace(/\${{IGNORED}}/g, changelogIgnored);
// fill other placeholders
transformedChangelog = transformedChangelog.replace(/\${{CATEGORIZED_COUNT}}/g, categorizedPrs.length.toString());
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());
transformedChangelog = fillAdditionalPlaceholders(transformedChangelog, options);
core.info(`️ Filled template`);
@@ -1413,19 +1498,21 @@ function haveEveryElements(arr1, arr2) {
return arr1.every(item => arr2.has(item));
}
function fillTemplate(pr, template) {
var _a, _b, _c;
var _a, _b, _c, _d, _e;
let transformed = template;
transformed = transformed.replace(/\${{NUMBER}}/g, pr.number.toString());
transformed = transformed.replace(/\${{TITLE}}/g, pr.title);
transformed = transformed.replace(/\${{URL}}/g, pr.htmlURL);
transformed = transformed.replace(/\${{MERGED_AT}}/g, pr.mergedAt.toISOString());
transformed = transformed.replace(/\${{STATUS}}/g, pr.status);
transformed = transformed.replace(/\${{CREATED_AT}}/g, pr.createdAt.toISOString());
transformed = transformed.replace(/\${{MERGED_AT}}/g, ((_a = pr.mergedAt) === null || _a === void 0 ? void 0 : _a.toISOString()) || '');
transformed = transformed.replace(/\${{MERGE_SHA}}/g, pr.mergeCommitSha);
transformed = transformed.replace(/\${{AUTHOR}}/g, pr.author);
transformed = transformed.replace(/\${{LABELS}}/g, ((_a = [...pr.labels]) === null || _a === void 0 ? void 0 : _a.join(', ')) || '');
transformed = transformed.replace(/\${{LABELS}}/g, ((_c = (_b = [...pr.labels]) === null || _b === void 0 ? void 0 : _b.filter(l => !l.startsWith('##rcba-'))) === null || _c === void 0 ? void 0 : _c.join(', ')) || '');
transformed = transformed.replace(/\${{MILESTONE}}/g, pr.milestone || '');
transformed = transformed.replace(/\${{BODY}}/g, pr.body);
transformed = transformed.replace(/\${{ASSIGNEES}}/g, ((_b = pr.assignees) === null || _b === void 0 ? void 0 : _b.join(', ')) || '');
transformed = transformed.replace(/\${{REVIEWERS}}/g, ((_c = pr.requestedReviewers) === null || _c === void 0 ? void 0 : _c.join(', ')) || '');
transformed = transformed.replace(/\${{ASSIGNEES}}/g, ((_d = pr.assignees) === null || _d === void 0 ? void 0 : _d.join(', ')) || '');
transformed = transformed.replace(/\${{REVIEWERS}}/g, ((_e = pr.requestedReviewers) === null || _e === void 0 ? void 0 : _e.join(', ')) || '');
return transformed;
}
function transform(filled, transformers) {
Generated Vendored
+1 -1
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -32,6 +32,7 @@ async function run(): Promise<void> {
const fromTag = core.getInput('fromTag')
const toTag = core.getInput('toTag')
// read in flags
const includeOpen = core.getInput('includeOpen') === 'true'
const ignorePreReleases = core.getInput('ignorePreReleases') === 'true'
const failOnError = core.getInput('failOnError') === 'true'
const commitMode = core.getInput('commitMode') === 'true'
@@ -44,6 +45,7 @@ async function run(): Promise<void> {
repo,
fromTag,
toTag,
includeOpen,
failOnError,
ignorePreReleases,
commitMode,
+63 -10
View File
@@ -8,7 +8,8 @@ export interface PullRequestInfo {
title: string
htmlURL: string
baseBranch: string
mergedAt: moment.Moment
createdAt: moment.Moment
mergedAt: moment.Moment | null
mergeCommitSha: string
author: string
repoName: string
@@ -17,6 +18,7 @@ export interface PullRequestInfo {
body: string
assignees: string[]
requestedReviewers: string[]
status: 'open' | 'merged'
}
type PullData = RestEndpointMethodTypes['pulls']['get']['response']['data']
@@ -69,7 +71,7 @@ export class PullRequests {
const prs: PullsListData = response.data as PullsListData
for (const pr of prs.filter(p => !!p.merged_at)) {
mergedPRs.push(mapPullRequest(pr))
mergedPRs.push(mapPullRequest(pr, 'merged'))
}
const firstPR = prs[0]
@@ -89,6 +91,42 @@ export class PullRequests {
return sortPullRequests(mergedPRs, true)
}
async getOpen(
owner: string,
repo: string,
maxPullRequests: number
): Promise<PullRequestInfo[]> {
const mergedPRs: PullRequestInfo[] = []
const options = this.octokit.pulls.list.endpoint.merge({
owner,
repo,
state: 'open',
sort: 'created',
per_page: '100',
direction: 'desc'
})
for await (const response of this.octokit.paginate.iterator(options)) {
const prs: PullsListData = response.data as PullsListData
for (const pr of prs) {
mergedPRs.push(mapPullRequest(pr, 'open'))
}
const firstPR = prs[0]
if (firstPR === undefined || mergedPRs.length >= maxPullRequests) {
if (mergedPRs.length >= maxPullRequests) {
core.warning(`⚠️ Reached 'maxPullRequests' count ${maxPullRequests}`)
}
// bail out early to not keep iterating on PRs super old
return sortPullRequests(mergedPRs, true)
}
}
return sortPullRequests(mergedPRs, true)
}
}
export function sortPullRequests(
@@ -97,18 +135,22 @@ export function sortPullRequests(
): PullRequestInfo[] {
if (ascending) {
pullRequests.sort((a, b) => {
if (a.mergedAt.isBefore(b.mergedAt)) {
const aa = a.mergedAt || a.createdAt
const bb = b.mergedAt || b.createdAt
if (aa.isBefore(bb)) {
return -1
} else if (b.mergedAt.isBefore(a.mergedAt)) {
} else if (bb.isBefore(aa)) {
return 1
}
return 0
})
} else {
pullRequests.sort((b, a) => {
if (a.mergedAt.isBefore(b.mergedAt)) {
const aa = a.mergedAt || a.createdAt
const bb = b.mergedAt || b.createdAt
if (aa.isBefore(bb)) {
return -1
} else if (b.mergedAt.isBefore(a.mergedAt)) {
} else if (bb.isBefore(aa)) {
return 1
}
return 0
@@ -117,23 +159,34 @@ export function sortPullRequests(
return pullRequests
}
// helper function to add a special open label to prs not merged.
function addOpenLabel(labels: Set<string>): Set<string> {
labels.add('##rcba-open')
return labels
}
const mapPullRequest = (
pr: PullData | Unpacked<PullsListData>
pr: PullData | Unpacked<PullsListData>,
status: 'open' | 'merged' = 'open'
): PullRequestInfo => ({
number: pr.number,
title: pr.title,
htmlURL: pr.html_url,
baseBranch: pr.base.ref,
mergedAt: moment(pr.merged_at),
createdAt: moment(pr.created_at),
mergedAt: pr.merged_at ? moment(pr.merged_at) : null,
mergeCommitSha: pr.merge_commit_sha || '',
author: pr.user?.login || '',
repoName: pr.base.repo.full_name,
labels: new Set(
labels: addOpenLabel(
new Set(
pr.labels?.map(lbl => lbl.name?.toLocaleLowerCase('en') || '') || []
)
),
milestone: pr.milestone?.title || '',
body: pr.body || '',
assignees: pr.assignees?.map(asignee => asignee?.login || '') || [],
requestedReviewers:
pr.requested_reviewers?.map(reviewer => reviewer?.login || '') || []
pr.requested_reviewers?.map(reviewer => reviewer?.login || '') || [],
status
})
+33 -9
View File
@@ -11,6 +11,7 @@ export interface ReleaseNotesOptions {
repo: string // the repository
fromTag: string // the tag/ref to start from
toTag: string // the tag/ref up to
includeOpen: boolean // defines if we should also fetch open pull requests
failOnError: boolean // defines if we should fail the action in case of an error
commitMode: boolean // defines if we use the alternative commit based mode. note: this is only partially supported
configuration: Configuration // the configuration as defined in `configuration.ts`
@@ -80,7 +81,7 @@ export class ReleaseNotes {
private async getMergedPullRequests(
octokit: Octokit
): Promise<PullRequestInfo[]> {
const {owner, repo, configuration} = this.options
const {owner, repo, includeOpen, configuration} = this.options
const commits = await this.getCommitHistory(octokit)
if (commits.length === 0) {
@@ -133,6 +134,29 @@ export class ReleaseNotes {
return commmit.sha
})
// filter out pull requests not associated with this release
const mergedPullRequests = pullRequests.filter(pr => {
return releaseCommitHashes.includes(pr.mergeCommitSha)
})
let allPullRequests = mergedPullRequests
if (includeOpen) {
// retrieve all open pull requests
const openPullRequests = await pullRequestsApi.getOpen(
owner,
repo,
configuration.max_pull_requests ||
DefaultConfiguration.max_pull_requests
)
core.info(
`️ Retrieved ${pullRequests.length} open PRs for ${owner}/${repo}`
)
// all pull requests
allPullRequests = allPullRequests.concat(openPullRequests)
}
// retrieve base branches we allow
const baseBranches =
configuration.base_branches || DefaultConfiguration.base_branches
@@ -140,16 +164,14 @@ export class ReleaseNotes {
return new RegExp(baseBranch.replace('\\\\', '\\'), 'gu')
})
// return only the pull requests associated with this release
// and if the baseBranch is matching the configuration
return pullRequests.filter(pr => {
let keep = releaseCommitHashes.includes(pr.mergeCommitSha)
if (keep && baseBranches.length !== 0) {
keep = baseBranchPatterns.some(pattern => {
// return only prs if the baseBranch is matching the configuration
return allPullRequests.filter(pr => {
if (baseBranches.length !== 0) {
return baseBranchPatterns.some(pattern => {
return pr.baseBranch.match(pattern) !== null
})
}
return keep
return true
})
}
@@ -177,6 +199,7 @@ export class ReleaseNotes {
title: commit.summary,
htmlURL: '',
baseBranch: '',
createdAt: commit.date,
mergedAt: commit.date,
mergeCommitSha: commit.sha,
author: commit.author || '',
@@ -185,7 +208,8 @@ export class ReleaseNotes {
milestone: '',
body: commit.message || '',
assignees: [],
requestedReviewers: []
requestedReviewers: [],
status: 'merged'
}
})
}
+2
View File
@@ -15,6 +15,7 @@ export class ReleaseNotesBuilder {
private repo: string | null,
private fromTag: string | null,
private toTag: string | null,
private includeOpen: boolean,
private failOnError: boolean,
private ignorePreReleases: boolean,
private commitMode: boolean,
@@ -88,6 +89,7 @@ export class ReleaseNotesBuilder {
repo: this.repo,
fromTag: this.fromTag,
toTag: this.toTag,
includeOpen: this.includeOpen,
failOnError: this.failOnError,
commitMode: this.commitMode,
configuration: this.configuration
+35 -3
View File
@@ -95,6 +95,7 @@ export function buildChangelog(
const categorizedPrs: string[] = []
const ignoredPrs: string[] = []
const openPrs: string[] = []
const uncategorizedPrs: string[] = []
// bring elements in order
@@ -109,6 +110,10 @@ export function buildChangelog(
continue
}
if (pr.status === 'open') {
openPrs.push(body)
}
let matched = false
for (const [category, pullRequests] of categorized) {
// check if any exclude label matches
@@ -206,6 +211,20 @@ export function buildChangelog(
}
core.setOutput('uncategorized_prs', uncategorizedPrs.length)
let changelogOpen = ''
if (openPrs.length > 0) {
for (const pr of openPrs) {
changelogOpen = `${changelogOpen + pr}\n`
}
core.info(`✒️ Wrote ${openPrs.length} open pull requests down`)
if (core.isDebug()) {
for (const pr of openPrs) {
core.debug(` ${pr}`)
}
}
core.setOutput('open_prs', openPrs.length)
}
let changelogIgnored = ''
for (const pr of ignoredPrs) {
changelogIgnored = `${changelogIgnored + pr}\n`
@@ -227,6 +246,10 @@ export function buildChangelog(
/\${{UNCATEGORIZED}}/g,
changelogUncategorized
)
transformedChangelog = transformedChangelog.replace(
/\${{OPEN}}/g,
changelogOpen
)
transformedChangelog = transformedChangelog.replace(
/\${{IGNORED}}/g,
changelogIgnored
@@ -241,6 +264,10 @@ export function buildChangelog(
/\${{UNCATEGORIZED_COUNT}}/g,
uncategorizedPrs.length.toString()
)
transformedChangelog = transformedChangelog.replace(
/\${{OPEN_COUNT}}/g,
openPrs.length.toString()
)
transformedChangelog = transformedChangelog.replace(
/\${{IGNORED_COUNT}}/g,
ignoredPrs.length.toString()
@@ -283,15 +310,20 @@ function fillTemplate(pr: PullRequestInfo, template: string): string {
transformed = transformed.replace(/\${{NUMBER}}/g, pr.number.toString())
transformed = transformed.replace(/\${{TITLE}}/g, pr.title)
transformed = transformed.replace(/\${{URL}}/g, pr.htmlURL)
transformed = transformed.replace(/\${{STATUS}}/g, pr.status)
transformed = transformed.replace(
/\${{CREATED_AT}}/g,
pr.createdAt.toISOString()
)
transformed = transformed.replace(
/\${{MERGED_AT}}/g,
pr.mergedAt.toISOString()
pr.mergedAt?.toISOString() || ''
)
transformed = transformed.replace(/\${{MERGE_SHA}}/g, pr.mergeCommitSha)
transformed = transformed.replace(/\${{AUTHOR}}/g, pr.author)
transformed = transformed.replace(
/\${{LABELS}}/g,
[...pr.labels]?.join(', ') || ''
[...pr.labels]?.filter(l => !l.startsWith('##rcba-'))?.join(', ') || ''
)
transformed = transformed.replace(/\${{MILESTONE}}/g, pr.milestone || '')
transformed = transformed.replace(/\${{BODY}}/g, pr.body)
@@ -384,7 +416,7 @@ function extractValues(
if (extractor.onProperty !== undefined) {
let results: string[] = []
const list: ('title' | 'author' | 'milestone' | 'body')[] =
const list: ('title' | 'author' | 'milestone' | 'body' | 'status')[] =
extractor.onProperty
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < list.length; i++) {