1672c95de3
* fix: use PR 'Notes' comment in release notes * fix: follow links in roller-bot PRs * refactor: better reference point version selection * if we're a stable release, use the current brnach's previous stable * if we're a beta release, use the current branch's previous beta * if no match found, use the newest stable that precedes this branch * refactor: dedup the caching functions' code * refactor: partially rewrite release note generator * parse release notes comments from PRs * do not display no-notes PRs * handle roller-bot commits by following cross-repo commits/PRs * minor tweaks to note rendering, e.g. capitalization * fix: fix lint:js script typo * fix: copy originalPr value to rollerbot PR chains * fix: handle more cases in release notes generator * handle force-pushes where no PR * better type guessing on pre-semantic commits * fix: handle more edge cases in the note generator * better removal of commits that landed before the reference point * ensure '<!-- One-line Change Summary Here-->' is removed from notes * handle more legacy commit body notes e.g. "Chore(docs)" * check for fix markdown in PR body e.g. a link to the issue page * chore: tweak code comments * refactor: easier note generator command-line args * refactor: group related notes together * feat: query commits locally for gyp and gn deps * chore: slightly better filtering of old commits * feat: omit submodule commits for .0.0 releases More specifically, only include them if generating release notes relative to another release on the same branch. Before that first release, there's just too much churn. * refactor: make release-notes usable as a module Calling it from the command line and from require()() now do pretty much the same thing. * refactor: passing command-line args means use HEAD * chore: plug in the release note generator * feat: support multiline 'Notes:' messages. xref: https://github.com/electron/trop/pull/56 xref: https://github.com/electron/clerk/pull/16 * remove accidental change in package.json * simplify an overcomplicated require() call * Don't use PascalCase on releaseNotesGenerator() * Remove code duplication in release notes warnings * remove commented-out code. * don't use single-character variable names. For example, use 'tag' instead of 't'. The latter was being used for map/filter arrow function args. * Look for 'backport' rather than 'ackport'. * Wrap all block statements in curly braces. * fix tyop * fix oops * Check semver validity before calling semver.sort()
162 lines
4.7 KiB
JavaScript
Executable file
162 lines
4.7 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
const { GitProcess } = require('dugite')
|
|
const path = require('path')
|
|
const semver = require('semver')
|
|
|
|
const notesGenerator = require('./notes.js')
|
|
|
|
const gitDir = path.resolve(__dirname, '..', '..')
|
|
|
|
const semverify = version => version.replace(/^origin\//, '').replace('x', '0').replace(/-/g, '.')
|
|
|
|
const runGit = async (args) => {
|
|
const response = await GitProcess.exec(args, gitDir)
|
|
if (response.exitCode !== 0) {
|
|
throw new Error(response.stderr.trim())
|
|
}
|
|
return response.stdout.trim()
|
|
}
|
|
|
|
const tagIsSupported = tag => tag && !tag.includes('nightly') && !tag.includes('unsupported')
|
|
const tagIsBeta = tag => tag.includes('beta')
|
|
const tagIsStable = tag => tagIsSupported(tag) && !tagIsBeta(tag)
|
|
|
|
const getTagsOf = async (point) => {
|
|
return (await runGit(['tag', '--merged', point]))
|
|
.split('\n')
|
|
.map(tag => tag.trim())
|
|
.filter(tag => semver.valid(tag))
|
|
.sort(semver.compare)
|
|
}
|
|
|
|
const getTagsOnBranch = async (point) => {
|
|
const masterTags = await getTagsOf('master')
|
|
if (point === 'master') {
|
|
return masterTags
|
|
}
|
|
|
|
const masterTagsSet = new Set(masterTags)
|
|
return (await getTagsOf(point)).filter(tag => !masterTagsSet.has(tag))
|
|
}
|
|
|
|
const getBranchOf = async (point) => {
|
|
const branches = (await runGit(['branch', '-a', '--contains', point]))
|
|
.split('\n')
|
|
.map(branch => branch.trim())
|
|
.filter(branch => !!branch)
|
|
const current = branches.find(branch => branch.startsWith('* '))
|
|
return current ? current.slice(2) : branches.shift()
|
|
}
|
|
|
|
const getAllBranches = async () => {
|
|
return (await runGit(['branch', '--remote']))
|
|
.split('\n')
|
|
.map(branch => branch.trim())
|
|
.filter(branch => !!branch)
|
|
.filter(branch => branch !== 'origin/HEAD -> origin/master')
|
|
.sort()
|
|
}
|
|
|
|
const getStabilizationBranches = async () => {
|
|
return (await getAllBranches())
|
|
.filter(branch => /^origin\/\d+-\d+-x$/.test(branch))
|
|
}
|
|
|
|
const getPreviousStabilizationBranch = async (current) => {
|
|
const stabilizationBranches = (await getStabilizationBranches())
|
|
.filter(branch => branch !== current && branch !== `origin/${current}`)
|
|
|
|
if (!semver.valid(current)) {
|
|
// since we don't seem to be on a stabilization branch right now,
|
|
// pick a placeholder name that will yield the newest branch
|
|
// as a comparison point.
|
|
current = 'v999.999.999'
|
|
}
|
|
|
|
let newestMatch = null
|
|
for (const branch of stabilizationBranches) {
|
|
if (semver.gte(semverify(branch), semverify(current))) {
|
|
continue
|
|
}
|
|
if (newestMatch && semver.lte(semverify(branch), semverify(newestMatch))) {
|
|
continue
|
|
}
|
|
newestMatch = branch
|
|
}
|
|
return newestMatch
|
|
}
|
|
|
|
const getPreviousPoint = async (point) => {
|
|
const currentBranch = await getBranchOf(point)
|
|
const currentTag = (await getTagsOf(point)).filter(tag => tagIsSupported(tag)).pop()
|
|
const currentIsStable = tagIsStable(currentTag)
|
|
|
|
try {
|
|
// First see if there's an earlier tag on the same branch
|
|
// that can serve as a reference point.
|
|
let tags = (await getTagsOnBranch(`${point}^`)).filter(tag => tagIsSupported(tag))
|
|
if (currentIsStable) {
|
|
tags = tags.filter(tag => tagIsStable(tag))
|
|
}
|
|
if (tags.length) {
|
|
return tags.pop()
|
|
}
|
|
} catch (error) {
|
|
console.log('error', error)
|
|
}
|
|
|
|
// Otherwise, use the newest stable release that preceeds this branch.
|
|
// To reach that you may have to walk past >1 branch, e.g. to get past
|
|
// 2-1-x which never had a stable release.
|
|
let branch = currentBranch
|
|
while (branch) {
|
|
const prevBranch = await getPreviousStabilizationBranch(branch)
|
|
const tags = (await getTagsOnBranch(prevBranch)).filter(tag => tagIsStable(tag))
|
|
if (tags.length) {
|
|
return tags.pop()
|
|
}
|
|
branch = prevBranch
|
|
}
|
|
}
|
|
|
|
async function getReleaseNotes (range) {
|
|
const rangeList = range.split('..') || ['HEAD']
|
|
const to = rangeList.pop()
|
|
const from = rangeList.pop() || (await getPreviousPoint(to))
|
|
console.log(`Generating release notes between ${from} and ${to}`)
|
|
|
|
const notes = await notesGenerator.get(from, to)
|
|
const ret = {
|
|
text: notesGenerator.render(notes)
|
|
}
|
|
|
|
if (notes.unknown.length) {
|
|
ret.warning = `You have ${notes.unknown.length} unknown release notes. Please fix them before releasing.`
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
async function main () {
|
|
if (process.argv.length > 3) {
|
|
console.log('Use: script/release-notes/index.js [tag | tag1..tag2]')
|
|
return 1
|
|
}
|
|
|
|
const range = process.argv[2] || 'HEAD'
|
|
const notes = await getReleaseNotes(range)
|
|
console.log(notes.text)
|
|
if (notes.warning) {
|
|
throw new Error(notes.warning)
|
|
}
|
|
}
|
|
|
|
if (process.mainModule === module) {
|
|
main().catch((err) => {
|
|
console.error('Error Occurred:', err)
|
|
process.exit(1)
|
|
})
|
|
}
|
|
|
|
module.exports = getReleaseNotes
|