mirror of
https://github.com/cypress-io/cypress.git
synced 2026-04-26 08:59:26 -05:00
chore: update changelog linting (#25809)
This commit is contained in:
@@ -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 []
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
+8
-7
@@ -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
|
||||
`)
|
||||
Reference in New Issue
Block a user