- 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
+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,
+64 -11
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(
pr.labels?.map(lbl => lbl.name?.toLocaleLowerCase('en') || '') || []
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++) {