2017-10-23 15:02:50 +00:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
|
|
require('colors')
|
2018-05-14 21:21:51 +00:00
|
|
|
const args = require('minimist')(process.argv.slice(2), {
|
|
|
|
boolean: ['automaticRelease', 'notesOnly', 'stable']
|
|
|
|
})
|
2017-10-23 15:02:50 +00:00
|
|
|
const assert = require('assert')
|
2017-11-17 20:13:30 +00:00
|
|
|
const ciReleaseBuild = require('./ci-release-build')
|
2017-10-23 15:02:50 +00:00
|
|
|
const { execSync } = require('child_process')
|
|
|
|
const fail = '\u2717'.red
|
2018-02-12 16:01:50 +00:00
|
|
|
const { GitProcess } = require('dugite')
|
2017-10-23 15:02:50 +00:00
|
|
|
const GitHub = require('github')
|
|
|
|
const pass = '\u2713'.green
|
|
|
|
const path = require('path')
|
|
|
|
const pkg = require('../package.json')
|
2018-01-31 23:40:38 +00:00
|
|
|
const readline = require('readline')
|
2017-10-23 15:02:50 +00:00
|
|
|
const versionType = args._[0]
|
|
|
|
|
|
|
|
// TODO (future) automatically determine version based on conventional commits
|
|
|
|
// via conventional-recommended-bump
|
|
|
|
|
|
|
|
assert(process.env.ELECTRON_GITHUB_TOKEN, 'ELECTRON_GITHUB_TOKEN not found in environment')
|
|
|
|
if (!versionType && !args.notesOnly) {
|
|
|
|
console.log(`Usage: prepare-release versionType [major | minor | patch | beta]` +
|
2018-05-14 21:21:51 +00:00
|
|
|
` (--stable) (--notesOnly) (--automaticRelease) (--branch)`)
|
2017-10-23 15:02:50 +00:00
|
|
|
process.exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
const github = new GitHub()
|
|
|
|
const gitDir = path.resolve(__dirname, '..')
|
|
|
|
github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
|
|
|
|
|
2018-01-31 23:40:38 +00:00
|
|
|
function getNewVersion (dryRun) {
|
2017-10-23 15:02:50 +00:00
|
|
|
console.log(`Bumping for new "${versionType}" version.`)
|
|
|
|
let bumpScript = path.join(__dirname, 'bump-version.py')
|
|
|
|
let scriptArgs = [bumpScript, `--bump ${versionType}`]
|
|
|
|
if (args.stable) {
|
|
|
|
scriptArgs.push('--stable')
|
|
|
|
}
|
2018-01-31 23:40:38 +00:00
|
|
|
if (dryRun) {
|
|
|
|
scriptArgs.push('--dry-run')
|
|
|
|
}
|
2017-10-23 15:02:50 +00:00
|
|
|
try {
|
|
|
|
let bumpVersion = execSync(scriptArgs.join(' '), {encoding: 'UTF-8'})
|
|
|
|
bumpVersion = bumpVersion.substr(bumpVersion.indexOf(':') + 1).trim()
|
|
|
|
let newVersion = `v${bumpVersion}`
|
2018-01-31 23:40:38 +00:00
|
|
|
if (!dryRun) {
|
|
|
|
console.log(`${pass} Successfully bumped version to ${newVersion}`)
|
|
|
|
}
|
2017-10-23 15:02:50 +00:00
|
|
|
return newVersion
|
|
|
|
} catch (err) {
|
|
|
|
console.log(`${fail} Could not bump version, error was:`, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getCurrentBranch (gitDir) {
|
|
|
|
console.log(`Determining current git branch`)
|
|
|
|
let gitArgs = ['rev-parse', '--abbrev-ref', 'HEAD']
|
|
|
|
let branchDetails = await GitProcess.exec(gitArgs, gitDir)
|
|
|
|
if (branchDetails.exitCode === 0) {
|
|
|
|
let currentBranch = branchDetails.stdout.trim()
|
|
|
|
console.log(`${pass} Successfully determined current git branch is ` +
|
|
|
|
`${currentBranch}`)
|
|
|
|
return currentBranch
|
|
|
|
} else {
|
|
|
|
let error = GitProcess.parseError(branchDetails.stderr)
|
|
|
|
console.log(`${fail} Could not get details for the current branch,
|
|
|
|
error was ${branchDetails.stderr}`, error)
|
|
|
|
process.exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getReleaseNotes (currentBranch) {
|
|
|
|
console.log(`Generating release notes for ${currentBranch}.`)
|
|
|
|
let githubOpts = {
|
|
|
|
owner: 'electron',
|
|
|
|
repo: 'electron',
|
2018-06-20 16:52:49 +00:00
|
|
|
base: `2a97e48465a3af9e5448a8d06c1d65d16c516e8f`,
|
2017-10-23 15:02:50 +00:00
|
|
|
head: currentBranch
|
|
|
|
}
|
2018-05-14 21:21:51 +00:00
|
|
|
let releaseNotes
|
|
|
|
if (args.automaticRelease) {
|
|
|
|
releaseNotes = '## Bug Fixes/Changes \n\n'
|
|
|
|
} else {
|
|
|
|
releaseNotes = '(placeholder)\n'
|
|
|
|
}
|
2017-10-23 15:02:50 +00:00
|
|
|
console.log(`Checking for commits from ${pkg.version} to ${currentBranch}`)
|
|
|
|
let commitComparison = await github.repos.compareCommits(githubOpts)
|
|
|
|
.catch(err => {
|
2018-02-12 16:01:50 +00:00
|
|
|
console.log(`${fail} Error checking for commits from ${pkg.version} to ` +
|
2017-10-23 15:02:50 +00:00
|
|
|
`${currentBranch}`, err)
|
|
|
|
process.exit(1)
|
|
|
|
})
|
|
|
|
|
2018-05-14 21:21:51 +00:00
|
|
|
if (commitComparison.data.commits.length === 0) {
|
|
|
|
console.log(`${pass} There are no commits from ${pkg.version} to ` +
|
|
|
|
`${currentBranch}, skipping release.`)
|
|
|
|
process.exit(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
let prCount = 0
|
|
|
|
const mergeRE = /Merge pull request #(\d+) from .*\n/
|
|
|
|
const newlineRE = /(.*)\n*.*/
|
|
|
|
const prRE = /(.* )\(#(\d+)\)(?:.*)/
|
2017-10-23 15:02:50 +00:00
|
|
|
commitComparison.data.commits.forEach(commitEntry => {
|
|
|
|
let commitMessage = commitEntry.commit.message
|
2018-05-14 21:21:51 +00:00
|
|
|
if (commitMessage.indexOf('#') > -1) {
|
|
|
|
let prMatch = commitMessage.match(mergeRE)
|
|
|
|
let prNumber
|
|
|
|
if (prMatch) {
|
|
|
|
commitMessage = commitMessage.replace(mergeRE, '').replace('\n', '')
|
|
|
|
let newlineMatch = commitMessage.match(newlineRE)
|
|
|
|
if (newlineMatch) {
|
|
|
|
commitMessage = newlineMatch[1]
|
|
|
|
}
|
|
|
|
prNumber = prMatch[1]
|
|
|
|
} else {
|
|
|
|
prMatch = commitMessage.match(prRE)
|
|
|
|
if (prMatch) {
|
|
|
|
commitMessage = prMatch[1].trim()
|
|
|
|
prNumber = prMatch[2]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (prMatch) {
|
|
|
|
if (commitMessage.substr(commitMessage.length - 1, commitMessage.length) !== '.') {
|
|
|
|
commitMessage += '.'
|
|
|
|
}
|
|
|
|
releaseNotes += `* ${commitMessage} #${prNumber} \n\n`
|
|
|
|
prCount++
|
|
|
|
}
|
2017-10-23 15:02:50 +00:00
|
|
|
}
|
|
|
|
})
|
2018-05-14 21:21:51 +00:00
|
|
|
console.log(`${pass} Done generating release notes for ${currentBranch}. Found ${prCount} PRs.`)
|
2017-10-23 15:02:50 +00:00
|
|
|
return releaseNotes
|
|
|
|
}
|
|
|
|
|
|
|
|
async function createRelease (branchToTarget, isBeta) {
|
|
|
|
let releaseNotes = await getReleaseNotes(branchToTarget)
|
|
|
|
let newVersion = getNewVersion()
|
2018-02-12 16:01:50 +00:00
|
|
|
await tagRelease(newVersion)
|
2017-10-23 15:02:50 +00:00
|
|
|
const githubOpts = {
|
|
|
|
owner: 'electron',
|
|
|
|
repo: 'electron'
|
|
|
|
}
|
|
|
|
console.log(`Checking for existing draft release.`)
|
|
|
|
let releases = await github.repos.getReleases(githubOpts)
|
|
|
|
.catch(err => {
|
|
|
|
console.log('$fail} Could not get releases. Error was', err)
|
|
|
|
})
|
2018-01-31 00:35:16 +00:00
|
|
|
let drafts = releases.data.filter(release => release.draft &&
|
|
|
|
release.tag_name === newVersion)
|
2017-10-23 15:02:50 +00:00
|
|
|
if (drafts.length > 0) {
|
|
|
|
console.log(`${fail} Aborting because draft release for
|
2017-11-06 21:11:34 +00:00
|
|
|
${drafts[0].tag_name} already exists.`)
|
2017-10-23 15:02:50 +00:00
|
|
|
process.exit(1)
|
|
|
|
}
|
|
|
|
console.log(`${pass} A draft release does not exist; creating one.`)
|
|
|
|
githubOpts.draft = true
|
|
|
|
githubOpts.name = `electron ${newVersion}`
|
|
|
|
if (isBeta) {
|
|
|
|
githubOpts.body = `Note: This is a beta release. Please file new issues ` +
|
|
|
|
`for any bugs you find in it.\n \n This release is published to npm ` +
|
|
|
|
`under the beta tag and can be installed via npm install electron@beta, ` +
|
2018-01-31 23:40:38 +00:00
|
|
|
`or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes}`
|
2017-10-23 15:02:50 +00:00
|
|
|
githubOpts.name = `${githubOpts.name}`
|
|
|
|
githubOpts.prerelease = true
|
2018-01-31 23:40:38 +00:00
|
|
|
} else {
|
|
|
|
githubOpts.body = releaseNotes
|
2017-10-23 15:02:50 +00:00
|
|
|
}
|
|
|
|
githubOpts.tag_name = newVersion
|
|
|
|
githubOpts.target_commitish = branchToTarget
|
|
|
|
await github.repos.createRelease(githubOpts)
|
|
|
|
.catch(err => {
|
|
|
|
console.log(`${fail} Error creating new release: `, err)
|
|
|
|
process.exit(1)
|
|
|
|
})
|
|
|
|
console.log(`${pass} Draft release for ${newVersion} has been created.`)
|
|
|
|
}
|
|
|
|
|
2018-05-14 21:21:51 +00:00
|
|
|
async function pushRelease (branch) {
|
|
|
|
let pushDetails = await GitProcess.exec(['push', 'origin', `HEAD:${branch}`, '--follow-tags'], gitDir)
|
2017-10-23 15:02:50 +00:00
|
|
|
if (pushDetails.exitCode === 0) {
|
2018-02-12 16:01:50 +00:00
|
|
|
console.log(`${pass} Successfully pushed the release. Wait for ` +
|
2017-10-23 15:02:50 +00:00
|
|
|
`release builds to finish before running "npm run release".`)
|
|
|
|
} else {
|
2018-02-12 16:01:50 +00:00
|
|
|
console.log(`${fail} Error pushing the release: ` +
|
2017-10-23 15:02:50 +00:00
|
|
|
`${pushDetails.stderr}`)
|
|
|
|
process.exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-12 16:01:50 +00:00
|
|
|
async function runReleaseBuilds (branch) {
|
|
|
|
await ciReleaseBuild(branch, {
|
2018-05-14 21:21:51 +00:00
|
|
|
ghRelease: true,
|
|
|
|
automaticRelease: args.automaticRelease
|
2017-11-17 20:13:30 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-02-12 16:01:50 +00:00
|
|
|
async function tagRelease (version) {
|
|
|
|
console.log(`Tagging release ${version}.`)
|
|
|
|
let checkoutDetails = await GitProcess.exec([ 'tag', '-a', '-m', version, version ], gitDir)
|
|
|
|
if (checkoutDetails.exitCode === 0) {
|
|
|
|
console.log(`${pass} Successfully tagged ${version}.`)
|
|
|
|
} else {
|
|
|
|
console.log(`${fail} Error tagging ${version}: ` +
|
|
|
|
`${checkoutDetails.stderr}`)
|
|
|
|
process.exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-31 23:40:38 +00:00
|
|
|
async function verifyNewVersion () {
|
2018-02-12 16:01:50 +00:00
|
|
|
let newVersion = getNewVersion(true)
|
2018-05-14 21:21:51 +00:00
|
|
|
let response
|
|
|
|
if (args.automaticRelease) {
|
|
|
|
response = 'y'
|
|
|
|
} else {
|
|
|
|
response = await promptForVersion(newVersion)
|
|
|
|
}
|
2018-01-31 23:40:38 +00:00
|
|
|
if (response.match(/^y/i)) {
|
|
|
|
console.log(`${pass} Starting release of ${newVersion}`)
|
|
|
|
} else {
|
|
|
|
console.log(`${fail} Aborting release of ${newVersion}`)
|
|
|
|
process.exit()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function promptForVersion (version) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const rl = readline.createInterface({
|
|
|
|
input: process.stdin,
|
|
|
|
output: process.stdout
|
|
|
|
})
|
|
|
|
rl.question(`Do you want to create the release ${version.green} (y/N)? `, (answer) => {
|
|
|
|
rl.close()
|
|
|
|
resolve(answer)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-10-23 15:02:50 +00:00
|
|
|
async function prepareRelease (isBeta, notesOnly) {
|
2018-05-14 21:21:51 +00:00
|
|
|
if (args.automaticRelease && (pkg.version.indexOf('beta') === -1 ||
|
|
|
|
versionType !== 'beta')) {
|
|
|
|
console.log(`${fail} Automatic release is only supported for beta releases`)
|
|
|
|
process.exit(1)
|
|
|
|
}
|
|
|
|
let currentBranch
|
|
|
|
if (args.branch) {
|
|
|
|
currentBranch = args.branch
|
|
|
|
} else {
|
|
|
|
currentBranch = await getCurrentBranch(gitDir)
|
|
|
|
}
|
2017-10-23 15:02:50 +00:00
|
|
|
if (notesOnly) {
|
|
|
|
let releaseNotes = await getReleaseNotes(currentBranch)
|
2018-05-14 21:21:51 +00:00
|
|
|
console.log(`Draft release notes are: \n${releaseNotes}`)
|
2017-10-23 15:02:50 +00:00
|
|
|
} else {
|
2018-01-31 23:40:38 +00:00
|
|
|
await verifyNewVersion()
|
2017-10-23 15:02:50 +00:00
|
|
|
await createRelease(currentBranch, isBeta)
|
2018-05-14 21:21:51 +00:00
|
|
|
await pushRelease(currentBranch)
|
2018-02-12 16:01:50 +00:00
|
|
|
await runReleaseBuilds(currentBranch)
|
2017-10-23 15:02:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
prepareRelease(!args.stable, args.notesOnly)
|