mirror of
https://github.com/desktop/desktop
synced 2024-09-19 08:02:22 +00:00
extract logic so it can be used with new command
This commit is contained in:
parent
255f92962c
commit
3c17c19ee0
17
script/changelog/git.ts
Normal file
17
script/changelog/git.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { spawn } from './spawn'
|
||||
|
||||
export async function getLogLines(
|
||||
previousVersion: string
|
||||
): Promise<ReadonlyArray<string>> {
|
||||
const log = await spawn('git', [
|
||||
'log',
|
||||
`...${previousVersion}`,
|
||||
'--merges',
|
||||
'--grep="Merge pull request"',
|
||||
'--format=format:%s',
|
||||
'-z',
|
||||
'--',
|
||||
])
|
||||
|
||||
return log.split('\0')
|
||||
}
|
84
script/changelog/parser.ts
Normal file
84
script/changelog/parser.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
import { fetchPR, IAPIPR } from './api'
|
||||
|
||||
const PlaceholderChangeType = '???'
|
||||
const OfficialOwner = 'desktop'
|
||||
|
||||
interface IParsedCommit {
|
||||
readonly prID: number
|
||||
readonly owner: string
|
||||
}
|
||||
|
||||
function parseCommitTitle(line: string): IParsedCommit {
|
||||
// E.g.: Merge pull request #2424 from desktop/fix-shrinkwrap-file
|
||||
const re = /^Merge pull request #(\d+) from (.+?)\/.*$/
|
||||
const matches = line.match(re)
|
||||
if (!matches || matches.length !== 3) {
|
||||
throw new Error(`Unable to parse '${line}'`)
|
||||
}
|
||||
|
||||
const id = parseInt(matches[1], 10)
|
||||
if (isNaN(id)) {
|
||||
throw new Error(`Unable to parse PR number from '${line}': ${matches[1]}`)
|
||||
}
|
||||
|
||||
return {
|
||||
prID: id,
|
||||
owner: matches[2],
|
||||
}
|
||||
}
|
||||
|
||||
function capitalized(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
|
||||
function getChangelogEntry(commit: IParsedCommit, pr: IAPIPR): string {
|
||||
let issueRef = ''
|
||||
let type = PlaceholderChangeType
|
||||
const description = capitalized(pr.title)
|
||||
|
||||
const re = /Fixes #(\d+)/gi
|
||||
let match
|
||||
do {
|
||||
match = re.exec(pr.body)
|
||||
if (match && match.length > 1) {
|
||||
issueRef += ` #${match[1]}`
|
||||
}
|
||||
} while (match)
|
||||
|
||||
if (issueRef.length) {
|
||||
type = 'Fixed'
|
||||
} else {
|
||||
issueRef = ` #${commit.prID}`
|
||||
}
|
||||
|
||||
let attribution = ''
|
||||
if (commit.owner !== OfficialOwner) {
|
||||
attribution = `. Thanks @${commit.owner}!`
|
||||
}
|
||||
|
||||
return `[${type}] ${description} -${issueRef}${attribution}`
|
||||
}
|
||||
|
||||
export async function getChangelogEntries(
|
||||
lines: ReadonlyArray<string>
|
||||
): Promise<ReadonlyArray<string>> {
|
||||
const entries = []
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const commit = parseCommitTitle(line)
|
||||
const pr = await fetchPR(commit.prID)
|
||||
if (!pr) {
|
||||
throw new Error(`Unable to get PR from API: ${commit.prID}`)
|
||||
}
|
||||
|
||||
const entry = getChangelogEntry(commit, pr)
|
||||
entries.push(entry)
|
||||
} catch (e) {
|
||||
console.warn('Unable to parse line, using the full message.', e)
|
||||
|
||||
entries.push(`[${PlaceholderChangeType}] ${line}`)
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
|
@ -1,108 +1,10 @@
|
|||
import { spawn } from './spawn'
|
||||
import { fetchPR, IAPIPR } from './api'
|
||||
import { getLogLines } from './git'
|
||||
import { getChangelogEntries } from './parser'
|
||||
import { sort as semverSort } from 'semver'
|
||||
|
||||
const jsonStringify: (obj: any) => string = require('json-pretty')
|
||||
|
||||
const PlaceholderChangeType = '???'
|
||||
const OfficialOwner = 'desktop'
|
||||
|
||||
async function getLogLines(
|
||||
previousVersion: string
|
||||
): Promise<ReadonlyArray<string>> {
|
||||
const log = await spawn('git', [
|
||||
'log',
|
||||
`...${previousVersion}`,
|
||||
'--merges',
|
||||
'--grep="Merge pull request"',
|
||||
'--format=format:%s',
|
||||
'-z',
|
||||
'--',
|
||||
])
|
||||
|
||||
return log.split('\0')
|
||||
}
|
||||
|
||||
interface IParsedCommit {
|
||||
readonly prID: number
|
||||
readonly owner: string
|
||||
}
|
||||
|
||||
function parseCommitTitle(line: string): IParsedCommit {
|
||||
// E.g.: Merge pull request #2424 from desktop/fix-shrinkwrap-file
|
||||
const re = /^Merge pull request #(\d+) from (.+?)\/.*$/
|
||||
const matches = line.match(re)
|
||||
if (!matches || matches.length !== 3) {
|
||||
throw new Error(`Unable to parse '${line}'`)
|
||||
}
|
||||
|
||||
const id = parseInt(matches[1], 10)
|
||||
if (isNaN(id)) {
|
||||
throw new Error(`Unable to parse PR number from '${line}': ${matches[1]}`)
|
||||
}
|
||||
|
||||
return {
|
||||
prID: id,
|
||||
owner: matches[2],
|
||||
}
|
||||
}
|
||||
|
||||
function capitalized(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
|
||||
function getChangelogEntry(commit: IParsedCommit, pr: IAPIPR): string {
|
||||
let issueRef = ''
|
||||
let type = PlaceholderChangeType
|
||||
const description = capitalized(pr.title)
|
||||
|
||||
const re = /Fixes #(\d+)/gi
|
||||
let match
|
||||
do {
|
||||
match = re.exec(pr.body)
|
||||
if (match && match.length > 1) {
|
||||
issueRef += ` #${match[1]}`
|
||||
}
|
||||
} while (match)
|
||||
|
||||
if (issueRef.length) {
|
||||
type = 'Fixed'
|
||||
} else {
|
||||
issueRef = ` #${commit.prID}`
|
||||
}
|
||||
|
||||
let attribution = ''
|
||||
if (commit.owner !== OfficialOwner) {
|
||||
attribution = `. Thanks @${commit.owner}!`
|
||||
}
|
||||
|
||||
return `[${type}] ${description} -${issueRef}${attribution}`
|
||||
}
|
||||
|
||||
async function getChangelogEntries(
|
||||
lines: ReadonlyArray<string>
|
||||
): Promise<ReadonlyArray<string>> {
|
||||
const entries = []
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const commit = parseCommitTitle(line)
|
||||
const pr = await fetchPR(commit.prID)
|
||||
if (!pr) {
|
||||
throw new Error(`Unable to get PR from API: ${commit.prID}`)
|
||||
}
|
||||
|
||||
const entry = getChangelogEntry(commit, pr)
|
||||
entries.push(entry)
|
||||
} catch (e) {
|
||||
console.warn('Unable to parse line, using the full message.', e)
|
||||
|
||||
entries.push(`[${PlaceholderChangeType}] ${line}`)
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
export async function run(args: ReadonlyArray<string>): Promise<void> {
|
||||
try {
|
||||
await spawn('git', ['--version'])
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { spawn } from './spawn'
|
||||
import { spawn } from '../changelog/spawn'
|
||||
import { getLogLines } from '../changelog/git'
|
||||
import { getChangelogEntries } from '../changelog/parser'
|
||||
import { sort as semverSort, SemVer } from 'semver'
|
||||
import { getNextVersionNumber } from './version'
|
||||
|
||||
|
@ -51,10 +53,13 @@ export async function run(args: ReadonlyArray<string>): Promise<void> {
|
|||
|
||||
const channel = parseChannel(args[0])
|
||||
const excludeBetaReleases = channel === 'production'
|
||||
const latestVersion = await getLatestRelease(excludeBetaReleases)
|
||||
const nextVersion = getNextVersionNumber(latestVersion, channel)
|
||||
const previousVersion = await getLatestRelease(excludeBetaReleases)
|
||||
const nextVersion = getNextVersionNumber(previousVersion, channel)
|
||||
|
||||
const lines = await getLogLines(previousVersion)
|
||||
const changelogEntries = await getChangelogEntries(lines)
|
||||
|
||||
throw new Error(
|
||||
`Drafting a release from ${latestVersion} which will be ${nextVersion}`
|
||||
`Drafting a release from ${previousVersion} which will be ${nextVersion}`
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
import * as ChildProcess from 'child_process'
|
||||
|
||||
export function spawn(
|
||||
cmd: string,
|
||||
args: ReadonlyArray<string>
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = ChildProcess.spawn(cmd, args as string[], { shell: true })
|
||||
let receivedData = ''
|
||||
|
||||
child.on('error', reject)
|
||||
|
||||
child.stdout.on('data', data => {
|
||||
receivedData += data
|
||||
})
|
||||
|
||||
child.on('close', (code, signal) => {
|
||||
if (code === 0) {
|
||||
resolve(receivedData)
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`'${cmd} ${args.join(
|
||||
' '
|
||||
)}' exited with code ${code}, signal ${signal}`
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue