mirror of
https://github.com/cypress-io/cypress.git
synced 2026-01-01 12:10:07 -06:00
222 lines
6.7 KiB
JavaScript
222 lines
6.7 KiB
JavaScript
const Bluebird = require('bluebird')
|
|
const childProcess = require('child_process')
|
|
const _ = require('lodash')
|
|
const { Octokit } = require('@octokit/core')
|
|
const debugLib = require('debug')
|
|
|
|
const { getCurrentReleaseData } = require('./get-current-release-data')
|
|
const { getNextVersionForBinary } = require('../get-next-version')
|
|
const { getLinkedIssues } = require('./get-linked-issues')
|
|
const debug = debugLib('scripts:semantic-commits:get-binary-release-data')
|
|
|
|
const ensureAuth = () => {
|
|
if (!process.env.GH_TOKEN) {
|
|
throw new Error('The GH_TOKEN env is not set.')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the list of file names that have been added, deleted or changed since the git
|
|
* sha associated with the latest tag published on npm.
|
|
*
|
|
* @param {object} latestReleaseInfo - data of the latest tag published on npm
|
|
* @param {string} latestReleaseInfo.version - version of Cypress
|
|
* @param {string} latestReleaseInfo.commitDate - data of release
|
|
* @param {string} latestReleaseInfo.buildSha - git commit associated with published content
|
|
*/
|
|
const getChangedFilesSinceLastRelease = (latestReleaseInfo) => {
|
|
const stdout = childProcess.execSync(`git diff ${latestReleaseInfo.buildSha}.. --name-only`, { encoding: 'utf8' })
|
|
|
|
if (!stdout) {
|
|
console.log('no files changes since last release')
|
|
|
|
return []
|
|
}
|
|
|
|
return stdout.split('\n')
|
|
}
|
|
|
|
// returns number of milliseconds to wait for retry
|
|
const getRetryAfter = (headers) => {
|
|
// retry-after header is number of seconds to wait
|
|
if (headers['retry-after'] && headers['retry-after'] !== '0') {
|
|
return parseInt(headers['retry-after'], 10) * 1000
|
|
}
|
|
|
|
// x-ratelimit-reset header is the time in seconds since epoch after
|
|
// which to retry
|
|
if (headers['x-ratelimit-reset']) {
|
|
const epochSeconds = parseInt(headers['x-ratelimit-reset'], 10)
|
|
|
|
// turn it into milliseconds to wait and pad it by a second for good measure
|
|
return (epochSeconds - (Date.now() / 1000)) * 1000 + 1000
|
|
}
|
|
|
|
// otherwise, just wait a minute
|
|
return 6000
|
|
}
|
|
|
|
// https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#secondary-rate-limits
|
|
const parseRateLimit = (err) => {
|
|
if (err.status === 403 && err.response?.data?.message.includes('secondary rate limit')) {
|
|
const retryAfter = getRetryAfter(err.response?.headers)
|
|
|
|
return {
|
|
rateLimitHit: true,
|
|
retryAfter,
|
|
}
|
|
}
|
|
|
|
return {
|
|
rateLimitHit: false,
|
|
}
|
|
}
|
|
|
|
const fetchPullRequest = async (octokit, pullNumber) => {
|
|
try {
|
|
const { data: pullRequest } = await octokit.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', {
|
|
owner: 'cypress-io',
|
|
repo: 'cypress',
|
|
pull_number: pullNumber,
|
|
})
|
|
|
|
return pullRequest
|
|
} catch (err) {
|
|
const { rateLimitHit, retryAfter } = parseRateLimit(err)
|
|
|
|
if (rateLimitHit) {
|
|
console.log(`Rate limit hit - Retry fetching PR #${pullNumber} after ${retryAfter}ms`)
|
|
|
|
return Bluebird.delay(retryAfter).then(() => fetchPullRequest(octokit, pullNumber))
|
|
}
|
|
|
|
throw err
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the next release version given the semantic commits in the git history. Then using the commit history,
|
|
* determine which files have changed, list of PRs merged and issues resolved since the latest tag was
|
|
* published on npm. It also collects the list of commit data including the semantic type, PR and associated
|
|
* issues.
|
|
*
|
|
* @param {object} latestReleaseInfo - data of the latest tag published on npm
|
|
* @param {string} latestReleaseInfo.version - version of Cypress
|
|
* @param {string} latestReleaseInfo.commitDate - data of release
|
|
* @param {string} latestReleaseInfo.buildSha - git commit associated with published content
|
|
*/
|
|
const getReleaseData = async (latestReleaseInfo) => {
|
|
ensureAuth()
|
|
const octokit = new Octokit({ auth: process.env.GH_TOKEN })
|
|
|
|
let {
|
|
nextVersion,
|
|
commits: semanticCommits,
|
|
} = await getNextVersionForBinary()
|
|
|
|
semanticCommits = _.uniqBy(semanticCommits, (commit) => commit.header)
|
|
|
|
const changedFiles = await getChangedFilesSinceLastRelease(latestReleaseInfo)
|
|
|
|
const issuesInRelease = []
|
|
const prsInRelease = []
|
|
const commits = []
|
|
|
|
await Bluebird.each(semanticCommits, (async (semanticResult) => {
|
|
if (!semanticResult) return
|
|
|
|
const { type: semanticType, references } = semanticResult
|
|
|
|
if (!references.length) {
|
|
console.log('Commit does not have an associated pull request number...')
|
|
|
|
return
|
|
}
|
|
|
|
const refs = references.filter((r) => !r.raw.includes('revert #'))
|
|
|
|
if (!refs.length) {
|
|
console.log('Commit does not have an associated pull request number...')
|
|
|
|
return
|
|
}
|
|
|
|
for (const [idx, ref] of refs.entries()) {
|
|
let pullRequest
|
|
|
|
try {
|
|
pullRequest = await fetchPullRequest(octokit, ref.issue)
|
|
debug(`Pull request #${ref.issue} found!`)
|
|
} catch (err) {
|
|
debug(`Error while fetching PR #${ ref.issue}:`, err)
|
|
// If we have tried to fetch all other references and all of them failed,
|
|
// print the failure of the last reference and exit
|
|
if (idx === refs.length) {
|
|
debug(`all references exhausted and no PR could be found!`)
|
|
console.log(`Error while fetching PR #${ ref.issue}:`, err)
|
|
throw err
|
|
} else {
|
|
// otherwise, we still might be able to link a PR to the commit
|
|
debug(`Other references need to be tried. Continuing to see if we can find a corresponding pull request.`)
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
const associatedIssues = pullRequest.body ? getLinkedIssues(pullRequest.body) : []
|
|
|
|
commits.push({
|
|
commitMessage: semanticResult.header,
|
|
semanticType,
|
|
prNumber: ref.issue,
|
|
associatedIssues,
|
|
})
|
|
|
|
prsInRelease.push(`https://github.com/cypress-io/cypress/pull/${ref.issue}`)
|
|
|
|
associatedIssues.forEach((issueNumber) => {
|
|
issuesInRelease.push(`https://github.com/cypress-io/cypress/issues/${issueNumber}`)
|
|
})
|
|
|
|
// since we found our pull request, we don't need to check the rest of the references
|
|
break
|
|
}
|
|
}))
|
|
|
|
console.log('Next release version is', nextVersion)
|
|
|
|
console.log(`${prsInRelease.length} pull requests have merged since ${latestReleaseInfo.version} was released.`)
|
|
|
|
prsInRelease.forEach((link) => {
|
|
console.log(' -', link)
|
|
})
|
|
|
|
console.log(`${issuesInRelease.length} issues addressed since ${latestReleaseInfo.version} was released.`)
|
|
|
|
issuesInRelease.forEach((link) => {
|
|
console.log(' -', link)
|
|
})
|
|
|
|
return {
|
|
nextVersion,
|
|
changedFiles,
|
|
commits,
|
|
issuesInRelease,
|
|
prsInRelease,
|
|
}
|
|
}
|
|
|
|
if (require.main !== module) {
|
|
module.exports = {
|
|
getReleaseData,
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
(async () => {
|
|
const latestReleaseInfo = await getCurrentReleaseData()
|
|
|
|
await getReleaseData(latestReleaseInfo)
|
|
})()
|