chore: update changelog linting (#25809)

This commit is contained in:
Emily Rohrbough
2023-02-14 12:35:01 -06:00
committed by GitHub
parent d1ac427fc3
commit 06187121b1
5 changed files with 96 additions and 33 deletions
@@ -70,7 +70,7 @@ const getReleaseData = async (latestReleaseInfo) => {
return
}
const { data: pullRequest } = await octokit.request('GET /repos/{owner}/{repo}/pull/{pull_number}', {
const { data: pullRequest } = await octokit.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', {
owner: 'cypress-io',
repo: 'cypress',
pull_number: references[0].issue,
@@ -7,7 +7,7 @@ const getLinkedIssues = (body = '') => {
// remove markdown comments
body.replace(/(<!--.*?-->)|(<!--[\S\s]+?-->)|(<!--[\S\s]*?$)/g, '')
const references = body.match(/(close[sd]?|fix(es|ed)?|resolve[s|d]?) (cypress-io\/cypress)?#\d+/gi)
const references = body.match(/(close[sd]?|fix(es|ed)?|resolve[s|d]?) ((cypress-io\/cypress)?#\d+|https\:\/\/github.com\/cypress-io\/cypress\/issues\/\d+)/gi)
if (!references) {
return []
+36 -16
View File
@@ -19,26 +19,40 @@ function _getResolvedMessage (semanticType, prNumber, associatedIssues = []) {
return `[#${issueNumber}](https://github.com/cypress-io/cypress/issues/${issueNumber})`
})
// one issue: [#num]
// two issues: [#num] and [#num]
// two+ issues: [#num], [#num] and [#num]
const linkMessage = [links.slice(0, -1).join(', '), links.slice(-1)[0]].join(links.length < 2 ? '' : ' and ')
return `${issueMessage} ${linkMessage}.`
return {
message: issueMessage,
links,
}
}
const prMessage = userFacingChanges[semanticType].message.onlyPR
return `${prMessage} [#${prNumber}](https://github.com/cypress-io/cypress/pull/${prNumber}).`
return {
message: prMessage,
links: [`[#${prNumber}](https://github.com/cypress-io/cypress/pull/${prNumber})`],
}
}
function _linksText (links) {
// one issue: [#num]
// two issues: [#num] and [#num]
// two+ issues: [#num], [#num] and [#num]
const linkMessage = [links.slice(0, -1).join(', '), links.slice(-1)[0]].join(links.length < 2 ? '' : ' and ')
return linkMessage
}
function _printResolveExample ({ message, links }) {
return `${message} ${_linksText(links)}.`
}
/**
* Helper to format an example of what the changelog entry might look like for a given commit.
*/
function _printChangeLogExample (semanticType, prNumber, associatedIssues = []) {
const resolveMessage = _getResolvedMessage(semanticType, prNumber, associatedIssues)
const resolveData = _getResolvedMessage(semanticType, prNumber, associatedIssues)
return `${userFacingChanges[semanticType].section}\n\n - <Insert change details>. ${resolveMessage}`
return `${userFacingChanges[semanticType].section}\n\n - <Insert change details>. ${_printResolveExample(resolveData)}`
}
/**
@@ -51,21 +65,27 @@ function _validateEntry (changelog, { commitMessage, prNumber, semanticType, ass
}
const expectedSection = userFacingChanges[semanticType].section
let missingExpectedSection = false
let missingExpectedSection = !changelog[expectedSection]
let sectionEntryFoundIn = ''
const resolveMessage = _getResolvedMessage(semanticType, prNumber, associatedIssues)
const resolveData = _getResolvedMessage(semanticType, prNumber, associatedIssues)
const hasMatchingEntry = Object.entries(userFacingChanges).some(([type, { section }]) => {
const sectionDetails = changelog[section]
if (!sectionDetails) {
missingExpectedSection = semanticType === type
return false
}
const hasMatchingEntry = sectionDetails.some((detail) => detail.includes(resolveMessage))
const hasMatchingEntry = sectionDetails.some((detail) => {
const index = detail.lastIndexOf(resolveData.message)
if (index === undefined) return false // missing message
const resolveString = detail.substring(index)
return resolveData.links.every((link) => resolveString.includes(link))
})
if (hasMatchingEntry) {
sectionEntryFoundIn = section
@@ -80,10 +100,10 @@ function _validateEntry (changelog, { commitMessage, prNumber, semanticType, ass
if (!hasMatchingEntry) {
if (associatedIssues && associatedIssues.length) {
return `The changelog entry does not include the linked issues that this pull request resolves. Please update your entry for '${commitMessage}' to include:\n\n${resolveMessage}`
return `The changelog entry does not include the linked issues that this pull request resolves. Please update your entry for '${commitMessage}' to include:\n\n${_printResolveExample(resolveData)}`
}
return `The changelog entry does not include the pull request link. Please update your entry for '${commitMessage}' to include:\n\n${resolveMessage}`
return `The changelog entry does not include the pull request link. Please update your entry for '${commitMessage}' to include:\n\n${_printResolveExample(resolveData)}`
}
if (hasMatchingEntry && sectionEntryFoundIn !== expectedSection) {
@@ -11,25 +11,36 @@ use(sinonChai)
describe('semantic-pull-request/validate-changelog', () => {
context('_getResolvedMessage', () => {
it('returned pr link', () => {
const message = _getResolvedMessage('feat', 52, [])
const { message, links } = _getResolvedMessage('feat', 52, [])
expect(message).to.contain('Addressed in [#52](https://github.com/cypress-io/cypress/pull/52).')
expect(message).to.eq('Addressed in')
expect(links).to.have.length(1)
expect(links[0]).to.eq('[#52](https://github.com/cypress-io/cypress/pull/52)')
})
it('returns linked issue', () => {
const message = _getResolvedMessage('feat', 52, [39])
const { message, links } = _getResolvedMessage('feat', 52, [39])
expect(message).to.contain('Addresses [#39](https://github.com/cypress-io/cypress/issues/39).')
expect(message).to.eq('Addresses')
expect(links).to.have.length(1)
expect(links[0]).to.eq('[#39](https://github.com/cypress-io/cypress/issues/39)')
})
it('returns all linked issues', () => {
let message = _getResolvedMessage('feat', 52, [39, 20])
let { message, links } = _getResolvedMessage('feat', 52, [39, 20])
expect(message).to.contain('Addresses [#20](https://github.com/cypress-io/cypress/issues/20) and [#39](https://github.com/cypress-io/cypress/issues/39).')
expect(message).to.eq('Addresses')
expect(links).to.have.length(2)
expect(links[0]).to.eq('[#20](https://github.com/cypress-io/cypress/issues/20)')
expect(links[1]).to.eq('[#39](https://github.com/cypress-io/cypress/issues/39)')
message = _getResolvedMessage('feat', 52, [39, 20, 30])
const resolveData = _getResolvedMessage('feat', 52, [39, 20, 30])
expect(message).to.contain('Addresses [#20](https://github.com/cypress-io/cypress/issues/20), [#30](https://github.com/cypress-io/cypress/issues/30) and [#39](https://github.com/cypress-io/cypress/issues/39).')
expect(resolveData.message).to.eq('Addresses')
expect(resolveData.links).to.have.length(3)
expect(resolveData.links[0]).to.eq('[#20](https://github.com/cypress-io/cypress/issues/20)')
expect(resolveData.links[1]).to.eq('[#30](https://github.com/cypress-io/cypress/issues/30)')
expect(resolveData.links[2]).to.eq('[#39](https://github.com/cypress-io/cypress/issues/39)')
})
})
@@ -140,6 +151,37 @@ _Released 01/17/2033 (PENDING)_
expect(console.log).to.be.calledWith('It appears at a high-level your changelog entry is correct! The remaining validation is left to the pull request reviewers.')
})
it('verifies changelog with shared entry', async () => {
const changedFiles = [
'packages/driver/lib/index.js',
'cli/CHANGELOG.md',
]
fs.readFileSync.returns(`
## 120.2.0
_Released 01/17/2033 (PENDING)_
**Misc:**
- Addresses [#77](https://github.com/cypress-io/cypress/issues/77) and [#88](https://github.com/cypress-io/cypress/issues/88).`)
await validateChangelog({
changedFiles,
commits: [{
prNumber: 74,
semanticType: 'misc',
associatedIssues: ['77'],
}, {
prNumber: 75,
semanticType: 'misc',
associatedIssues: ['88'],
}],
})
expect(console.log).to.be.calledWith('It appears at a high-level your changelog entry is correct! The remaining validation is left to the pull request reviewers.')
})
describe('ignores validation', () => {
it('when commit has cli or binary file changes that are not user facing', async () => {
const changedFiles = [
@@ -1,13 +1,13 @@
const { expect, use } = require('chai')
const sinonChai = require('sinon-chai')
const { getIssueNumbers } = require('../../semantic-commits/get-linked-issues')
const { getLinkedIssues } = require('../../semantic-commits/get-linked-issues')
use(sinonChai)
describe('semantic-commits/get-linked-issues', () => {
it('returns single issue link', () => {
const issues = getIssueNumbers(`
const issues = getLinkedIssues(`
<!-- comment ->
- Closes #23
summary of changes see in #458
@@ -17,7 +17,7 @@ describe('semantic-commits/get-linked-issues', () => {
})
it('returns issue links for all linking keywords', () => {
const issues = getIssueNumbers(`
const issues = getLinkedIssues(`
<!-- comment ->
- Close #23
- Closes #24
@@ -29,9 +29,10 @@ describe('semantic-commits/get-linked-issues', () => {
- Resolves #46
- addresses #77 <-- not a valid linking word
summary of changes
- Closes https://github.com/cypress-io/cypress/issues/50
`)
expect(issues).to.deep.eq(['23', '24', '25', '33', '34', '35', '44', '45', '46'])
expect(issues).to.deep.eq(['23', '24', '25', '33', '34', '35', '44', '45', '46', '50'])
})
it('only counts an issue once', () => {
@@ -39,7 +40,7 @@ describe('semantic-commits/get-linked-issues', () => {
- closes #44
- closes #44
`
const issues = getIssueNumbers(body)
const issues = getLinkedIssues(body)
expect(issues).to.deep.eq(['44'])
})
@@ -49,13 +50,13 @@ describe('semantic-commits/get-linked-issues', () => {
fixes cypress-io/cypress#123 which is a local issue
and this is issue in another repo foo/bar#101
`
const issues = getIssueNumbers(body)
const issues = getLinkedIssues(body)
expect(issues).to.deep.eq(['123'])
})
it('returns empty list when no issues found', () => {
const issues = getIssueNumbers(`
const issues = getLinkedIssues(`
<!-- comment ->
summary of changes
`)