Skip to content

Commit

Permalink
feat: include referenced issues from PRs
Browse files Browse the repository at this point in the history
  • Loading branch information
NGPixel committed Dec 15, 2022
1 parent d4736e8 commit f64e045
Show file tree
Hide file tree
Showing 8 changed files with 2,033 additions and 35 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,4 @@ typings/
.tern-port

workflow
test-output.md
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ jobs:
* `toTag`: The tag up to which the changelog is to be determined (oldest) - **REQUIRED (unless using `tag`)**
* `excludeTypes`: A comma-separated list of commit types you want to exclude from the changelog (e.g. `doc,chore,perf`) - **Optional** - Default: `build,docs,other,style`
* `writeToFile`: Should CHANGELOG.md be updated with latest changelog - **Optional** - Default: `true`
* `includeRefIssues`: Should the changelog include the issues referenced for each PR. - **Optional** - Default: `true`
* `useGitmojis`: Should type headers be prepended with their related gitmoji - **Optional** - Default: `true`
* `includeInvalidCommits`: Whether to include commits that don't respect the Conventional Commits format - **Optional** - Default: `false`

Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ inputs:
description: Should CHANGELOG.md be updated with latest changelog
required: false
default: 'true'
includeRefIssues:
description: Should the changelog include the issues referenced for each PR.
required: false
default: 'true'
useGitmojis:
description: Prepend type headers with their corresponding gitmoji
required: false
Expand Down
79 changes: 66 additions & 13 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27982,6 +27982,14 @@ module.exports = require("stream");

/***/ }),

/***/ 8670:
/***/ ((module) => {

"use strict";
module.exports = require("timers/promises");

/***/ }),

/***/ 4404:
/***/ ((module) => {

Expand Down Expand Up @@ -28080,10 +28088,11 @@ const core = __nccwpck_require__(2186)
const _ = __nccwpck_require__(250)
const cc = __nccwpck_require__(4523)
const fs = (__nccwpck_require__(7147).promises)
const { setTimeout } = __nccwpck_require__(8670)

const types = [
{ types: ['feat', 'feature'], header: 'New Features', icon: ':sparkles:' },
{ types: ['fix', 'bugfix'], header: 'Bug Fixes', icon: ':bug:' },
{ types: ['fix', 'bugfix'], header: 'Bug Fixes', icon: ':bug:', relIssuePrefix: 'fixes' },
{ types: ['perf'], header: 'Performance Improvements', icon: ':zap:' },
{ types: ['refactor'], header: 'Refactors', icon: ':recycle:' },
{ types: ['test', 'tests'], header: 'Tests', icon: ':white_check_mark:' },
Expand All @@ -28099,31 +28108,37 @@ const rePrEnding = /\(#([0-9]+)\)$/

function buildSubject ({ writeToFile, subject, author, authorUrl, owner, repo }) {
const hasPR = rePrEnding.test(subject)
let final = subject
const prs = []
let output = subject
if (writeToFile) {
if (hasPR) {
const prMatch = subject.match(rePrEnding)
const msgOnly = subject.slice(0, prMatch[0].length * -1)
final = msgOnly.replace(rePrId, (m, prId) => {
output = msgOnly.replace(rePrId, (m, prId) => {
prs.push(prId)
return `[#${prId}](https://github.com/${owner}/${repo}/pull/${prId})`
})
final += `*(PR [#${prMatch[1]}](https://github.com/${owner}/${repo}/pull/${prMatch[1]}) by [@${author}](${authorUrl}))*`
output += `*(PR [#${prMatch[1]}](https://github.com/${owner}/${repo}/pull/${prMatch[1]}) by [@${author}](${authorUrl}))*`
} else {
final = subject.replace(rePrId, (m, prId) => {
output = subject.replace(rePrId, (m, prId) => {
return `[#${prId}](https://github.com/${owner}/${repo}/pull/${prId})`
})
final += ` *(commit by [@${author}](${authorUrl}))*`
output += ` *(commit by [@${author}](${authorUrl}))*`
}
} else {
if (hasPR) {
final = subject.replace(rePrEnding, (m, prId) => {
output = subject.replace(rePrEnding, (m, prId) => {
prs.push(prId)
return `*(PR #${prId} by @${author})*`
})
} else {
final = `${subject} *(commit by @${author})*`
output = `${subject} *(commit by @${author})*`
}
}
return final
return {
output,
prs
}
}

async function main () {
Expand All @@ -28133,6 +28148,7 @@ async function main () {
const toTag = core.getInput('toTag')
const excludeTypes = (core.getInput('excludeTypes') || '').split(',').map(t => t.trim())
const writeToFile = core.getBooleanInput('writeToFile')
const includeRefIssues = core.getBooleanInput('includeRefIssues')
const useGitmojis = core.getBooleanInput('useGitmojis')
const includeInvalidCommits = core.getBooleanInput('includeInvalidCommits')
const gh = github.getOctokit(token)
Expand Down Expand Up @@ -28262,6 +28278,7 @@ async function main () {
author: commit.author.login,
authorUrl: commit.author.html_url
})
core.info(`[OK] Commit ${commit.sha} with invalid type, falling back to other - ${commit.commit.message}`)
} else {
core.info(`[INVALID] Skipping commit ${commit.sha} as it doesn't follow conventional commit format.`)
}
Expand Down Expand Up @@ -28299,8 +28316,8 @@ async function main () {
owner,
repo
})
changesFile.push(`- due to [\`${breakChange.sha.substring(0, 7)}\`](${breakChange.url}) - ${subjectFile}:\n\n${body}\n`)
changesVar.push(`- due to [\`${breakChange.sha.substring(0, 7)}\`](${breakChange.url}) - ${subjectVar}:\n\n${body}\n`)
changesFile.push(`- due to [\`${breakChange.sha.substring(0, 7)}\`](${breakChange.url}) - ${subjectFile.output}:\n\n${body}\n`)
changesVar.push(`- due to [\`${breakChange.sha.substring(0, 7)}\`](${breakChange.url}) - ${subjectVar.output}:\n\n${body}\n`)
}
idx++
}
Expand All @@ -28319,6 +28336,9 @@ async function main () {
}
changesFile.push(useGitmojis ? `### ${type.icon} ${type.header}` : `### ${type.header}`)
changesVar.push(useGitmojis ? `### ${type.icon} ${type.header}` : `### ${type.header}`)

const relIssuePrefix = type.relIssuePrefix || 'addresses'

for (const commit of matchingCommits) {
const scope = commit.scope ? `**${commit.scope}**: ` : ''
const subjectFile = buildSubject({
Expand All @@ -28337,8 +28357,41 @@ async function main () {
owner,
repo
})
changesFile.push(`- [\`${commit.sha.substring(0, 7)}\`](${commit.url}) - ${scope}${subjectFile}`)
changesVar.push(`- [\`${commit.sha.substring(0, 7)}\`](${commit.url}) - ${scope}${subjectVar}`)
changesFile.push(`- [\`${commit.sha.substring(0, 7)}\`](${commit.url}) - ${scope}${subjectFile.output}`)
changesVar.push(`- [\`${commit.sha.substring(0, 7)}\`](${commit.url}) - ${scope}${subjectVar.output}`)

if (includeRefIssues && subjectVar.prs.length > 0) {
for (const prId of subjectVar.prs) {
core.info(`Querying related issues for PR ${prId}...`)
await setTimeout(500) // Make sure we don't go over GitHub API rate limits
const issuesRaw = await gh.graphql(`
query relIssues ($owner: String!, $repo: String!, $prId: Int!) {
repository (owner: $owner, name: $repo) {
pullRequest(number: $prId) {
closingIssuesReferences(first: 50) {
nodes {
number
author {
login
url
}
}
}
}
}
}
`, {
owner,
repo,
prId: parseInt(prId)
})
const relIssues = _.get(issuesRaw, 'repository.pullRequest.closingIssuesReferences.nodes')
for (const relIssue of relIssues) {
changesFile.push(` - :arrow_lower_right: *${relIssuePrefix} issue [#${relIssue.number}](${relIssue.url}) opened by [@${relIssue.author.login}](${relIssue.author.url})*`)
changesVar.push(` - :arrow_lower_right: *${relIssuePrefix} issue #${relIssue.number} opened by @${relIssue.author.login}*`)
}
}
}
}
idx++
}
Expand Down
71 changes: 58 additions & 13 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ const core = require('@actions/core')
const _ = require('lodash')
const cc = require('@conventional-commits/parser')
const fs = require('fs').promises
const { setTimeout } = require('timers/promises')

const types = [
{ types: ['feat', 'feature'], header: 'New Features', icon: ':sparkles:' },
{ types: ['fix', 'bugfix'], header: 'Bug Fixes', icon: ':bug:' },
{ types: ['fix', 'bugfix'], header: 'Bug Fixes', icon: ':bug:', relIssuePrefix: 'fixes' },
{ types: ['perf'], header: 'Performance Improvements', icon: ':zap:' },
{ types: ['refactor'], header: 'Refactors', icon: ':recycle:' },
{ types: ['test', 'tests'], header: 'Tests', icon: ':white_check_mark:' },
Expand All @@ -22,31 +23,37 @@ const rePrEnding = /\(#([0-9]+)\)$/

function buildSubject ({ writeToFile, subject, author, authorUrl, owner, repo }) {
const hasPR = rePrEnding.test(subject)
let final = subject
const prs = []
let output = subject
if (writeToFile) {
if (hasPR) {
const prMatch = subject.match(rePrEnding)
const msgOnly = subject.slice(0, prMatch[0].length * -1)
final = msgOnly.replace(rePrId, (m, prId) => {
output = msgOnly.replace(rePrId, (m, prId) => {
prs.push(prId)
return `[#${prId}](https://github.com/${owner}/${repo}/pull/${prId})`
})
final += `*(PR [#${prMatch[1]}](https://github.com/${owner}/${repo}/pull/${prMatch[1]}) by [@${author}](${authorUrl}))*`
output += `*(PR [#${prMatch[1]}](https://github.com/${owner}/${repo}/pull/${prMatch[1]}) by [@${author}](${authorUrl}))*`
} else {
final = subject.replace(rePrId, (m, prId) => {
output = subject.replace(rePrId, (m, prId) => {
return `[#${prId}](https://github.com/${owner}/${repo}/pull/${prId})`
})
final += ` *(commit by [@${author}](${authorUrl}))*`
output += ` *(commit by [@${author}](${authorUrl}))*`
}
} else {
if (hasPR) {
final = subject.replace(rePrEnding, (m, prId) => {
output = subject.replace(rePrEnding, (m, prId) => {
prs.push(prId)
return `*(PR #${prId} by @${author})*`
})
} else {
final = `${subject} *(commit by @${author})*`
output = `${subject} *(commit by @${author})*`
}
}
return final
return {
output,
prs
}
}

async function main () {
Expand All @@ -56,6 +63,7 @@ async function main () {
const toTag = core.getInput('toTag')
const excludeTypes = (core.getInput('excludeTypes') || '').split(',').map(t => t.trim())
const writeToFile = core.getBooleanInput('writeToFile')
const includeRefIssues = core.getBooleanInput('includeRefIssues')
const useGitmojis = core.getBooleanInput('useGitmojis')
const includeInvalidCommits = core.getBooleanInput('includeInvalidCommits')
const gh = github.getOctokit(token)
Expand Down Expand Up @@ -185,6 +193,7 @@ async function main () {
author: commit.author.login,
authorUrl: commit.author.html_url
})
core.info(`[OK] Commit ${commit.sha} with invalid type, falling back to other - ${commit.commit.message}`)
} else {
core.info(`[INVALID] Skipping commit ${commit.sha} as it doesn't follow conventional commit format.`)
}
Expand Down Expand Up @@ -222,8 +231,8 @@ async function main () {
owner,
repo
})
changesFile.push(`- due to [\`${breakChange.sha.substring(0, 7)}\`](${breakChange.url}) - ${subjectFile}:\n\n${body}\n`)
changesVar.push(`- due to [\`${breakChange.sha.substring(0, 7)}\`](${breakChange.url}) - ${subjectVar}:\n\n${body}\n`)
changesFile.push(`- due to [\`${breakChange.sha.substring(0, 7)}\`](${breakChange.url}) - ${subjectFile.output}:\n\n${body}\n`)
changesVar.push(`- due to [\`${breakChange.sha.substring(0, 7)}\`](${breakChange.url}) - ${subjectVar.output}:\n\n${body}\n`)
}
idx++
}
Expand All @@ -242,6 +251,9 @@ async function main () {
}
changesFile.push(useGitmojis ? `### ${type.icon} ${type.header}` : `### ${type.header}`)
changesVar.push(useGitmojis ? `### ${type.icon} ${type.header}` : `### ${type.header}`)

const relIssuePrefix = type.relIssuePrefix || 'addresses'

for (const commit of matchingCommits) {
const scope = commit.scope ? `**${commit.scope}**: ` : ''
const subjectFile = buildSubject({
Expand All @@ -260,8 +272,41 @@ async function main () {
owner,
repo
})
changesFile.push(`- [\`${commit.sha.substring(0, 7)}\`](${commit.url}) - ${scope}${subjectFile}`)
changesVar.push(`- [\`${commit.sha.substring(0, 7)}\`](${commit.url}) - ${scope}${subjectVar}`)
changesFile.push(`- [\`${commit.sha.substring(0, 7)}\`](${commit.url}) - ${scope}${subjectFile.output}`)
changesVar.push(`- [\`${commit.sha.substring(0, 7)}\`](${commit.url}) - ${scope}${subjectVar.output}`)

if (includeRefIssues && subjectVar.prs.length > 0) {
for (const prId of subjectVar.prs) {
core.info(`Querying related issues for PR ${prId}...`)
await setTimeout(500) // Make sure we don't go over GitHub API rate limits
const issuesRaw = await gh.graphql(`
query relIssues ($owner: String!, $repo: String!, $prId: Int!) {
repository (owner: $owner, name: $repo) {
pullRequest(number: $prId) {
closingIssuesReferences(first: 50) {
nodes {
number
author {
login
url
}
}
}
}
}
}
`, {
owner,
repo,
prId: parseInt(prId)
})
const relIssues = _.get(issuesRaw, 'repository.pullRequest.closingIssuesReferences.nodes')
for (const relIssue of relIssues) {
changesFile.push(` - :arrow_lower_right: *${relIssuePrefix} issue [#${relIssue.number}](${relIssue.url}) opened by [@${relIssue.author.login}](${relIssue.author.url})*`)
changesVar.push(` - :arrow_lower_right: *${relIssuePrefix} issue #${relIssue.number} opened by @${relIssue.author.login}*`)
}
}
}
}
idx++
}
Expand Down
15 changes: 15 additions & 0 deletions index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const process = require('process')

// shows how the runner will run a javascript action with env / stdout protocol
test('test run', () => {
process.env['GITHUB_REPOSITORY'] = '__TEST_VALUE__'
process.env['INPUT_TOKEN'] = '__TEST_VALUE__'
process.env['INPUT_FROMTAG'] = '__TEST_VALUE__'
process.env['INPUT_TOTAG'] = '__TEST_VALUE__'
process.env['INPUT_WRITETOFILE'] = 'false'
process.env['INPUT_INCLUDEREFISSUES'] = 'true'
process.env['INPUT_USEGITMOJIS'] = 'true'
process.env['INPUT_INCLUDEINVALIDCOMMITS'] = 'false'

require('./index.js')
})
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"description": "GitHub Action to generate changelog from conventional commits",
"main": "dist/index.js",
"scripts": {
"build": "ncc build index.js -o dist"
"build": "ncc build index.js -o dist",
"test": "jest"
},
"repository": {
"type": "git",
Expand All @@ -25,6 +26,7 @@
"@actions/core": "1.10.0",
"@actions/github": "5.1.1",
"@conventional-commits/parser": "0.4.1",
"jest": "29.3.1",
"lodash": "4.17.21"
},
"devDependencies": {
Expand Down
Loading

0 comments on commit f64e045

Please sign in to comment.