Merge pull request #13359 from electron/release-notes-generator
build: New release notes generator
This commit is contained in:
commit
f1cc40e3bf
5 changed files with 2600 additions and 1913 deletions
3838
package-lock.json
generated
3838
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -15,14 +15,17 @@
|
||||||
"electron-docs-linter": "^2.3.4",
|
"electron-docs-linter": "^2.3.4",
|
||||||
"electron-typescript-definitions": "^1.3.5",
|
"electron-typescript-definitions": "^1.3.5",
|
||||||
"github": "^9.2.0",
|
"github": "^9.2.0",
|
||||||
|
"html-entities": "^1.2.1",
|
||||||
"husky": "^0.14.3",
|
"husky": "^0.14.3",
|
||||||
"lint": "^1.1.2",
|
"lint": "^1.1.2",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
|
"node-fetch": "^2.1.2",
|
||||||
"nugget": "^2.0.1",
|
"nugget": "^2.0.1",
|
||||||
"octicons": "^7.3.0",
|
"octicons": "^7.3.0",
|
||||||
"remark-cli": "^4.0.0",
|
"remark-cli": "^4.0.0",
|
||||||
"remark-preset-lint-markdown-style-guide": "^2.1.1",
|
"remark-preset-lint-markdown-style-guide": "^2.1.1",
|
||||||
"request": "^2.68.0",
|
"request": "^2.68.0",
|
||||||
|
"semver": "^5.5.0",
|
||||||
"serve": "^6.5.3",
|
"serve": "^6.5.3",
|
||||||
"standard": "^10.0.0",
|
"standard": "^10.0.0",
|
||||||
"standard-markdown": "^4.0.0",
|
"standard-markdown": "^4.0.0",
|
||||||
|
|
1
script/release-notes/.gitignore
vendored
Normal file
1
script/release-notes/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.cache
|
478
script/release-notes/index.js
Normal file
478
script/release-notes/index.js
Normal file
|
@ -0,0 +1,478 @@
|
||||||
|
const { GitProcess } = require('dugite')
|
||||||
|
const Entities = require('html-entities').AllHtmlEntities
|
||||||
|
const fetch = require('node-fetch')
|
||||||
|
const fs = require('fs')
|
||||||
|
const GitHub = require('github')
|
||||||
|
const path = require('path')
|
||||||
|
const semver = require('semver')
|
||||||
|
|
||||||
|
const CACHE_DIR = path.resolve(__dirname, '.cache')
|
||||||
|
// Fill this with tags to ignore if you are generating release notes for older
|
||||||
|
// versions
|
||||||
|
//
|
||||||
|
// E.g. ['v3.0.0-beta.1'] to generate the release notes for 3.0.0-beta.1 :) from
|
||||||
|
// the current 3-0-x branch
|
||||||
|
const EXCLUDE_TAGS = []
|
||||||
|
|
||||||
|
const entities = new Entities()
|
||||||
|
const github = new GitHub()
|
||||||
|
const gitDir = path.resolve(__dirname, '..', '..')
|
||||||
|
github.authenticate({ type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN })
|
||||||
|
let currentBranch
|
||||||
|
|
||||||
|
const semanticMap = new Map()
|
||||||
|
for (const line of fs.readFileSync(path.resolve(__dirname, 'legacy-pr-semantic-map.csv'), 'utf8').split('\n')) {
|
||||||
|
if (!line) continue
|
||||||
|
const bits = line.split(',')
|
||||||
|
if (bits.length !== 2) continue
|
||||||
|
semanticMap.set(bits[0], bits[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCurrentBranch = async () => {
|
||||||
|
if (currentBranch) return currentBranch
|
||||||
|
const gitArgs = ['rev-parse', '--abbrev-ref', 'HEAD']
|
||||||
|
const branchDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||||
|
if (branchDetails.exitCode === 0) {
|
||||||
|
currentBranch = branchDetails.stdout.trim()
|
||||||
|
return currentBranch
|
||||||
|
}
|
||||||
|
throw GitProcess.parseError(branchDetails.stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBranchOffPoint = async (branchName) => {
|
||||||
|
const gitArgs = ['merge-base', branchName, 'master']
|
||||||
|
const commitDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||||
|
if (commitDetails.exitCode === 0) {
|
||||||
|
return commitDetails.stdout.trim()
|
||||||
|
}
|
||||||
|
throw GitProcess.parseError(commitDetails.stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTagsOnBranch = async (branchName) => {
|
||||||
|
const gitArgs = ['tag', '--merged', branchName]
|
||||||
|
const tagDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||||
|
if (tagDetails.exitCode === 0) {
|
||||||
|
return tagDetails.stdout.trim().split('\n').filter(tag => !EXCLUDE_TAGS.includes(tag))
|
||||||
|
}
|
||||||
|
throw GitProcess.parseError(tagDetails.stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
const memLastKnownRelease = new Map()
|
||||||
|
|
||||||
|
const getLastKnownReleaseOnBranch = async (branchName) => {
|
||||||
|
if (memLastKnownRelease.has(branchName)) {
|
||||||
|
return memLastKnownRelease.get(branchName)
|
||||||
|
}
|
||||||
|
const tags = await getTagsOnBranch(branchName)
|
||||||
|
if (!tags.length) {
|
||||||
|
throw new Error(`Branch ${branchName} has no tags, we have no idea what the last release was`)
|
||||||
|
}
|
||||||
|
const branchOffPointTags = await getTagsOnBranch(await getBranchOffPoint(branchName))
|
||||||
|
if (branchOffPointTags.length >= tags.length) {
|
||||||
|
// No release on this branch
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
memLastKnownRelease.set(branchName, tags[tags.length - 1])
|
||||||
|
// Latest tag is the latest release
|
||||||
|
return tags[tags.length - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBranches = async () => {
|
||||||
|
const gitArgs = ['branch', '--remote']
|
||||||
|
const branchDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||||
|
if (branchDetails.exitCode === 0) {
|
||||||
|
return branchDetails.stdout.trim().split('\n').map(b => b.trim()).filter(branch => branch !== 'origin/HEAD -> origin/master')
|
||||||
|
}
|
||||||
|
throw GitProcess.parseError(branchDetails.stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
const semverify = (v) => v.replace(/^origin\//, '').replace('x', '0').replace(/-/g, '.')
|
||||||
|
|
||||||
|
const getLastReleaseBranch = async () => {
|
||||||
|
const current = await getCurrentBranch()
|
||||||
|
const allBranches = await getBranches()
|
||||||
|
const releaseBranches = allBranches
|
||||||
|
.filter(branch => /^origin\/[0-9]+-[0-9]+-x$/.test(branch))
|
||||||
|
.filter(branch => branch !== current && branch !== `origin/${current}`)
|
||||||
|
let latest = null
|
||||||
|
for (const b of releaseBranches) {
|
||||||
|
if (latest === null) latest = b
|
||||||
|
if (semver.gt(semverify(b), semverify(latest))) {
|
||||||
|
latest = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return latest
|
||||||
|
}
|
||||||
|
|
||||||
|
const commitBeforeTag = async (commit, tag) => {
|
||||||
|
const gitArgs = ['tag', '--contains', commit]
|
||||||
|
const tagDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||||
|
if (tagDetails.exitCode === 0) {
|
||||||
|
return tagDetails.stdout.split('\n').includes(tag)
|
||||||
|
}
|
||||||
|
throw GitProcess.parseError(tagDetails.stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCommitsMergedIntoCurrentBranchSincePoint = async (point) => {
|
||||||
|
return getCommitsBetween(point, 'HEAD')
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCommitsBetween = async (point1, point2) => {
|
||||||
|
const gitArgs = ['rev-list', `${point1}..${point2}`]
|
||||||
|
const commitsDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||||
|
if (commitsDetails.exitCode !== 0) {
|
||||||
|
throw GitProcess.parseError(commitsDetails.stderr)
|
||||||
|
}
|
||||||
|
return commitsDetails.stdout.trim().split('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
const TITLE_PREFIX = 'Merged Pull Request: '
|
||||||
|
|
||||||
|
const getCommitDetails = async (commitHash) => {
|
||||||
|
const commitInfo = await (await fetch(`https://github.com/electron/electron/branch_commits/${commitHash}`)).text()
|
||||||
|
const bits = commitInfo.split('</a>)')[0].split('>')
|
||||||
|
const prIdent = bits[bits.length - 1].trim()
|
||||||
|
if (!prIdent || commitInfo.indexOf('href="/electron/electron/pull') === -1) {
|
||||||
|
console.warn(`WARNING: Could not track commit "${commitHash}" to a pull request, it may have been committed directly to the branch`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const title = commitInfo.split('title="')[1].split('"')[0]
|
||||||
|
if (!title.startsWith(TITLE_PREFIX)) {
|
||||||
|
console.warn(`WARNING: Unknown PR title for commit "${commitHash}" in PR "${prIdent}"`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mergedFrom: prIdent,
|
||||||
|
prTitle: entities.decode(title.substr(TITLE_PREFIX.length))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const doWork = async (items, fn, concurrent = 5) => {
|
||||||
|
const results = []
|
||||||
|
const toUse = [].concat(items)
|
||||||
|
let i = 1
|
||||||
|
const doBit = async () => {
|
||||||
|
if (toUse.length === 0) return
|
||||||
|
console.log(`Running ${i}/${items.length}`)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
const item = toUse.pop()
|
||||||
|
const index = toUse.length
|
||||||
|
results[index] = await fn(item)
|
||||||
|
await doBit()
|
||||||
|
}
|
||||||
|
const bits = []
|
||||||
|
for (let i = 0; i < concurrent; i += 1) {
|
||||||
|
bits.push(doBit())
|
||||||
|
}
|
||||||
|
await Promise.all(bits)
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
const notes = new Map()
|
||||||
|
|
||||||
|
const NoteType = {
|
||||||
|
FIX: 'fix',
|
||||||
|
FEATURE: 'feature',
|
||||||
|
BREAKING_CHANGE: 'breaking-change',
|
||||||
|
DOCUMENTATION: 'doc',
|
||||||
|
OTHER: 'other',
|
||||||
|
UNKNOWN: 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
class Note {
|
||||||
|
constructor (trueTitle, prNumber, ignoreIfInVersion) {
|
||||||
|
// Self bindings
|
||||||
|
this.guessType = this.guessType.bind(this)
|
||||||
|
this.fetchPrInfo = this.fetchPrInfo.bind(this)
|
||||||
|
this._getPr = this._getPr.bind(this)
|
||||||
|
|
||||||
|
if (!trueTitle.trim()) console.error(prNumber)
|
||||||
|
|
||||||
|
this._ignoreIfInVersion = ignoreIfInVersion
|
||||||
|
this.reverted = false
|
||||||
|
if (notes.has(trueTitle)) {
|
||||||
|
console.warn(`Duplicate PR trueTitle: "${trueTitle}", "${prNumber}" this might cause weird reversions (this would be RARE)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memoize
|
||||||
|
notes.set(trueTitle, this)
|
||||||
|
|
||||||
|
this.originalTitle = trueTitle
|
||||||
|
this.title = trueTitle
|
||||||
|
this.prNumber = prNumber
|
||||||
|
this.stripColon = true
|
||||||
|
if (this.guessType() !== NoteType.UNKNOWN && this.stripColon) {
|
||||||
|
this.title = trueTitle.split(':').slice(1).join(':').trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guessType () {
|
||||||
|
if (this.originalTitle.startsWith('fix:') ||
|
||||||
|
this.originalTitle.startsWith('Fix:')) return NoteType.FIX
|
||||||
|
if (this.originalTitle.startsWith('feat:')) return NoteType.FEATURE
|
||||||
|
if (this.originalTitle.startsWith('spec:') ||
|
||||||
|
this.originalTitle.startsWith('build:') ||
|
||||||
|
this.originalTitle.startsWith('test:') ||
|
||||||
|
this.originalTitle.startsWith('chore:') ||
|
||||||
|
this.originalTitle.startsWith('deps:') ||
|
||||||
|
this.originalTitle.startsWith('refactor:') ||
|
||||||
|
this.originalTitle.startsWith('tools:') ||
|
||||||
|
this.originalTitle.startsWith('vendor:') ||
|
||||||
|
this.originalTitle.startsWith('perf:') ||
|
||||||
|
this.originalTitle.startsWith('style:') ||
|
||||||
|
this.originalTitle.startsWith('ci')) return NoteType.OTHER
|
||||||
|
if (this.originalTitle.startsWith('doc:') ||
|
||||||
|
this.originalTitle.startsWith('docs:')) return NoteType.DOCUMENTATION
|
||||||
|
|
||||||
|
this.stripColon = false
|
||||||
|
|
||||||
|
if (this.pr && this.pr.data.labels.find(label => label.name === 'semver/breaking-change')) {
|
||||||
|
return NoteType.BREAKING_CHANGE
|
||||||
|
}
|
||||||
|
// FIXME: Backported features will not be picked up by this
|
||||||
|
if (this.pr && this.pr.data.labels.find(label => label.name === 'semver/nonbreaking-feature')) {
|
||||||
|
return NoteType.FEATURE
|
||||||
|
}
|
||||||
|
|
||||||
|
const n = this.prNumber.replace('#', '')
|
||||||
|
if (semanticMap.has(n)) {
|
||||||
|
switch (semanticMap.get(n)) {
|
||||||
|
case 'feat':
|
||||||
|
return NoteType.FEATURE
|
||||||
|
case 'fix':
|
||||||
|
return NoteType.FIX
|
||||||
|
case 'breaking-change':
|
||||||
|
return NoteType.BREAKING_CHANGE
|
||||||
|
case 'doc':
|
||||||
|
return NoteType.DOCUMENTATION
|
||||||
|
case 'build':
|
||||||
|
case 'vendor':
|
||||||
|
case 'refactor':
|
||||||
|
case 'spec':
|
||||||
|
return NoteType.OTHER
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown semantic mapping: ${semanticMap.get(n)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NoteType.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
async _getPr (n) {
|
||||||
|
const cachePath = path.resolve(CACHE_DIR, n)
|
||||||
|
if (fs.existsSync(cachePath)) {
|
||||||
|
return JSON.parse(fs.readFileSync(cachePath, 'utf8'))
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const pr = await github.pullRequests.get({
|
||||||
|
number: n,
|
||||||
|
owner: 'electron',
|
||||||
|
repo: 'electron'
|
||||||
|
})
|
||||||
|
fs.writeFileSync(cachePath, JSON.stringify({ data: pr.data }))
|
||||||
|
return pr
|
||||||
|
} catch (err) {
|
||||||
|
console.info('#### FAILED:', `#${n}`)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchPrInfo () {
|
||||||
|
if (this.pr) return
|
||||||
|
const n = this.prNumber.replace('#', '')
|
||||||
|
this.pr = await this._getPr(n)
|
||||||
|
if (this.pr.data.labels.find(label => label.name === `merged/${this._ignoreIfInVersion.replace('origin/', '')}`)) {
|
||||||
|
// This means we probably backported this PR, let's try figure out what
|
||||||
|
// the corresponding backport PR would be by searching through comments
|
||||||
|
// for trop
|
||||||
|
let comments
|
||||||
|
const cacheCommentsPath = path.resolve(CACHE_DIR, `${n}-comments`)
|
||||||
|
if (fs.existsSync(cacheCommentsPath)) {
|
||||||
|
comments = JSON.parse(fs.readFileSync(cacheCommentsPath, 'utf8'))
|
||||||
|
} else {
|
||||||
|
comments = await github.issues.getComments({
|
||||||
|
number: n,
|
||||||
|
owner: 'electron',
|
||||||
|
repo: 'electron',
|
||||||
|
per_page: 100
|
||||||
|
})
|
||||||
|
fs.writeFileSync(cacheCommentsPath, JSON.stringify({ data: comments.data }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const tropComment = comments.data.find(
|
||||||
|
c => (
|
||||||
|
new RegExp(`We have automatically backported this PR to "${this._ignoreIfInVersion.replace('origin/', '')}", please check out #[0-9]+`)
|
||||||
|
).test(c.body)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (tropComment) {
|
||||||
|
const commentBits = tropComment.body.split('#')
|
||||||
|
const tropPrNumber = commentBits[commentBits.length - 1]
|
||||||
|
|
||||||
|
const tropPr = await this._getPr(tropPrNumber)
|
||||||
|
if (tropPr.data.merged && tropPr.data.merge_commit_sha) {
|
||||||
|
if (await commitBeforeTag(tropPr.data.merge_commit_sha, await getLastKnownReleaseOnBranch(this._ignoreIfInVersion))) {
|
||||||
|
this.reverted = true
|
||||||
|
console.log('PR', this.prNumber, 'was backported to a previous version, ignoring from notes')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Note.findByTrueTitle = (trueTitle) => notes.get(trueTitle)
|
||||||
|
|
||||||
|
class ReleaseNotes {
|
||||||
|
constructor (ignoreIfInVersion) {
|
||||||
|
this._ignoreIfInVersion = ignoreIfInVersion
|
||||||
|
this._handledPrs = new Set()
|
||||||
|
this._revertedPrs = new Set()
|
||||||
|
this.other = []
|
||||||
|
this.docs = []
|
||||||
|
this.fixes = []
|
||||||
|
this.features = []
|
||||||
|
this.breakingChanges = []
|
||||||
|
this.unknown = []
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseCommits (commitHashes) {
|
||||||
|
await doWork(commitHashes, async (commit) => {
|
||||||
|
const info = await getCommitDetails(commit)
|
||||||
|
if (!info) return
|
||||||
|
// Only handle each PR once
|
||||||
|
if (this._handledPrs.has(info.mergedFrom)) return
|
||||||
|
this._handledPrs.add(info.mergedFrom)
|
||||||
|
|
||||||
|
// Strip the trop backport prefix
|
||||||
|
const trueTitle = info.prTitle.replace(/^Backport \([0-9]+-[0-9]+-x\) - /, '')
|
||||||
|
if (this._revertedPrs.has(trueTitle)) return
|
||||||
|
|
||||||
|
// Handle PRs that revert other PRs
|
||||||
|
if (trueTitle.startsWith('Revert "')) {
|
||||||
|
const revertedTrueTitle = trueTitle.substr(8, trueTitle.length - 9)
|
||||||
|
this._revertedPrs.add(revertedTrueTitle)
|
||||||
|
const existingNote = Note.findByTrueTitle(revertedTrueTitle)
|
||||||
|
if (existingNote) {
|
||||||
|
existingNote.reverted = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a note for this PR
|
||||||
|
const note = new Note(trueTitle, info.mergedFrom, this._ignoreIfInVersion)
|
||||||
|
try {
|
||||||
|
await note.fetchPrInfo()
|
||||||
|
} catch (err) {
|
||||||
|
console.error(commit, info)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
switch (note.guessType()) {
|
||||||
|
case NoteType.FIX:
|
||||||
|
this.fixes.push(note)
|
||||||
|
break
|
||||||
|
case NoteType.FEATURE:
|
||||||
|
this.features.push(note)
|
||||||
|
break
|
||||||
|
case NoteType.BREAKING_CHANGE:
|
||||||
|
this.breakingChanges.push(note)
|
||||||
|
break
|
||||||
|
case NoteType.OTHER:
|
||||||
|
this.other.push(note)
|
||||||
|
break
|
||||||
|
case NoteType.DOCUMENTATION:
|
||||||
|
this.docs.push(note)
|
||||||
|
break
|
||||||
|
case NoteType.UNKNOWN:
|
||||||
|
default:
|
||||||
|
this.unknown.push(note)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}, 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
list (notes) {
|
||||||
|
if (notes.length === 0) {
|
||||||
|
return '_There are no items in this section this release_'
|
||||||
|
}
|
||||||
|
return notes
|
||||||
|
.filter(note => !note.reverted)
|
||||||
|
.sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase()))
|
||||||
|
.map((note) => `* ${note.title.trim()} ${note.prNumber}`).join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return `
|
||||||
|
# Release Notes
|
||||||
|
|
||||||
|
## Breaking Changes
|
||||||
|
|
||||||
|
${this.list(this.breakingChanges)}
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
${this.list(this.features)}
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
${this.list(this.fixes)}
|
||||||
|
|
||||||
|
## Other Changes (E.g. Internal refactors or build system updates)
|
||||||
|
|
||||||
|
${this.list(this.other)}
|
||||||
|
|
||||||
|
## Documentation Updates
|
||||||
|
|
||||||
|
Some documentation updates, fixes and reworks: ${
|
||||||
|
this.docs.length === 0
|
||||||
|
? '_None in this release_'
|
||||||
|
: this.docs.sort((a, b) => a.prNumber.localeCompare(b.prNumber)).map(note => note.prNumber).join(', ')
|
||||||
|
}
|
||||||
|
${this.unknown.filter(n => !n.reverted).length > 0
|
||||||
|
? `## Unknown (fix these before publishing release)
|
||||||
|
|
||||||
|
${this.list(this.unknown)}
|
||||||
|
` : ''}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main () {
|
||||||
|
if (!fs.existsSync(CACHE_DIR)) {
|
||||||
|
fs.mkdirSync(CACHE_DIR)
|
||||||
|
}
|
||||||
|
const lastReleaseBranch = await getLastReleaseBranch()
|
||||||
|
|
||||||
|
const notes = new ReleaseNotes(lastReleaseBranch)
|
||||||
|
const lastKnownReleaseInCurrentStream = await getLastKnownReleaseOnBranch(await getCurrentBranch())
|
||||||
|
const currentBranchOff = await getBranchOffPoint(await getCurrentBranch())
|
||||||
|
|
||||||
|
const commits = await getCommitsMergedIntoCurrentBranchSincePoint(
|
||||||
|
lastKnownReleaseInCurrentStream || currentBranchOff
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!lastKnownReleaseInCurrentStream) {
|
||||||
|
// This means we are the first release in our stream
|
||||||
|
// FIXME: This will not work for minor releases!!!!
|
||||||
|
|
||||||
|
const lastReleaseBranch = await getLastReleaseBranch()
|
||||||
|
const lastBranchOff = await getBranchOffPoint(lastReleaseBranch)
|
||||||
|
commits.push(...await getCommitsBetween(lastBranchOff, currentBranchOff))
|
||||||
|
}
|
||||||
|
|
||||||
|
await notes.parseCommits(commits)
|
||||||
|
|
||||||
|
console.log(notes.render())
|
||||||
|
|
||||||
|
const badNotes = notes.unknown.filter(n => !n.reverted).length
|
||||||
|
if (badNotes > 0) {
|
||||||
|
throw new Error(`You have ${badNotes.length} unknown release notes, please fix them before releasing`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.mainModule === module) {
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error('Error Occurred:', err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
}
|
193
script/release-notes/legacy-pr-semantic-map.csv
Normal file
193
script/release-notes/legacy-pr-semantic-map.csv
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
12884,fix
|
||||||
|
12093,feat
|
||||||
|
12595,doc
|
||||||
|
12674,doc
|
||||||
|
12577,doc
|
||||||
|
12084,doc
|
||||||
|
12103,doc
|
||||||
|
12948,build
|
||||||
|
12496,feat
|
||||||
|
13133,build
|
||||||
|
12651,build
|
||||||
|
12767,doc
|
||||||
|
12238,build
|
||||||
|
12646,build
|
||||||
|
12373,doc
|
||||||
|
12723,feat
|
||||||
|
12202,doc
|
||||||
|
12504,doc
|
||||||
|
12669,doc
|
||||||
|
13044,feat
|
||||||
|
12746,spec
|
||||||
|
12617,doc
|
||||||
|
12532,feat
|
||||||
|
12619,feat
|
||||||
|
12118,build
|
||||||
|
12921,build
|
||||||
|
13281,doc
|
||||||
|
12059,feat
|
||||||
|
12131,doc
|
||||||
|
12123,doc
|
||||||
|
12080,build
|
||||||
|
12904,fix
|
||||||
|
12562,fix
|
||||||
|
12122,spec
|
||||||
|
12817,spec
|
||||||
|
12254,fix
|
||||||
|
12999,vendor
|
||||||
|
13248,vendor
|
||||||
|
12104,build
|
||||||
|
12477,feat
|
||||||
|
12648,refactor
|
||||||
|
12649,refactor
|
||||||
|
12650,refactor
|
||||||
|
12673,refactor
|
||||||
|
12305,refactor
|
||||||
|
12168,refactor
|
||||||
|
12627,refactor
|
||||||
|
12446,doc
|
||||||
|
12304,refactor
|
||||||
|
12615,breaking-change
|
||||||
|
12135,feat
|
||||||
|
12155,doc
|
||||||
|
12975,fix
|
||||||
|
12501,fix
|
||||||
|
13065,fix
|
||||||
|
13089,build
|
||||||
|
12786,doc
|
||||||
|
12736,doc
|
||||||
|
11966,doc
|
||||||
|
12885,fix
|
||||||
|
12984,refactor
|
||||||
|
12187,build
|
||||||
|
12535,refactor
|
||||||
|
12538,feat
|
||||||
|
12190,fix
|
||||||
|
12139,fix
|
||||||
|
11328,fix
|
||||||
|
12828,feat
|
||||||
|
12614,feat
|
||||||
|
12546,feat
|
||||||
|
12647,refactor
|
||||||
|
12987,build
|
||||||
|
12900,doc
|
||||||
|
12389,doc
|
||||||
|
12387,doc
|
||||||
|
12232,doc
|
||||||
|
12742,build
|
||||||
|
12043,fix
|
||||||
|
12741,fix
|
||||||
|
12995,fix
|
||||||
|
12395,fix
|
||||||
|
12003,build
|
||||||
|
12216,fix
|
||||||
|
12132,fix
|
||||||
|
12062,fix
|
||||||
|
12968,doc
|
||||||
|
12422,doc
|
||||||
|
12149,doc
|
||||||
|
13339,build
|
||||||
|
12044,fix
|
||||||
|
12327,fix
|
||||||
|
12180,fix
|
||||||
|
12263,spec
|
||||||
|
12153,spec
|
||||||
|
13055,feat
|
||||||
|
12113,doc
|
||||||
|
12067,doc
|
||||||
|
12882,build
|
||||||
|
13029,build
|
||||||
|
13067,doc
|
||||||
|
12196,build
|
||||||
|
12797,doc
|
||||||
|
12013,fix
|
||||||
|
12507,fix
|
||||||
|
11607,feat
|
||||||
|
12837,build
|
||||||
|
11613,feat
|
||||||
|
12015,spec
|
||||||
|
12058,doc
|
||||||
|
12403,spec
|
||||||
|
12192,feat
|
||||||
|
12204,doc
|
||||||
|
13294,doc
|
||||||
|
12542,doc
|
||||||
|
12826,refactor
|
||||||
|
12781,doc
|
||||||
|
12157,fix
|
||||||
|
12319,fix
|
||||||
|
12188,build
|
||||||
|
12399,doc
|
||||||
|
12145,doc
|
||||||
|
12661,refactor
|
||||||
|
8953,fix
|
||||||
|
12037,fix
|
||||||
|
12186,spec
|
||||||
|
12397,fix
|
||||||
|
12040,doc
|
||||||
|
12886,refactor
|
||||||
|
12008,refactor
|
||||||
|
12716,refactor
|
||||||
|
12750,refactor
|
||||||
|
12787,refactor
|
||||||
|
12858,refactor
|
||||||
|
12140,refactor
|
||||||
|
12503,refactor
|
||||||
|
12514,refactor
|
||||||
|
12584,refactor
|
||||||
|
12596,refactor
|
||||||
|
12637,refactor
|
||||||
|
12660,refactor
|
||||||
|
12696,refactor
|
||||||
|
12877,refactor
|
||||||
|
13030,refactor
|
||||||
|
12916,build
|
||||||
|
12896,build
|
||||||
|
13039,breaking-change
|
||||||
|
11927,build
|
||||||
|
12847,doc
|
||||||
|
12852,doc
|
||||||
|
12194,fix
|
||||||
|
12870,doc
|
||||||
|
12924,fix
|
||||||
|
12682,doc
|
||||||
|
12004,refactor
|
||||||
|
12601,refactor
|
||||||
|
12998,fix
|
||||||
|
13105,vendor
|
||||||
|
12452,doc
|
||||||
|
12738,fix
|
||||||
|
12536,refactor
|
||||||
|
12189,spec
|
||||||
|
13122,spec
|
||||||
|
12662,fix
|
||||||
|
12665,doc
|
||||||
|
12419,feat
|
||||||
|
12756,doc
|
||||||
|
12616,refactor
|
||||||
|
12679,breaking-change
|
||||||
|
12000,doc
|
||||||
|
12372,build
|
||||||
|
12805,build
|
||||||
|
12348,fix
|
||||||
|
12315,doc
|
||||||
|
12072,doc
|
||||||
|
12912,doc
|
||||||
|
12982,fix
|
||||||
|
12105,doc
|
||||||
|
12917,spec
|
||||||
|
12400,doc
|
||||||
|
12101,feat
|
||||||
|
12642,build
|
||||||
|
13058,fix
|
||||||
|
12913,vendor
|
||||||
|
13298,vendor
|
||||||
|
13042,build
|
||||||
|
11230,feat
|
||||||
|
11459,feat
|
||||||
|
12476,vendor
|
||||||
|
11937,doc
|
||||||
|
12328,build
|
||||||
|
12539,refactor
|
||||||
|
12127,build
|
||||||
|
12537,build
|
|
Loading…
Reference in a new issue