Skip to content

Commit

Permalink
add support for generating release notes from upstream release (#900)
Browse files Browse the repository at this point in the history
  • Loading branch information
shiftkey committed Jul 27, 2023
1 parent e7803e7 commit 51bf5d5
Showing 1 changed file with 184 additions and 25 deletions.
209 changes: 184 additions & 25 deletions script/generate-release-notes.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
/* eslint-disable no-sync */

const glob = require('glob')
const { basename } = require('path')
const { basename, dirname, join } = require('path')
const fs = require('fs')

type ChecksumEntry = { filename: string; checksum: string }

type ChecksumGroups = Record<'x64' | 'arm' | 'arm64', Array<ChecksumEntry>>

type ReleaseNotesGroupType = 'new' | 'added' | 'fixed' | 'improved' | 'removed'

type ReleaseNotesGroups = Record<ReleaseNotesGroupType, Array<ReleaseNoteEntry>>

type ReleaseNoteEntry = {
text: string
ids: Array<number>
contributor?: string
}

// 3 architectures * 3 package formats * 2 files (package + checksum file)
const SUCCESSFUL_RELEASE_FILE_COUNT = 3 * 3 * 2

Expand Down Expand Up @@ -74,11 +84,13 @@ const shaEntriesByArchitecture: ChecksumGroups = {
console.log(`Found ${countFiles} files in artifacts directory`)
console.log(shaEntriesByArchitecture)

const releaseNotesByGroup = getReleaseGroups(releaseTagWithoutPrefix)

const draftReleaseNotes = generateDraftReleaseNotes(
[],
releaseNotesByGroup,
shaEntriesByArchitecture
)
const releaseNotesPath = __dirname + '/release_notes.txt'
const releaseNotesPath = join(__dirname, 'release_notes.txt')

fs.writeFileSync(releaseNotesPath, draftReleaseNotes, { encoding: 'utf8' })

Expand All @@ -99,42 +111,189 @@ function getShaContents(filePath: string): {
return { filename, checksum }
}

function formatEntry(e: ChecksumEntry): string {
return `**${e.filename}**\n${e.checksum}\n`
function extractIds(str: string): Array<number> {
const idRegex = /#(\d+)/g

const idArray = new Array<number>()
let match

while ((match = idRegex.exec(str))) {
const textValue = match[1].trim()
const numValue = parseInt(textValue, 10)
if (!isNaN(numValue)) {
idArray.push(numValue)
}
}

return idArray
}

/**
* Takes the release notes entries and the SHA entries, then merges them into the full draft release notes ✨
*/
function generateDraftReleaseNotes(
releaseNotesEntries: Array<string>,
shaEntries: ChecksumGroups
function parseCategory(str: string): ReleaseNotesGroupType | null {
const input = str.toLocaleLowerCase()
switch (input) {
case 'added':
case 'fixed':
case 'improved':
case 'new':
case 'removed':
return input
default:
return null
}
}

function getReleaseGroups(version: string): ReleaseNotesGroups {
if (!version.endsWith('-linux1')) {
return {
new: [],
added: [],
fixed: [],
improved: [],
removed: [],
}
}

const upstreamVersion = version.replace('-linux1', '')
const rootDir = dirname(__dirname)
const changelogFile = fs.readFileSync(join(rootDir, 'changelog.json'))
const changelogJson = JSON.parse(changelogFile)
const releases = changelogJson['releases']
const changelogForVersion: Array<string> | undefined =
releases[upstreamVersion]

if (!changelogForVersion) {
console.error(
`🔴 Changelog version ${upstreamVersion} not found in changelog.json, which is required for publishing a release based off an upstream releease. Aborting...`
)
process.exit(1)
}

console.log(`found release notes`, changelogForVersion)

const releaseNotesByGroup: ReleaseNotesGroups = {
new: [],
added: [],
fixed: [],
improved: [],
removed: [],
}

const releaseEntryExternalContributor = /\[(.*)\](.*)- (.*)\. Thanks (.*)!/
const releaseEntryRegex = /\[(.*)\](.*)- (.*)/

for (const entry of changelogForVersion) {
const externalMatch = releaseEntryExternalContributor.exec(entry)
if (externalMatch) {
const category = parseCategory(externalMatch[1])
const text = externalMatch[2].trim()
const ids = extractIds(externalMatch[3])
const contributor = externalMatch[4]

if (!category) {
console.warn(`unable to identify category for '${entry}'`)
} else {
releaseNotesByGroup[category].push({
text,
ids,
contributor,
})
}
} else {
const match = releaseEntryRegex.exec(entry)
if (match) {
const category = parseCategory(match[1])
const text = match[2].trim()
const ids = extractIds(match[3])
if (!category) {
console.warn(`unable to identify category for '${entry}'`)
} else {
releaseNotesByGroup[category].push({
text,
ids,
})
}
} else {
console.warn(`release entry does not match any format: '${entry}'`)
}
}
}

return releaseNotesByGroup
}

function formatReleaseNote(note: ReleaseNoteEntry): string {
const idsAsUrls = note.ids
.map(id => `https://github.com/desktop/desktop/issues/${id}`)
.join(' ')
const contributorNote = note.contributor
? `. Thanks ${note.contributor}!`
: ''

const template = ` - ${note.text} - ${idsAsUrls}${contributorNote}`

return template.trim()
}

function renderSection(
name: string,
items: Array<ReleaseNoteEntry>,
omitIfEmpty: boolean = true
): string {
const changelogText = releaseNotesEntries.join('\n')
if (items.length === 0 && omitIfEmpty) {
return ''
}

const x64Section = shaEntries.x64.map(formatEntry).join('\n')
const armSection = shaEntries.arm.map(formatEntry).join('\n')
const arm64Section = shaEntries.arm64.map(formatEntry).join('\n')
const itemsText =
items.length === 0 ? 'TODO' : items.map(formatReleaseNote).join('\n')

const draftReleaseNotes = `${changelogText}
return `
## ${name}
## Fixes and improvements
${itemsText}
`
}

TODO
function formatEntry(e: ChecksumEntry): string {
return `**${e.filename}**\n${e.checksum}\n`
}

## SHA-256 checksums
function renderArchitectureIfNotEmpty(
name: string,
items: Array<ChecksumEntry>
): string {
if (items.length === 0) {
return ''
}

### x64
const itemsText = items.map(formatEntry).join('\n')

${x64Section}
return `
## ${name}
### ARM64
${itemsText}
`
}

${arm64Section}
/**
* Takes the release notes entries and the SHA entries, then merges them into the full draft release notes ✨
*/
function generateDraftReleaseNotes(
releaseNotesGroups: ReleaseNotesGroups,
shaEntries: ChecksumGroups
): string {
const draftReleaseNotes = `
${renderSection('New', releaseNotesGroups.new)}
${renderSection('Added', releaseNotesGroups.added)}
${renderSection('Fixed', releaseNotesGroups.fixed, false)}
${renderSection('Improved', releaseNotesGroups.improved, false)}
${renderSection('Removed', releaseNotesGroups.removed)}
### ARM
## SHA-256 checksums
${armSection}`
${renderArchitectureIfNotEmpty('x64', shaEntries.x64)}
${renderArchitectureIfNotEmpty('ARM64', shaEntries.arm64)}
${renderArchitectureIfNotEmpty('ARM', shaEntries.arm)}`

return draftReleaseNotes
}

0 comments on commit 51bf5d5

Please sign in to comment.