From 3c17c19ee09019503ca14de203d8eadd7f4c874a Mon Sep 17 00:00:00 2001 From: Brendan Forster Date: Sun, 4 Feb 2018 13:32:55 +1100 Subject: [PATCH] extract logic so it can be used with new command --- script/changelog/git.ts | 17 ++++++ script/changelog/parser.ts | 84 ++++++++++++++++++++++++++++ script/changelog/run.ts | 102 +--------------------------------- script/draft-release/run.ts | 13 +++-- script/draft-release/spawn.ts | 31 ----------- 5 files changed, 112 insertions(+), 135 deletions(-) create mode 100644 script/changelog/git.ts create mode 100644 script/changelog/parser.ts delete mode 100644 script/draft-release/spawn.ts diff --git a/script/changelog/git.ts b/script/changelog/git.ts new file mode 100644 index 0000000000..98726b8e03 --- /dev/null +++ b/script/changelog/git.ts @@ -0,0 +1,17 @@ +import { spawn } from './spawn' + +export async function getLogLines( + previousVersion: string +): Promise> { + const log = await spawn('git', [ + 'log', + `...${previousVersion}`, + '--merges', + '--grep="Merge pull request"', + '--format=format:%s', + '-z', + '--', + ]) + + return log.split('\0') +} diff --git a/script/changelog/parser.ts b/script/changelog/parser.ts new file mode 100644 index 0000000000..ec7a6f0b22 --- /dev/null +++ b/script/changelog/parser.ts @@ -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 +): Promise> { + 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 +} diff --git a/script/changelog/run.ts b/script/changelog/run.ts index 9b84ae160c..95e9d5ef8a 100644 --- a/script/changelog/run.ts +++ b/script/changelog/run.ts @@ -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> { - 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 -): Promise> { - 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): Promise { try { await spawn('git', ['--version']) diff --git a/script/draft-release/run.ts b/script/draft-release/run.ts index 76995a0126..48c5cf8b22 100644 --- a/script/draft-release/run.ts +++ b/script/draft-release/run.ts @@ -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): Promise { 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}` ) } diff --git a/script/draft-release/spawn.ts b/script/draft-release/spawn.ts deleted file mode 100644 index be3079b10d..0000000000 --- a/script/draft-release/spawn.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as ChildProcess from 'child_process' - -export function spawn( - cmd: string, - args: ReadonlyArray -): Promise { - 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}` - ) - ) - } - }) - }) -}