2018-02-04 11:18:09 +00:00
|
|
|
import { sort as semverSort, SemVer } from 'semver'
|
|
|
|
|
2018-02-04 02:32:55 +00:00
|
|
|
import { spawn } from '../changelog/spawn'
|
|
|
|
import { getLogLines } from '../changelog/git'
|
2018-02-04 04:16:32 +00:00
|
|
|
import {
|
|
|
|
convertToChangelogFormat,
|
|
|
|
getChangelogEntriesSince,
|
|
|
|
} from '../changelog/parser'
|
2018-02-04 11:18:09 +00:00
|
|
|
|
|
|
|
import { Channel } from './channel'
|
2018-02-04 00:32:38 +00:00
|
|
|
import { getNextVersionNumber } from './version'
|
2020-01-30 14:34:14 +00:00
|
|
|
import { execSync } from 'child_process'
|
|
|
|
|
|
|
|
import { writeFileSync } from 'fs'
|
2020-01-30 14:39:21 +00:00
|
|
|
import { join } from 'path'
|
|
|
|
|
|
|
|
const changelogPath = join(__dirname, '..', '..', 'changelog.json')
|
2018-02-03 23:35:23 +00:00
|
|
|
|
2018-02-04 02:37:26 +00:00
|
|
|
const jsonStringify: (obj: any) => string = require('json-pretty')
|
|
|
|
|
2020-02-04 22:33:46 +00:00
|
|
|
/**
|
|
|
|
* Returns the latest release tag, according to git and semver
|
|
|
|
* (ignores test releases)
|
|
|
|
*
|
|
|
|
* @param options there's only one option `excludeBetaReleases`,
|
|
|
|
* which is a boolean
|
|
|
|
*/
|
2018-02-09 04:04:30 +00:00
|
|
|
async function getLatestRelease(options: {
|
|
|
|
excludeBetaReleases: boolean
|
|
|
|
}): Promise<string> {
|
2018-02-03 23:35:23 +00:00
|
|
|
const allTags = await spawn('git', ['tag'])
|
|
|
|
let releaseTags = allTags
|
|
|
|
.split('\n')
|
|
|
|
.filter(tag => tag.startsWith('release-'))
|
2020-01-30 14:34:14 +00:00
|
|
|
.filter(tag => !tag.includes('-linux'))
|
|
|
|
.filter(tag => !tag.includes('-test'))
|
2018-02-03 23:35:23 +00:00
|
|
|
|
2018-02-09 04:04:30 +00:00
|
|
|
if (options.excludeBetaReleases) {
|
2020-01-30 14:34:14 +00:00
|
|
|
releaseTags = releaseTags.filter(tag => !tag.includes('-beta'))
|
2018-02-03 23:35:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const releaseVersions = releaseTags.map(tag => tag.substr(8))
|
|
|
|
|
|
|
|
const sortedTags = semverSort(releaseVersions)
|
|
|
|
const latestTag = sortedTags[sortedTags.length - 1]
|
|
|
|
|
2018-02-09 03:55:45 +00:00
|
|
|
return latestTag instanceof SemVer ? latestTag.raw : latestTag
|
2018-02-03 23:35:23 +00:00
|
|
|
}
|
|
|
|
|
2020-02-04 22:33:46 +00:00
|
|
|
/** Converts a string to Channel type if possible */
|
2018-02-04 11:17:04 +00:00
|
|
|
function parseChannel(arg: string): Channel {
|
2018-02-09 04:04:30 +00:00
|
|
|
if (arg === 'production' || arg === 'beta' || arg === 'test') {
|
2018-02-04 01:15:29 +00:00
|
|
|
return arg
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error(`An invalid channel ${arg} has been provided`)
|
|
|
|
}
|
|
|
|
|
2020-02-04 22:33:46 +00:00
|
|
|
/**
|
|
|
|
* Prints out next steps to the console
|
|
|
|
*
|
|
|
|
* @param nextVersion version for the next release
|
|
|
|
* @param entries release notes for the next release
|
|
|
|
*/
|
2018-02-04 04:16:32 +00:00
|
|
|
function printInstructions(nextVersion: string, entries: Array<string>) {
|
2018-02-09 03:55:45 +00:00
|
|
|
const object: any = {}
|
2020-02-04 22:39:08 +00:00
|
|
|
object[nextVersion] = entries.sort()
|
2018-02-09 03:55:45 +00:00
|
|
|
|
2018-02-09 04:47:47 +00:00
|
|
|
const steps = [
|
2018-03-14 14:43:21 +00:00
|
|
|
`Update the app/package.json 'version' to '${nextVersion}' (make sure this aligns with semver format of 'major.minor.patch')`,
|
|
|
|
`Concatenate this to the beginning of the 'releases' element in the changelog.json as a starting point:\n${jsonStringify(
|
2018-02-09 04:47:47 +00:00
|
|
|
object
|
|
|
|
)}\n`,
|
2020-01-29 14:32:43 +00:00
|
|
|
'Revise the release notes according to https://github.com/desktop/desktop/blob/development/docs/process/writing-release-notes.md',
|
2019-01-09 18:02:04 +00:00
|
|
|
'Commit the changes (on development or as new branch) and push them to GitHub',
|
|
|
|
'Read this to perform the release: https://github.com/desktop/desktop/blob/development/docs/process/releasing-updates.md',
|
2018-02-09 04:47:47 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
console.log(steps.map((value, index) => `${index + 1}. ${value}`).join('\n'))
|
2018-02-04 03:51:21 +00:00
|
|
|
}
|
|
|
|
|
2018-02-03 23:35:23 +00:00
|
|
|
export async function run(args: ReadonlyArray<string>): Promise<void> {
|
|
|
|
if (args.length === 0) {
|
|
|
|
throw new Error(
|
|
|
|
`You have not specified a channel to draft this release for. Choose one of 'production' or 'beta'`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-02-04 01:15:29 +00:00
|
|
|
const channel = parseChannel(args[0])
|
2018-02-04 00:32:38 +00:00
|
|
|
const excludeBetaReleases = channel === 'production'
|
2018-02-09 04:04:30 +00:00
|
|
|
const previousVersion = await getLatestRelease({ excludeBetaReleases })
|
2018-02-04 02:32:55 +00:00
|
|
|
const nextVersion = getNextVersionNumber(previousVersion, channel)
|
|
|
|
|
2018-02-04 02:37:26 +00:00
|
|
|
const lines = await getLogLines(`release-${previousVersion}`)
|
2018-02-04 05:16:58 +00:00
|
|
|
const noChangesFound = lines.every(l => l.trim().length === 0)
|
|
|
|
|
|
|
|
if (noChangesFound) {
|
2020-02-04 22:33:46 +00:00
|
|
|
// print instructions with no changelog included
|
2018-02-04 05:16:58 +00:00
|
|
|
printInstructions(nextVersion, [])
|
|
|
|
} else {
|
2020-01-30 14:34:14 +00:00
|
|
|
console.log(
|
|
|
|
`Setting app version to "${nextVersion}" in app/package.json...`
|
|
|
|
)
|
|
|
|
// this can throw and that's okay!
|
|
|
|
execSync(`npm version ${nextVersion}`, {
|
|
|
|
cwd: 'app',
|
|
|
|
encoding: 'utf8',
|
|
|
|
})
|
|
|
|
console.log(`Set!`)
|
|
|
|
|
2018-02-04 05:16:58 +00:00
|
|
|
const changelogEntries = await convertToChangelogFormat(lines)
|
2020-01-30 14:39:21 +00:00
|
|
|
const changelog = require(changelogPath)
|
|
|
|
changelog.releases[nextVersion] = changelogEntries
|
2020-01-30 14:34:14 +00:00
|
|
|
|
|
|
|
// this might throw and that's ok (for now!)
|
2020-01-30 14:39:21 +00:00
|
|
|
writeFileSync(changelogPath, jsonStringify(changelog))
|
2020-01-30 14:34:14 +00:00
|
|
|
|
2018-02-04 05:16:58 +00:00
|
|
|
console.log("Here's what you should do next:\n")
|
|
|
|
|
|
|
|
if (channel === 'production') {
|
2020-02-04 22:33:46 +00:00
|
|
|
// make sure we only include entries since the latest production release
|
2018-02-04 05:16:58 +00:00
|
|
|
const existingChangelog = getChangelogEntriesSince(previousVersion)
|
2018-08-01 21:51:59 +00:00
|
|
|
const entries = [...existingChangelog]
|
2018-02-04 05:16:58 +00:00
|
|
|
printInstructions(nextVersion, entries)
|
|
|
|
} else if (channel === 'beta') {
|
2018-08-01 21:51:59 +00:00
|
|
|
const entries = [...changelogEntries]
|
2018-02-04 05:16:58 +00:00
|
|
|
printInstructions(nextVersion, entries)
|
|
|
|
}
|
2018-02-04 03:51:21 +00:00
|
|
|
}
|
2018-02-03 23:35:23 +00:00
|
|
|
}
|