- 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:
@@ -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
@@ -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
@@ -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'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
@@ -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++) {
|
||||
|
||||
Reference in New Issue
Block a user