mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
chore: backport release script changes for sudowoodo
This commit is contained in:
@@ -5,12 +5,12 @@ import re
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
from lib.util import execute, get_electron_version, parse_version, scoped_cwd
|
||||
|
||||
from lib.util import execute, get_electron_version, parse_version, scoped_cwd, \
|
||||
is_nightly, is_beta, is_stable, get_next_nightly, get_next_beta, \
|
||||
get_next_stable_from_pre, get_next_stable_from_stable, clean_parse_version
|
||||
|
||||
SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
@@ -34,14 +34,7 @@ def main():
|
||||
action='store',
|
||||
default=None,
|
||||
dest='bump',
|
||||
help='increment [major | minor | patch | beta]'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--stable',
|
||||
action='store_true',
|
||||
default= False,
|
||||
dest='stable',
|
||||
help='promote to stable (i.e. remove `-beta.x` suffix)'
|
||||
help='increment [stable | beta | nightly]'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
@@ -52,36 +45,56 @@ def main():
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
curr_version = get_electron_version()
|
||||
|
||||
if args.bump not in ['stable', 'beta', 'nightly']:
|
||||
raise Exception('bump must be set to either stable, beta or nightly')
|
||||
|
||||
if is_nightly(curr_version):
|
||||
if args.bump == 'nightly':
|
||||
version = get_next_nightly(curr_version)
|
||||
elif args.bump == 'beta':
|
||||
version = get_next_beta(curr_version)
|
||||
elif args.bump == 'stable':
|
||||
version = get_next_stable_from_pre(curr_version)
|
||||
else:
|
||||
not_reached()
|
||||
elif is_beta(curr_version):
|
||||
if args.bump == 'nightly':
|
||||
version = get_next_nightly(curr_version)
|
||||
elif args.bump == 'beta':
|
||||
version = get_next_beta(curr_version)
|
||||
elif args.bump == 'stable':
|
||||
version = get_next_stable_from_pre(curr_version)
|
||||
else:
|
||||
not_reached()
|
||||
elif is_stable(curr_version):
|
||||
if args.bump == 'nightly':
|
||||
version = get_next_nightly(curr_version)
|
||||
elif args.bump == 'beta':
|
||||
raise Exception("You can\'t bump to a beta from stable")
|
||||
elif args.bump == 'stable':
|
||||
version = get_next_stable_from_stable(curr_version)
|
||||
else:
|
||||
not_reached()
|
||||
else:
|
||||
raise Exception("Invalid current version: " + curr_version)
|
||||
|
||||
if args.new_version == None and args.bump == None and args.stable == False:
|
||||
parser.print_help()
|
||||
return 1
|
||||
|
||||
increments = ['major', 'minor', 'patch', 'beta']
|
||||
|
||||
curr_version = get_electron_version()
|
||||
versions = parse_version(re.sub('-beta', '', curr_version))
|
||||
|
||||
if args.bump in increments:
|
||||
versions = increase_version(versions, increments.index(args.bump))
|
||||
if versions[3] == '0':
|
||||
# beta starts at 1
|
||||
versions = increase_version(versions, increments.index('beta'))
|
||||
|
||||
if args.stable == True:
|
||||
versions[3] = '0'
|
||||
|
||||
if args.new_version != None:
|
||||
versions = parse_version(re.sub('-beta', '', args.new_version))
|
||||
|
||||
version = '.'.join(versions[:3])
|
||||
suffix = '' if versions[3] == '0' else '-beta.' + versions[3]
|
||||
versions = clean_parse_version(version)
|
||||
suffix = ''
|
||||
if '-' in version:
|
||||
suffix = '-' + version.split('-')[1]
|
||||
versions[3] = parse_version(version)[3]
|
||||
version = version.split('-')[0]
|
||||
|
||||
if args.dry_run:
|
||||
print 'new version number would be: {0}\n'.format(version + suffix)
|
||||
return 0
|
||||
|
||||
|
||||
with scoped_cwd(SOURCE_ROOT):
|
||||
update_electron_gyp(version, suffix)
|
||||
update_win_rc(version, versions)
|
||||
@@ -92,6 +105,9 @@ def main():
|
||||
|
||||
print 'Bumped to version: {0}'.format(version + suffix)
|
||||
|
||||
def not_reached():
|
||||
raise Exception('Unreachable code was reached')
|
||||
|
||||
def increase_version(versions, index):
|
||||
for i in range(index + 1, 4):
|
||||
versions[i] = '0'
|
||||
@@ -100,7 +116,8 @@ def increase_version(versions, index):
|
||||
|
||||
|
||||
def update_electron_gyp(version, suffix):
|
||||
pattern = re.compile(" *'version%' *: *'[0-9.]+(-beta[0-9.]*)?'")
|
||||
pattern = re.compile(" *'version%' *: *'[0-9.]+(-beta[0-9.]*)?(-dev)?"
|
||||
+ "(-nightly[0-9.]*)?'")
|
||||
with open('electron.gyp', 'r') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
@@ -192,7 +209,14 @@ def update_package_json(version, suffix):
|
||||
|
||||
|
||||
def tag_version(version, suffix):
|
||||
execute(['git', 'commit', '-a', '-m', 'Bump v{0}'.format(version + suffix)])
|
||||
execute([
|
||||
'git',
|
||||
'commit',
|
||||
'-a',
|
||||
'-m',
|
||||
'Bump v{0}'.format(version + suffix),
|
||||
'-n'
|
||||
])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
require('dotenv-safe').load()
|
||||
|
||||
const assert = require('assert')
|
||||
const request = require('request')
|
||||
const buildAppVeyorURL = 'https://windows-ci.electronjs.org/api/builds'
|
||||
const vstsURL = 'https://github.visualstudio.com/electron/_apis/build'
|
||||
|
||||
const appVeyorJobs = {
|
||||
'electron-x64': 'electron',
|
||||
'electron-ia32': 'electron-39ng6'
|
||||
}
|
||||
|
||||
const circleCIJobs = [
|
||||
'electron-linux-arm',
|
||||
@@ -10,6 +18,11 @@ const circleCIJobs = [
|
||||
'electron-linux-x64'
|
||||
]
|
||||
|
||||
const vstsJobs = [
|
||||
'electron-release-mas-x64',
|
||||
'electron-release-osx-x64'
|
||||
]
|
||||
|
||||
async function makeRequest (requestOptions, parseResponse) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(requestOptions, (err, res, body) => {
|
||||
@@ -21,8 +34,13 @@ async function makeRequest (requestOptions, parseResponse) {
|
||||
resolve(body)
|
||||
}
|
||||
} else {
|
||||
console.error('Error occurred while requesting:', requestOptions.url)
|
||||
if (parseResponse) {
|
||||
console.log('Error: ', `(status ${res.statusCode})`, err || JSON.parse(res.body), requestOptions)
|
||||
try {
|
||||
console.log('Error: ', `(status ${res.statusCode})`, err || JSON.parse(res.body), requestOptions)
|
||||
} catch (err) {
|
||||
console.log('Error: ', `(status ${res.statusCode})`, err || res.body, requestOptions)
|
||||
}
|
||||
} else {
|
||||
console.log('Error: ', `(status ${res.statusCode})`, err || res.body, requestOptions)
|
||||
}
|
||||
@@ -32,8 +50,7 @@ async function makeRequest (requestOptions, parseResponse) {
|
||||
})
|
||||
}
|
||||
|
||||
async function circleCIcall (buildUrl, targetBranch, job, ghRelease) {
|
||||
assert(process.env.CIRCLE_TOKEN, 'CIRCLE_TOKEN not found in environment')
|
||||
async function circleCIcall (buildUrl, targetBranch, job, options) {
|
||||
console.log(`Triggering CircleCI to run build job: ${job} on branch: ${targetBranch} with release flag.`)
|
||||
let buildRequest = {
|
||||
'build_parameters': {
|
||||
@@ -41,12 +58,16 @@ async function circleCIcall (buildUrl, targetBranch, job, ghRelease) {
|
||||
}
|
||||
}
|
||||
|
||||
if (ghRelease) {
|
||||
if (options.ghRelease) {
|
||||
buildRequest.build_parameters.ELECTRON_RELEASE = 1
|
||||
} else {
|
||||
buildRequest.build_parameters.RUN_RELEASE_BUILD = 'true'
|
||||
}
|
||||
|
||||
if (options.automaticRelease) {
|
||||
buildRequest.build_parameters.AUTO_RELEASE = 'true'
|
||||
}
|
||||
|
||||
let circleResponse = await makeRequest({
|
||||
method: 'POST',
|
||||
url: buildUrl,
|
||||
@@ -58,20 +79,33 @@ async function circleCIcall (buildUrl, targetBranch, job, ghRelease) {
|
||||
}, true).catch(err => {
|
||||
console.log('Error calling CircleCI:', err)
|
||||
})
|
||||
console.log(`Check ${circleResponse.build_url} for status. (${job})`)
|
||||
console.log(`CircleCI release build request for ${job} successful. Check ${circleResponse.build_url} for status.`)
|
||||
}
|
||||
|
||||
async function buildAppVeyor (targetBranch, ghRelease) {
|
||||
console.log(`Triggering AppVeyor to run build on branch: ${targetBranch} with release flag.`)
|
||||
assert(process.env.APPVEYOR_TOKEN, 'APPVEYOR_TOKEN not found in environment')
|
||||
function buildAppVeyor (targetBranch, options) {
|
||||
const validJobs = Object.keys(appVeyorJobs)
|
||||
if (options.job) {
|
||||
assert(validJobs.includes(options.job), `Unknown AppVeyor CI job name: ${options.job}. Valid values are: ${validJobs}.`)
|
||||
callAppVeyor(targetBranch, options.job, options)
|
||||
} else {
|
||||
validJobs.forEach((job) => callAppVeyor(targetBranch, job, options))
|
||||
}
|
||||
}
|
||||
|
||||
async function callAppVeyor (targetBranch, job, options) {
|
||||
console.log(`Triggering AppVeyor to run build job: ${job} on branch: ${targetBranch} with release flag.`)
|
||||
let environmentVariables = {}
|
||||
|
||||
if (ghRelease) {
|
||||
if (options.ghRelease) {
|
||||
environmentVariables.ELECTRON_RELEASE = 1
|
||||
} else {
|
||||
environmentVariables.RUN_RELEASE_BUILD = 'true'
|
||||
}
|
||||
|
||||
if (options.automaticRelease) {
|
||||
environmentVariables.AUTO_RELEASE = 'true'
|
||||
}
|
||||
|
||||
const requestOpts = {
|
||||
url: buildAppVeyorURL,
|
||||
auth: {
|
||||
@@ -82,7 +116,7 @@ async function buildAppVeyor (targetBranch, ghRelease) {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
accountName: 'AppVeyor',
|
||||
projectSlug: 'electron',
|
||||
projectSlug: appVeyorJobs[job],
|
||||
branch: targetBranch,
|
||||
environmentVariables
|
||||
}),
|
||||
@@ -91,46 +125,120 @@ async function buildAppVeyor (targetBranch, ghRelease) {
|
||||
let appVeyorResponse = await makeRequest(requestOpts, true).catch(err => {
|
||||
console.log('Error calling AppVeyor:', err)
|
||||
})
|
||||
const buildUrl = `https://windows-ci.electronjs.org/project/AppVeyor/electron/build/${appVeyorResponse.version}`
|
||||
console.log(`AppVeyor release build request successful. Check build status at ${buildUrl}`)
|
||||
const buildUrl = `https://windows-ci.electronjs.org/project/AppVeyor/${appVeyorJobs[job]}/build/${appVeyorResponse.version}`
|
||||
console.log(`AppVeyor release build request for ${job} successful. Check build status at ${buildUrl}`)
|
||||
}
|
||||
|
||||
function buildCircleCI (targetBranch, ghRelease, job) {
|
||||
function buildCircleCI (targetBranch, options) {
|
||||
const circleBuildUrl = `https://circleci.com/api/v1.1/project/github/electron/electron/tree/${targetBranch}?circle-token=${process.env.CIRCLE_TOKEN}`
|
||||
if (job) {
|
||||
assert(circleCIJobs.includes(job), `Unknown CI job name: ${job}.`)
|
||||
circleCIcall(circleBuildUrl, targetBranch, job, ghRelease)
|
||||
if (options.job) {
|
||||
assert(circleCIJobs.includes(options.job), `Unknown CircleCI job name: ${options.job}. Valid values are: ${circleCIJobs}.`)
|
||||
circleCIcall(circleBuildUrl, targetBranch, options.job, options)
|
||||
} else {
|
||||
circleCIJobs.forEach((job) => circleCIcall(circleBuildUrl, targetBranch, job, ghRelease))
|
||||
circleCIJobs.forEach((job) => circleCIcall(circleBuildUrl, targetBranch, job, options))
|
||||
}
|
||||
}
|
||||
|
||||
async function buildVSTS (targetBranch, options) {
|
||||
if (options.job) {
|
||||
assert(vstsJobs.includes(options.job), `Unknown VSTS CI job name: ${options.job}. Valid values are: ${vstsJobs}.`)
|
||||
}
|
||||
console.log(`Triggering VSTS to run build on branch: ${targetBranch} with release flag.`)
|
||||
let environmentVariables = {}
|
||||
|
||||
if (!options.ghRelease) {
|
||||
environmentVariables.UPLOAD_TO_S3 = 1
|
||||
}
|
||||
|
||||
if (options.automaticRelease) {
|
||||
environmentVariables.AUTO_RELEASE = 'true'
|
||||
}
|
||||
|
||||
let requestOpts = {
|
||||
url: `${vstsURL}/definitions?api-version=4.1`,
|
||||
auth: {
|
||||
user: '',
|
||||
password: process.env.VSTS_TOKEN
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
let vstsResponse = await makeRequest(requestOpts, true).catch(err => {
|
||||
console.log('Error calling VSTS to get build definitions:', err)
|
||||
})
|
||||
let buildsToRun = []
|
||||
if (options.job) {
|
||||
buildsToRun = vstsResponse.value.filter(build => build.name === options.job)
|
||||
} else {
|
||||
buildsToRun = vstsResponse.value.filter(build => vstsJobs.includes(build.name))
|
||||
}
|
||||
buildsToRun.forEach((build) => callVSTSBuild(build, targetBranch, environmentVariables))
|
||||
}
|
||||
|
||||
async function callVSTSBuild (build, targetBranch, environmentVariables) {
|
||||
let buildBody = {
|
||||
definition: build,
|
||||
sourceBranch: targetBranch
|
||||
}
|
||||
if (Object.keys(environmentVariables).length !== 0) {
|
||||
buildBody.parameters = JSON.stringify(environmentVariables)
|
||||
}
|
||||
let requestOpts = {
|
||||
url: `${vstsURL}/builds?api-version=4.1`,
|
||||
auth: {
|
||||
user: '',
|
||||
password: process.env.VSTS_TOKEN
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(buildBody),
|
||||
method: 'POST'
|
||||
}
|
||||
let vstsResponse = await makeRequest(requestOpts, true).catch(err => {
|
||||
console.log(`Error calling VSTS for job ${build.name}`, err)
|
||||
})
|
||||
console.log(`VSTS release build request for ${build.name} successful. Check ${vstsResponse._links.web.href} for status.`)
|
||||
}
|
||||
|
||||
function runRelease (targetBranch, options) {
|
||||
if (options.ci) {
|
||||
switch (options.ci) {
|
||||
case 'CircleCI': {
|
||||
buildCircleCI(targetBranch, options.ghRelease, options.job)
|
||||
buildCircleCI(targetBranch, options)
|
||||
break
|
||||
}
|
||||
case 'AppVeyor': {
|
||||
buildAppVeyor(targetBranch, options.ghRelease)
|
||||
buildAppVeyor(targetBranch, options)
|
||||
break
|
||||
}
|
||||
case 'VSTS': {
|
||||
buildVSTS(targetBranch, options)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
console.log(`Error! Unknown CI: ${options.ci}.`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buildCircleCI(targetBranch, options.ghRelease, options.job)
|
||||
buildAppVeyor(targetBranch, options.ghRelease)
|
||||
buildCircleCI(targetBranch, options)
|
||||
buildAppVeyor(targetBranch, options)
|
||||
buildVSTS(targetBranch, options)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = runRelease
|
||||
|
||||
if (require.main === module) {
|
||||
const args = require('minimist')(process.argv.slice(2), { boolean: 'ghRelease' })
|
||||
const args = require('minimist')(process.argv.slice(2), {
|
||||
boolean: ['ghRelease', 'automaticRelease']
|
||||
})
|
||||
const targetBranch = args._[0]
|
||||
if (args._.length < 1) {
|
||||
console.log(`Trigger CI to build release builds of electron.
|
||||
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--ci=CircleCI|AppVeyor] [--ghRelease] TARGET_BRANCH
|
||||
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--ci=CircleCI|AppVeyor|VSTS] [--ghRelease] [--automaticRelease] TARGET_BRANCH
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
39
script/find-release.js
Normal file
39
script/find-release.js
Normal file
@@ -0,0 +1,39 @@
|
||||
if (!process.env.CI) require('dotenv-safe').load()
|
||||
|
||||
const GitHub = require('github')
|
||||
const github = new GitHub()
|
||||
|
||||
if (process.argv.length < 3) {
|
||||
console.log('Usage: find-release version')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const version = process.argv[2]
|
||||
|
||||
async function findRelease () {
|
||||
github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
|
||||
let releases = await github.repos.getReleases({
|
||||
owner: 'electron',
|
||||
repo: version.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
|
||||
})
|
||||
let targetRelease = releases.data.find(release => {
|
||||
return release.tag_name === version
|
||||
})
|
||||
let returnObject = {}
|
||||
|
||||
if (targetRelease) {
|
||||
returnObject = {
|
||||
id: targetRelease.id,
|
||||
draft: targetRelease.draft,
|
||||
exists: true
|
||||
}
|
||||
} else {
|
||||
returnObject = {
|
||||
exists: false,
|
||||
draft: false
|
||||
}
|
||||
}
|
||||
console.log(JSON.stringify(returnObject))
|
||||
}
|
||||
|
||||
findRelease()
|
||||
29
script/get-last-major-for-master.js
Normal file
29
script/get-last-major-for-master.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const { GitProcess } = require('dugite')
|
||||
const path = require('path')
|
||||
const semver = require('semver')
|
||||
const gitDir = path.resolve(__dirname, '..')
|
||||
|
||||
async function determineNextMajorForMaster () {
|
||||
let branchNames
|
||||
let result = await GitProcess.exec(['branch', '-a', '--remote', '--list', 'origin/[0-9]-[0-9]-x'], gitDir)
|
||||
if (result.exitCode === 0) {
|
||||
branchNames = result.stdout.trim().split('\n')
|
||||
const filtered = branchNames.map(b => b.replace('origin/', ''))
|
||||
return getNextReleaseBranch(filtered)
|
||||
} else {
|
||||
throw new Error('Release branches could not be fetched.')
|
||||
}
|
||||
}
|
||||
|
||||
function getNextReleaseBranch (branches) {
|
||||
const converted = branches.map(b => b.replace(/-/g, '.').replace('x', '0'))
|
||||
const next = converted.reduce((v1, v2) => {
|
||||
return semver.gt(v1, v2) ? v1 : v2
|
||||
})
|
||||
return parseInt(next.split('.')[0], 10)
|
||||
}
|
||||
|
||||
determineNextMajorForMaster().then(console.info).catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
11
script/get-version.py
Executable file
11
script/get-version.py
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
|
||||
from lib.util import get_electron_version
|
||||
|
||||
def main():
|
||||
print get_electron_version()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
REQUESTS_DIR = os.path.abspath(os.path.join(__file__, '..', '..', '..',
|
||||
'vendor', 'requests'))
|
||||
sys.path.append(os.path.join(REQUESTS_DIR, 'build', 'lib'))
|
||||
sys.path.append(os.path.join(REQUESTS_DIR, 'build', 'lib.linux-x86_64-2.7'))
|
||||
import requests
|
||||
|
||||
GITHUB_URL = 'https://api.github.com'
|
||||
GITHUB_UPLOAD_ASSET_URL = 'https://uploads.github.com'
|
||||
|
||||
class GitHub:
|
||||
def __init__(self, access_token):
|
||||
self._authorization = 'token %s' % access_token
|
||||
|
||||
pattern = '^/repos/{0}/{0}/releases/{1}/assets$'.format('[^/]+', '[0-9]+')
|
||||
self._releases_upload_api_pattern = re.compile(pattern)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return _Callable(self, '/%s' % attr)
|
||||
|
||||
def send(self, method, path, **kw):
|
||||
if not 'headers' in kw:
|
||||
kw['headers'] = dict()
|
||||
headers = kw['headers']
|
||||
headers['Authorization'] = self._authorization
|
||||
headers['Accept'] = 'application/vnd.github.manifold-preview'
|
||||
|
||||
# Switch to a different domain for the releases uploading API.
|
||||
if self._releases_upload_api_pattern.match(path):
|
||||
url = '%s%s' % (GITHUB_UPLOAD_ASSET_URL, path)
|
||||
else:
|
||||
url = '%s%s' % (GITHUB_URL, path)
|
||||
# Data are sent in JSON format.
|
||||
if 'data' in kw:
|
||||
kw['data'] = json.dumps(kw['data'])
|
||||
|
||||
r = getattr(requests, method)(url, **kw).json()
|
||||
if 'message' in r:
|
||||
raise Exception(json.dumps(r, indent=2, separators=(',', ': ')))
|
||||
return r
|
||||
|
||||
|
||||
class _Executable:
|
||||
def __init__(self, gh, method, path):
|
||||
self._gh = gh
|
||||
self._method = method
|
||||
self._path = path
|
||||
|
||||
def __call__(self, **kw):
|
||||
return self._gh.send(self._method, self._path, **kw)
|
||||
|
||||
|
||||
class _Callable(object):
|
||||
def __init__(self, gh, name):
|
||||
self._gh = gh
|
||||
self._name = name
|
||||
|
||||
def __call__(self, *args):
|
||||
if len(args) == 0:
|
||||
return self
|
||||
|
||||
name = '%s/%s' % (self._name, '/'.join([str(arg) for arg in args]))
|
||||
return _Callable(self._gh, name)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr in ['get', 'put', 'post', 'patch', 'delete']:
|
||||
return _Executable(self._gh, attr, self._name)
|
||||
|
||||
name = '%s/%s' % (self._name, attr)
|
||||
return _Callable(self._gh, name)
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import atexit
|
||||
import contextlib
|
||||
import datetime
|
||||
import errno
|
||||
import platform
|
||||
import re
|
||||
@@ -87,7 +88,7 @@ def download(text, url, path):
|
||||
downloaded_size = 0
|
||||
block_size = 128
|
||||
|
||||
ci = os.environ.get('CI') == '1'
|
||||
ci = os.environ.get('CI') is not None
|
||||
|
||||
while True:
|
||||
buf = web_file.read(block_size)
|
||||
@@ -287,3 +288,67 @@ def update_node_modules(dirname, env=None):
|
||||
pass
|
||||
else:
|
||||
execute_stdout(args, env)
|
||||
|
||||
def clean_parse_version(v):
|
||||
return parse_version(v.split("-")[0])
|
||||
|
||||
def is_stable(v):
|
||||
return len(v.split(".")) == 3
|
||||
|
||||
def is_beta(v):
|
||||
return 'beta' in v
|
||||
|
||||
def is_nightly(v):
|
||||
return 'nightly' in v
|
||||
|
||||
def get_nightly_date():
|
||||
return datetime.datetime.today().strftime('%Y%m%d')
|
||||
|
||||
def get_last_major():
|
||||
return execute(['node', 'script/get-last-major-for-master.js'])
|
||||
|
||||
def get_next_nightly(v):
|
||||
pv = clean_parse_version(v)
|
||||
major = pv[0]; minor = pv[1]; patch = pv[2]
|
||||
|
||||
if (is_stable(v)):
|
||||
patch = str(int(pv[2]) + 1)
|
||||
|
||||
if execute(['git', 'rev-parse', '--abbrev-ref', 'HEAD']) == "master":
|
||||
major = str(get_last_major() + 1)
|
||||
minor = '0'
|
||||
patch = '0'
|
||||
|
||||
pre = 'nightly.' + get_nightly_date()
|
||||
return make_version(major, minor, patch, pre)
|
||||
|
||||
def non_empty(thing):
|
||||
return thing.strip() != ''
|
||||
|
||||
def get_next_beta(v):
|
||||
pv = clean_parse_version(v)
|
||||
tag_pattern = 'v' + pv[0] + '.' + pv[1] + '.' + pv[2] + '-beta.*'
|
||||
tag_list = filter(
|
||||
non_empty,
|
||||
execute(['git', 'tag', '--list', '-l', tag_pattern]).strip().split('\n')
|
||||
)
|
||||
if len(tag_list) == 0:
|
||||
return make_version(pv[0] , pv[1], pv[2], 'beta.1')
|
||||
|
||||
lv = parse_version(tag_list[-1])
|
||||
return make_version(pv[0] , pv[1], pv[2], 'beta.' + str(int(lv[3]) + 1))
|
||||
|
||||
def get_next_stable_from_pre(v):
|
||||
pv = clean_parse_version(v)
|
||||
major = pv[0]; minor = pv[1]; patch = pv[2]
|
||||
return make_version(major, minor, patch)
|
||||
|
||||
def get_next_stable_from_stable(v):
|
||||
pv = clean_parse_version(v)
|
||||
major = pv[0]; minor = pv[1]; patch = pv[2]
|
||||
return make_version(major, minor, str(int(patch) + 1))
|
||||
|
||||
def make_version(major, minor, patch, pre = None):
|
||||
if pre is None:
|
||||
return major + '.' + minor + '.' + patch
|
||||
return major + "." + minor + "." + patch + '-' + pre
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require('colors')
|
||||
const assert = require('assert')
|
||||
const branchToRelease = process.argv[2]
|
||||
const fail = '\u2717'.red
|
||||
const { GitProcess, GitError } = require('dugite')
|
||||
const pass = '\u2713'.green
|
||||
const path = require('path')
|
||||
const pkg = require('../package.json')
|
||||
|
||||
assert(process.env.ELECTRON_GITHUB_TOKEN, 'ELECTRON_GITHUB_TOKEN not found in environment')
|
||||
if (!branchToRelease) {
|
||||
console.log(`Usage: merge-release branch`)
|
||||
process.exit(1)
|
||||
}
|
||||
const gitDir = path.resolve(__dirname, '..')
|
||||
|
||||
async function callGit (args, errorMessage, successMessage) {
|
||||
let gitResult = await GitProcess.exec(args, gitDir)
|
||||
if (gitResult.exitCode === 0) {
|
||||
console.log(`${pass} ${successMessage}`)
|
||||
return true
|
||||
} else {
|
||||
console.log(`${fail} ${errorMessage} ${gitResult.stderr}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
async function checkoutBranch (branchName) {
|
||||
console.log(`Checking out ${branchName}.`)
|
||||
let errorMessage = `Error checking out branch ${branchName}:`
|
||||
let successMessage = `Successfully checked out branch ${branchName}.`
|
||||
return await callGit(['checkout', branchName], errorMessage, successMessage)
|
||||
}
|
||||
|
||||
async function commitMerge () {
|
||||
console.log(`Committing the merge for v${pkg.version}`)
|
||||
let errorMessage = `Error committing merge:`
|
||||
let successMessage = `Successfully committed the merge for v${pkg.version}`
|
||||
let gitArgs = ['commit', '-m', `v${pkg.version}`]
|
||||
return await callGit(gitArgs, errorMessage, successMessage)
|
||||
}
|
||||
|
||||
async function mergeReleaseIntoBranch (branchName) {
|
||||
console.log(`Merging release branch into ${branchName}.`)
|
||||
let mergeArgs = ['merge', 'release', '--squash']
|
||||
let mergeDetails = await GitProcess.exec(mergeArgs, gitDir)
|
||||
if (mergeDetails.exitCode === 0) {
|
||||
return true
|
||||
} else {
|
||||
const error = GitProcess.parseError(mergeDetails.stderr)
|
||||
if (error === GitError.MergeConflicts) {
|
||||
console.log(`${fail} Could not merge release branch into ${branchName} ` +
|
||||
`due to merge conflicts.`)
|
||||
return false
|
||||
} else {
|
||||
console.log(`${fail} Could not merge release branch into ${branchName} ` +
|
||||
`due to an error: ${mergeDetails.stderr}.`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function pushBranch (branchName) {
|
||||
console.log(`Pushing branch ${branchName}.`)
|
||||
let pushArgs = ['push', 'origin', branchName]
|
||||
let errorMessage = `Could not push branch ${branchName} due to an error:`
|
||||
let successMessage = `Successfully pushed branch ${branchName}.`
|
||||
return await callGit(pushArgs, errorMessage, successMessage)
|
||||
}
|
||||
|
||||
async function pull () {
|
||||
console.log(`Performing a git pull`)
|
||||
let errorMessage = `Could not pull due to an error:`
|
||||
let successMessage = `Successfully performed a git pull`
|
||||
return await callGit(['pull'], errorMessage, successMessage)
|
||||
}
|
||||
|
||||
async function rebase (targetBranch) {
|
||||
console.log(`Rebasing release branch from ${targetBranch}`)
|
||||
let errorMessage = `Could not rebase due to an error:`
|
||||
let successMessage = `Successfully rebased release branch from ` +
|
||||
`${targetBranch}`
|
||||
return await callGit(['rebase', targetBranch], errorMessage, successMessage)
|
||||
}
|
||||
|
||||
async function mergeRelease () {
|
||||
await checkoutBranch(branchToRelease)
|
||||
let mergeSuccess = await mergeReleaseIntoBranch(branchToRelease)
|
||||
if (mergeSuccess) {
|
||||
console.log(`${pass} Successfully merged release branch into ` +
|
||||
`${branchToRelease}.`)
|
||||
await commitMerge()
|
||||
let pushSuccess = await pushBranch(branchToRelease)
|
||||
if (pushSuccess) {
|
||||
console.log(`${pass} Success!!! ${branchToRelease} now has the latest release!`)
|
||||
}
|
||||
} else {
|
||||
console.log(`Trying rebase of ${branchToRelease} into release branch.`)
|
||||
await pull()
|
||||
await checkoutBranch('release')
|
||||
let rebaseResult = await rebase(branchToRelease)
|
||||
if (rebaseResult) {
|
||||
let pushResult = pushBranch('HEAD')
|
||||
if (pushResult) {
|
||||
console.log(`Rebase of ${branchToRelease} into release branch was ` +
|
||||
`successful. Let release builds run and then try this step again.`)
|
||||
}
|
||||
// Exit as failure so release doesn't continue
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mergeRelease()
|
||||
@@ -1,26 +1,28 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
if (!process.env.CI) require('dotenv-safe').load()
|
||||
require('colors')
|
||||
const args = require('minimist')(process.argv.slice(2))
|
||||
const assert = require('assert')
|
||||
const args = require('minimist')(process.argv.slice(2), {
|
||||
boolean: ['automaticRelease', 'notesOnly', 'stable']
|
||||
})
|
||||
const ciReleaseBuild = require('./ci-release-build')
|
||||
const { execSync } = require('child_process')
|
||||
const fail = '\u2717'.red
|
||||
const { GitProcess, GitError } = require('dugite')
|
||||
const { GitProcess } = require('dugite')
|
||||
const GitHub = require('github')
|
||||
const pass = '\u2713'.green
|
||||
const path = require('path')
|
||||
const pkg = require('../package.json')
|
||||
const readline = require('readline')
|
||||
const versionType = args._[0]
|
||||
const targetRepo = versionType === 'nightly' ? 'nightlies' : 'electron'
|
||||
|
||||
// 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]` +
|
||||
` (--stable) (--notesOnly)`)
|
||||
console.log(`Usage: prepare-release versionType [stable | beta | nightly]` +
|
||||
` (--stable) (--notesOnly) (--automaticRelease) (--branch)`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
@@ -28,28 +30,12 @@ const github = new GitHub()
|
||||
const gitDir = path.resolve(__dirname, '..')
|
||||
github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
|
||||
|
||||
async function createReleaseBranch () {
|
||||
console.log(`Creating release branch.`)
|
||||
let checkoutDetails = await GitProcess.exec([ 'checkout', '-b', 'release' ], gitDir)
|
||||
if (checkoutDetails.exitCode === 0) {
|
||||
console.log(`${pass} Successfully created the release branch.`)
|
||||
} else {
|
||||
const error = GitProcess.parseError(checkoutDetails.stderr)
|
||||
if (error === GitError.BranchAlreadyExists) {
|
||||
console.log(`${fail} Release branch already exists, aborting prepare ` +
|
||||
`release process.`)
|
||||
} else {
|
||||
console.log(`${fail} Error creating release branch: ` +
|
||||
`${checkoutDetails.stderr}`)
|
||||
}
|
||||
process.exit(1)
|
||||
async function getNewVersion (dryRun) {
|
||||
if (!dryRun) {
|
||||
console.log(`Bumping for new "${versionType}" version.`)
|
||||
}
|
||||
}
|
||||
|
||||
function getNewVersion (dryRun) {
|
||||
console.log(`Bumping for new "${versionType}" version.`)
|
||||
let bumpScript = path.join(__dirname, 'bump-version.py')
|
||||
let scriptArgs = [bumpScript, `--bump ${versionType}`]
|
||||
let scriptArgs = [bumpScript, '--bump', versionType]
|
||||
if (args.stable) {
|
||||
scriptArgs.push('--stable')
|
||||
}
|
||||
@@ -66,6 +52,7 @@ function getNewVersion (dryRun) {
|
||||
return newVersion
|
||||
} catch (err) {
|
||||
console.log(`${fail} Could not bump version, error was:`, err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,38 +74,79 @@ async function getCurrentBranch (gitDir) {
|
||||
}
|
||||
|
||||
async function getReleaseNotes (currentBranch) {
|
||||
if (versionType === 'nightly') {
|
||||
return 'Nightlies do not get release notes, please compare tags for info'
|
||||
}
|
||||
console.log(`Generating release notes for ${currentBranch}.`)
|
||||
let githubOpts = {
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
base: `v${pkg.version}`,
|
||||
head: currentBranch
|
||||
}
|
||||
let releaseNotes = '(placeholder)\n'
|
||||
let releaseNotes
|
||||
if (args.automaticRelease) {
|
||||
releaseNotes = '## Bug Fixes/Changes \n\n'
|
||||
} else {
|
||||
releaseNotes = '(placeholder)\n'
|
||||
}
|
||||
console.log(`Checking for commits from ${pkg.version} to ${currentBranch}`)
|
||||
let commitComparison = await github.repos.compareCommits(githubOpts)
|
||||
.catch(err => {
|
||||
console.log(`{$fail} Error checking for commits from ${pkg.version} to ` +
|
||||
console.log(`${fail} Error checking for commits from ${pkg.version} to ` +
|
||||
`${currentBranch}`, err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
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+)\)(?:.*)/
|
||||
commitComparison.data.commits.forEach(commitEntry => {
|
||||
let commitMessage = commitEntry.commit.message
|
||||
if (commitMessage.toLowerCase().indexOf('merge') > -1) {
|
||||
releaseNotes += `${commitMessage} \n`
|
||||
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++
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log(`${pass} Done generating release notes for ${currentBranch}.`)
|
||||
console.log(`${pass} Done generating release notes for ${currentBranch}. Found ${prCount} PRs.`)
|
||||
return releaseNotes
|
||||
}
|
||||
|
||||
async function createRelease (branchToTarget, isBeta) {
|
||||
let releaseNotes = await getReleaseNotes(branchToTarget)
|
||||
let newVersion = getNewVersion()
|
||||
let newVersion = await getNewVersion()
|
||||
await tagRelease(newVersion)
|
||||
const githubOpts = {
|
||||
owner: 'electron',
|
||||
repo: 'electron'
|
||||
repo: targetRepo
|
||||
}
|
||||
console.log(`Checking for existing draft release.`)
|
||||
let releases = await github.repos.getReleases(githubOpts)
|
||||
@@ -136,17 +164,24 @@ async function createRelease (branchToTarget, isBeta) {
|
||||
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, ` +
|
||||
`or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes}`
|
||||
if (newVersion.indexOf('nightly') > 0) {
|
||||
githubOpts.body = `Note: This is a nightly release. Please file new issues ` +
|
||||
`for any bugs you find in it.\n \n This release is published to npm ` +
|
||||
`under the nightly tag and can be installed via npm install electron@nightly, ` +
|
||||
`or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes}`
|
||||
} else {
|
||||
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, ` +
|
||||
`or npm i electron@${newVersion.substr(1)}.\n \n ${releaseNotes}`
|
||||
}
|
||||
githubOpts.name = `${githubOpts.name}`
|
||||
githubOpts.prerelease = true
|
||||
} else {
|
||||
githubOpts.body = releaseNotes
|
||||
}
|
||||
githubOpts.tag_name = newVersion
|
||||
githubOpts.target_commitish = branchToTarget
|
||||
githubOpts.target_commitish = newVersion.indexOf('nightly') !== -1 ? 'master' : branchToTarget
|
||||
await github.repos.createRelease(githubOpts)
|
||||
.catch(err => {
|
||||
console.log(`${fail} Error creating new release: `, err)
|
||||
@@ -155,27 +190,45 @@ async function createRelease (branchToTarget, isBeta) {
|
||||
console.log(`${pass} Draft release for ${newVersion} has been created.`)
|
||||
}
|
||||
|
||||
async function pushRelease () {
|
||||
let pushDetails = await GitProcess.exec(['push', 'origin', 'HEAD'], gitDir)
|
||||
async function pushRelease (branch) {
|
||||
let pushDetails = await GitProcess.exec(['push', 'origin', `HEAD:${branch}`, '--follow-tags'], gitDir)
|
||||
if (pushDetails.exitCode === 0) {
|
||||
console.log(`${pass} Successfully pushed the release branch. Wait for ` +
|
||||
console.log(`${pass} Successfully pushed the release. Wait for ` +
|
||||
`release builds to finish before running "npm run release".`)
|
||||
} else {
|
||||
console.log(`${fail} Error pushing the release branch: ` +
|
||||
console.log(`${fail} Error pushing the release: ` +
|
||||
`${pushDetails.stderr}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
async function runReleaseBuilds () {
|
||||
await ciReleaseBuild('release', {
|
||||
ghRelease: true
|
||||
async function runReleaseBuilds (branch) {
|
||||
await ciReleaseBuild(branch, {
|
||||
ghRelease: true,
|
||||
automaticRelease: args.automaticRelease
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyNewVersion () {
|
||||
let newVersion = await getNewVersion(true)
|
||||
let response = await promptForVersion(newVersion)
|
||||
let response
|
||||
if (args.automaticRelease) {
|
||||
response = 'y'
|
||||
} else {
|
||||
response = await promptForVersion(newVersion)
|
||||
}
|
||||
if (response.match(/^y/i)) {
|
||||
console.log(`${pass} Starting release of ${newVersion}`)
|
||||
} else {
|
||||
@@ -197,17 +250,34 @@ async function promptForVersion (version) {
|
||||
})
|
||||
}
|
||||
|
||||
// function to determine if there have been commits to master since the last release
|
||||
async function changesToRelease () {
|
||||
let lastCommitWasRelease = new RegExp(`^Bump v[0-9.]*(-beta[0-9.]*)?(-nightly[0-9.]*)?$`, 'g')
|
||||
let lastCommit = await GitProcess.exec(['log', '-n', '1', `--pretty=format:'%s'`], gitDir)
|
||||
return !lastCommitWasRelease.test(lastCommit.stdout)
|
||||
}
|
||||
|
||||
async function prepareRelease (isBeta, notesOnly) {
|
||||
let currentBranch = await getCurrentBranch(gitDir)
|
||||
if (notesOnly) {
|
||||
let releaseNotes = await getReleaseNotes(currentBranch)
|
||||
console.log(`Draft release notes are: ${releaseNotes}`)
|
||||
if (args.dryRun) {
|
||||
let newVersion = await getNewVersion(true)
|
||||
console.log(newVersion)
|
||||
} else {
|
||||
await verifyNewVersion()
|
||||
await createReleaseBranch()
|
||||
await createRelease(currentBranch, isBeta)
|
||||
await pushRelease()
|
||||
await runReleaseBuilds()
|
||||
const currentBranch = (args.branch) ? args.branch : await getCurrentBranch(gitDir)
|
||||
if (notesOnly) {
|
||||
let releaseNotes = await getReleaseNotes(currentBranch)
|
||||
console.log(`Draft release notes are: \n${releaseNotes}`)
|
||||
} else {
|
||||
const changes = await changesToRelease(currentBranch)
|
||||
if (changes) {
|
||||
await verifyNewVersion()
|
||||
await createRelease(currentBranch, isBeta)
|
||||
await pushRelease(currentBranch)
|
||||
await runReleaseBuilds(currentBranch)
|
||||
} else {
|
||||
console.log(`There are no new changes to this branch since the last release, aborting release.`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,15 @@ const fs = require('fs')
|
||||
const path = require('path')
|
||||
const childProcess = require('child_process')
|
||||
const GitHubApi = require('github')
|
||||
const {GitProcess} = require('dugite')
|
||||
const request = require('request')
|
||||
const assert = require('assert')
|
||||
const rootPackageJson = require('../package.json')
|
||||
|
||||
if (!process.env.ELECTRON_NPM_OTP) {
|
||||
console.error('Please set ELECTRON_NPM_OTP')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const github = new GitHubApi({
|
||||
// debug: true,
|
||||
headers: { 'User-Agent': 'electron-npm-publisher' },
|
||||
@@ -68,7 +73,7 @@ new Promise((resolve, reject) => {
|
||||
|
||||
return github.repos.getReleases({
|
||||
owner: 'electron',
|
||||
repo: 'electron'
|
||||
repo: rootPackageJson.version.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
|
||||
})
|
||||
})
|
||||
.then((releases) => {
|
||||
@@ -103,8 +108,17 @@ new Promise((resolve, reject) => {
|
||||
})
|
||||
})
|
||||
})
|
||||
.then((release) => {
|
||||
npmTag = release.prerelease ? 'beta' : 'latest'
|
||||
.then(async (release) => {
|
||||
if (release.tag_name.indexOf('nightly') > 0) {
|
||||
const currentBranch = await getCurrentBranch()
|
||||
if (currentBranch === 'master') {
|
||||
npmTag = 'nightly'
|
||||
} else {
|
||||
npmTag = `nightly-${currentBranch}`
|
||||
}
|
||||
} else {
|
||||
npmTag = release.prerelease ? 'beta' : 'latest'
|
||||
}
|
||||
})
|
||||
.then(() => childProcess.execSync('npm pack', { cwd: tempDir }))
|
||||
.then(() => {
|
||||
@@ -115,13 +129,29 @@ new Promise((resolve, reject) => {
|
||||
env: Object.assign({}, process.env, { electron_config_cache: tempDir }),
|
||||
cwd: tempDir
|
||||
})
|
||||
const checkVersion = childProcess.execSync(`${path.join(tempDir, 'node_modules', '.bin', 'electron')} -v`)
|
||||
assert.ok((`v${rootPackageJson.version}`.indexOf(checkVersion.toString().trim()) === 0), `Version is correct`)
|
||||
resolve(tarballPath)
|
||||
})
|
||||
})
|
||||
.then((tarballPath) => childProcess.execSync(`npm publish ${tarballPath} --tag ${npmTag}`))
|
||||
.then((tarballPath) => childProcess.execSync(`npm publish ${tarballPath} --tag ${npmTag} --otp=${process.env.ELECTRON_NPM_OTP}`))
|
||||
.catch((err) => {
|
||||
console.error(`Error: ${err}`)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
async function getCurrentBranch () {
|
||||
const gitDir = path.resolve(__dirname, '..')
|
||||
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(`Successfully determined current git branch is ` +
|
||||
`${currentBranch}`)
|
||||
return currentBranch
|
||||
} else {
|
||||
let error = GitProcess.parseError(branchDetails.stderr)
|
||||
console.log(`Could not get details for the current branch,
|
||||
error was ${branchDetails.stderr}`, error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
478
script/release-notes/index.js
Normal file
478
script/release-notes/index.js
Normal file
@@ -0,0 +1,478 @@
|
||||
const { GitProcess } = require('dugite')
|
||||
const Entities = require('html-entities').AllHtmlEntities
|
||||
const fetch = require('node-fetch')
|
||||
const fs = require('fs')
|
||||
const GitHub = require('github')
|
||||
const path = require('path')
|
||||
const semver = require('semver')
|
||||
|
||||
const CACHE_DIR = path.resolve(__dirname, '.cache')
|
||||
// Fill this with tags to ignore if you are generating release notes for older
|
||||
// versions
|
||||
//
|
||||
// E.g. ['v3.0.0-beta.1'] to generate the release notes for 3.0.0-beta.1 :) from
|
||||
// the current 3-0-x branch
|
||||
const EXCLUDE_TAGS = []
|
||||
|
||||
const entities = new Entities()
|
||||
const github = new GitHub()
|
||||
const gitDir = path.resolve(__dirname, '..', '..')
|
||||
github.authenticate({ type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN })
|
||||
let currentBranch
|
||||
|
||||
const semanticMap = new Map()
|
||||
for (const line of fs.readFileSync(path.resolve(__dirname, 'legacy-pr-semantic-map.csv'), 'utf8').split('\n')) {
|
||||
if (!line) continue
|
||||
const bits = line.split(',')
|
||||
if (bits.length !== 2) continue
|
||||
semanticMap.set(bits[0], bits[1])
|
||||
}
|
||||
|
||||
const getCurrentBranch = async () => {
|
||||
if (currentBranch) return currentBranch
|
||||
const gitArgs = ['rev-parse', '--abbrev-ref', 'HEAD']
|
||||
const branchDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (branchDetails.exitCode === 0) {
|
||||
currentBranch = branchDetails.stdout.trim()
|
||||
return currentBranch
|
||||
}
|
||||
throw GitProcess.parseError(branchDetails.stderr)
|
||||
}
|
||||
|
||||
const getBranchOffPoint = async (branchName) => {
|
||||
const gitArgs = ['merge-base', branchName, 'master']
|
||||
const commitDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (commitDetails.exitCode === 0) {
|
||||
return commitDetails.stdout.trim()
|
||||
}
|
||||
throw GitProcess.parseError(commitDetails.stderr)
|
||||
}
|
||||
|
||||
const getTagsOnBranch = async (branchName) => {
|
||||
const gitArgs = ['tag', '--merged', branchName]
|
||||
const tagDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (tagDetails.exitCode === 0) {
|
||||
return tagDetails.stdout.trim().split('\n').filter(tag => !EXCLUDE_TAGS.includes(tag))
|
||||
}
|
||||
throw GitProcess.parseError(tagDetails.stderr)
|
||||
}
|
||||
|
||||
const memLastKnownRelease = new Map()
|
||||
|
||||
const getLastKnownReleaseOnBranch = async (branchName) => {
|
||||
if (memLastKnownRelease.has(branchName)) {
|
||||
return memLastKnownRelease.get(branchName)
|
||||
}
|
||||
const tags = await getTagsOnBranch(branchName)
|
||||
if (!tags.length) {
|
||||
throw new Error(`Branch ${branchName} has no tags, we have no idea what the last release was`)
|
||||
}
|
||||
const branchOffPointTags = await getTagsOnBranch(await getBranchOffPoint(branchName))
|
||||
if (branchOffPointTags.length >= tags.length) {
|
||||
// No release on this branch
|
||||
return null
|
||||
}
|
||||
memLastKnownRelease.set(branchName, tags[tags.length - 1])
|
||||
// Latest tag is the latest release
|
||||
return tags[tags.length - 1]
|
||||
}
|
||||
|
||||
const getBranches = async () => {
|
||||
const gitArgs = ['branch', '--remote']
|
||||
const branchDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (branchDetails.exitCode === 0) {
|
||||
return branchDetails.stdout.trim().split('\n').map(b => b.trim()).filter(branch => branch !== 'origin/HEAD -> origin/master')
|
||||
}
|
||||
throw GitProcess.parseError(branchDetails.stderr)
|
||||
}
|
||||
|
||||
const semverify = (v) => v.replace(/^origin\//, '').replace('x', '0').replace(/-/g, '.')
|
||||
|
||||
const getLastReleaseBranch = async () => {
|
||||
const current = await getCurrentBranch()
|
||||
const allBranches = await getBranches()
|
||||
const releaseBranches = allBranches
|
||||
.filter(branch => /^origin\/[0-9]+-[0-9]+-x$/.test(branch))
|
||||
.filter(branch => branch !== current && branch !== `origin/${current}`)
|
||||
let latest = null
|
||||
for (const b of releaseBranches) {
|
||||
if (latest === null) latest = b
|
||||
if (semver.gt(semverify(b), semverify(latest))) {
|
||||
latest = b
|
||||
}
|
||||
}
|
||||
return latest
|
||||
}
|
||||
|
||||
const commitBeforeTag = async (commit, tag) => {
|
||||
const gitArgs = ['tag', '--contains', commit]
|
||||
const tagDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (tagDetails.exitCode === 0) {
|
||||
return tagDetails.stdout.split('\n').includes(tag)
|
||||
}
|
||||
throw GitProcess.parseError(tagDetails.stderr)
|
||||
}
|
||||
|
||||
const getCommitsMergedIntoCurrentBranchSincePoint = async (point) => {
|
||||
return getCommitsBetween(point, 'HEAD')
|
||||
}
|
||||
|
||||
const getCommitsBetween = async (point1, point2) => {
|
||||
const gitArgs = ['rev-list', `${point1}..${point2}`]
|
||||
const commitsDetails = await GitProcess.exec(gitArgs, gitDir)
|
||||
if (commitsDetails.exitCode !== 0) {
|
||||
throw GitProcess.parseError(commitsDetails.stderr)
|
||||
}
|
||||
return commitsDetails.stdout.trim().split('\n')
|
||||
}
|
||||
|
||||
const TITLE_PREFIX = 'Merged Pull Request: '
|
||||
|
||||
const getCommitDetails = async (commitHash) => {
|
||||
const commitInfo = await (await fetch(`https://github.com/electron/electron/branch_commits/${commitHash}`)).text()
|
||||
const bits = commitInfo.split('</a>)')[0].split('>')
|
||||
const prIdent = bits[bits.length - 1].trim()
|
||||
if (!prIdent || commitInfo.indexOf('href="/electron/electron/pull') === -1) {
|
||||
console.warn(`WARNING: Could not track commit "${commitHash}" to a pull request, it may have been committed directly to the branch`)
|
||||
return null
|
||||
}
|
||||
const title = commitInfo.split('title="')[1].split('"')[0]
|
||||
if (!title.startsWith(TITLE_PREFIX)) {
|
||||
console.warn(`WARNING: Unknown PR title for commit "${commitHash}" in PR "${prIdent}"`)
|
||||
return null
|
||||
}
|
||||
return {
|
||||
mergedFrom: prIdent,
|
||||
prTitle: entities.decode(title.substr(TITLE_PREFIX.length))
|
||||
}
|
||||
}
|
||||
|
||||
const doWork = async (items, fn, concurrent = 5) => {
|
||||
const results = []
|
||||
const toUse = [].concat(items)
|
||||
let i = 1
|
||||
const doBit = async () => {
|
||||
if (toUse.length === 0) return
|
||||
console.log(`Running ${i}/${items.length}`)
|
||||
i += 1
|
||||
|
||||
const item = toUse.pop()
|
||||
const index = toUse.length
|
||||
results[index] = await fn(item)
|
||||
await doBit()
|
||||
}
|
||||
const bits = []
|
||||
for (let i = 0; i < concurrent; i += 1) {
|
||||
bits.push(doBit())
|
||||
}
|
||||
await Promise.all(bits)
|
||||
return results
|
||||
}
|
||||
|
||||
const notes = new Map()
|
||||
|
||||
const NoteType = {
|
||||
FIX: 'fix',
|
||||
FEATURE: 'feature',
|
||||
BREAKING_CHANGE: 'breaking-change',
|
||||
DOCUMENTATION: 'doc',
|
||||
OTHER: 'other',
|
||||
UNKNOWN: 'unknown'
|
||||
}
|
||||
|
||||
class Note {
|
||||
constructor (trueTitle, prNumber, ignoreIfInVersion) {
|
||||
// Self bindings
|
||||
this.guessType = this.guessType.bind(this)
|
||||
this.fetchPrInfo = this.fetchPrInfo.bind(this)
|
||||
this._getPr = this._getPr.bind(this)
|
||||
|
||||
if (!trueTitle.trim()) console.error(prNumber)
|
||||
|
||||
this._ignoreIfInVersion = ignoreIfInVersion
|
||||
this.reverted = false
|
||||
if (notes.has(trueTitle)) {
|
||||
console.warn(`Duplicate PR trueTitle: "${trueTitle}", "${prNumber}" this might cause weird reversions (this would be RARE)`)
|
||||
}
|
||||
|
||||
// Memoize
|
||||
notes.set(trueTitle, this)
|
||||
|
||||
this.originalTitle = trueTitle
|
||||
this.title = trueTitle
|
||||
this.prNumber = prNumber
|
||||
this.stripColon = true
|
||||
if (this.guessType() !== NoteType.UNKNOWN && this.stripColon) {
|
||||
this.title = trueTitle.split(':').slice(1).join(':').trim()
|
||||
}
|
||||
}
|
||||
|
||||
guessType () {
|
||||
if (this.originalTitle.startsWith('fix:') ||
|
||||
this.originalTitle.startsWith('Fix:')) return NoteType.FIX
|
||||
if (this.originalTitle.startsWith('feat:')) return NoteType.FEATURE
|
||||
if (this.originalTitle.startsWith('spec:') ||
|
||||
this.originalTitle.startsWith('build:') ||
|
||||
this.originalTitle.startsWith('test:') ||
|
||||
this.originalTitle.startsWith('chore:') ||
|
||||
this.originalTitle.startsWith('deps:') ||
|
||||
this.originalTitle.startsWith('refactor:') ||
|
||||
this.originalTitle.startsWith('tools:') ||
|
||||
this.originalTitle.startsWith('vendor:') ||
|
||||
this.originalTitle.startsWith('perf:') ||
|
||||
this.originalTitle.startsWith('style:') ||
|
||||
this.originalTitle.startsWith('ci')) return NoteType.OTHER
|
||||
if (this.originalTitle.startsWith('doc:') ||
|
||||
this.originalTitle.startsWith('docs:')) return NoteType.DOCUMENTATION
|
||||
|
||||
this.stripColon = false
|
||||
|
||||
if (this.pr && this.pr.data.labels.find(label => label.name === 'semver/breaking-change')) {
|
||||
return NoteType.BREAKING_CHANGE
|
||||
}
|
||||
// FIXME: Backported features will not be picked up by this
|
||||
if (this.pr && this.pr.data.labels.find(label => label.name === 'semver/nonbreaking-feature')) {
|
||||
return NoteType.FEATURE
|
||||
}
|
||||
|
||||
const n = this.prNumber.replace('#', '')
|
||||
if (semanticMap.has(n)) {
|
||||
switch (semanticMap.get(n)) {
|
||||
case 'feat':
|
||||
return NoteType.FEATURE
|
||||
case 'fix':
|
||||
return NoteType.FIX
|
||||
case 'breaking-change':
|
||||
return NoteType.BREAKING_CHANGE
|
||||
case 'doc':
|
||||
return NoteType.DOCUMENTATION
|
||||
case 'build':
|
||||
case 'vendor':
|
||||
case 'refactor':
|
||||
case 'spec':
|
||||
return NoteType.OTHER
|
||||
default:
|
||||
throw new Error(`Unknown semantic mapping: ${semanticMap.get(n)}`)
|
||||
}
|
||||
}
|
||||
return NoteType.UNKNOWN
|
||||
}
|
||||
|
||||
async _getPr (n) {
|
||||
const cachePath = path.resolve(CACHE_DIR, n)
|
||||
if (fs.existsSync(cachePath)) {
|
||||
return JSON.parse(fs.readFileSync(cachePath, 'utf8'))
|
||||
} else {
|
||||
try {
|
||||
const pr = await github.pullRequests.get({
|
||||
number: n,
|
||||
owner: 'electron',
|
||||
repo: 'electron'
|
||||
})
|
||||
fs.writeFileSync(cachePath, JSON.stringify({ data: pr.data }))
|
||||
return pr
|
||||
} catch (err) {
|
||||
console.info('#### FAILED:', `#${n}`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fetchPrInfo () {
|
||||
if (this.pr) return
|
||||
const n = this.prNumber.replace('#', '')
|
||||
this.pr = await this._getPr(n)
|
||||
if (this.pr.data.labels.find(label => label.name === `merged/${this._ignoreIfInVersion.replace('origin/', '')}`)) {
|
||||
// This means we probably backported this PR, let's try figure out what
|
||||
// the corresponding backport PR would be by searching through comments
|
||||
// for trop
|
||||
let comments
|
||||
const cacheCommentsPath = path.resolve(CACHE_DIR, `${n}-comments`)
|
||||
if (fs.existsSync(cacheCommentsPath)) {
|
||||
comments = JSON.parse(fs.readFileSync(cacheCommentsPath, 'utf8'))
|
||||
} else {
|
||||
comments = await github.issues.getComments({
|
||||
number: n,
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
per_page: 100
|
||||
})
|
||||
fs.writeFileSync(cacheCommentsPath, JSON.stringify({ data: comments.data }))
|
||||
}
|
||||
|
||||
const tropComment = comments.data.find(
|
||||
c => (
|
||||
new RegExp(`We have automatically backported this PR to "${this._ignoreIfInVersion.replace('origin/', '')}", please check out #[0-9]+`)
|
||||
).test(c.body)
|
||||
)
|
||||
|
||||
if (tropComment) {
|
||||
const commentBits = tropComment.body.split('#')
|
||||
const tropPrNumber = commentBits[commentBits.length - 1]
|
||||
|
||||
const tropPr = await this._getPr(tropPrNumber)
|
||||
if (tropPr.data.merged && tropPr.data.merge_commit_sha) {
|
||||
if (await commitBeforeTag(tropPr.data.merge_commit_sha, await getLastKnownReleaseOnBranch(this._ignoreIfInVersion))) {
|
||||
this.reverted = true
|
||||
console.log('PR', this.prNumber, 'was backported to a previous version, ignoring from notes')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Note.findByTrueTitle = (trueTitle) => notes.get(trueTitle)
|
||||
|
||||
class ReleaseNotes {
|
||||
constructor (ignoreIfInVersion) {
|
||||
this._ignoreIfInVersion = ignoreIfInVersion
|
||||
this._handledPrs = new Set()
|
||||
this._revertedPrs = new Set()
|
||||
this.other = []
|
||||
this.docs = []
|
||||
this.fixes = []
|
||||
this.features = []
|
||||
this.breakingChanges = []
|
||||
this.unknown = []
|
||||
}
|
||||
|
||||
async parseCommits (commitHashes) {
|
||||
await doWork(commitHashes, async (commit) => {
|
||||
const info = await getCommitDetails(commit)
|
||||
if (!info) return
|
||||
// Only handle each PR once
|
||||
if (this._handledPrs.has(info.mergedFrom)) return
|
||||
this._handledPrs.add(info.mergedFrom)
|
||||
|
||||
// Strip the trop backport prefix
|
||||
const trueTitle = info.prTitle.replace(/^Backport \([0-9]+-[0-9]+-x\) - /, '')
|
||||
if (this._revertedPrs.has(trueTitle)) return
|
||||
|
||||
// Handle PRs that revert other PRs
|
||||
if (trueTitle.startsWith('Revert "')) {
|
||||
const revertedTrueTitle = trueTitle.substr(8, trueTitle.length - 9)
|
||||
this._revertedPrs.add(revertedTrueTitle)
|
||||
const existingNote = Note.findByTrueTitle(revertedTrueTitle)
|
||||
if (existingNote) {
|
||||
existingNote.reverted = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Add a note for this PR
|
||||
const note = new Note(trueTitle, info.mergedFrom, this._ignoreIfInVersion)
|
||||
try {
|
||||
await note.fetchPrInfo()
|
||||
} catch (err) {
|
||||
console.error(commit, info)
|
||||
throw err
|
||||
}
|
||||
switch (note.guessType()) {
|
||||
case NoteType.FIX:
|
||||
this.fixes.push(note)
|
||||
break
|
||||
case NoteType.FEATURE:
|
||||
this.features.push(note)
|
||||
break
|
||||
case NoteType.BREAKING_CHANGE:
|
||||
this.breakingChanges.push(note)
|
||||
break
|
||||
case NoteType.OTHER:
|
||||
this.other.push(note)
|
||||
break
|
||||
case NoteType.DOCUMENTATION:
|
||||
this.docs.push(note)
|
||||
break
|
||||
case NoteType.UNKNOWN:
|
||||
default:
|
||||
this.unknown.push(note)
|
||||
break
|
||||
}
|
||||
}, 20)
|
||||
}
|
||||
|
||||
list (notes) {
|
||||
if (notes.length === 0) {
|
||||
return '_There are no items in this section this release_'
|
||||
}
|
||||
return notes
|
||||
.filter(note => !note.reverted)
|
||||
.sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase()))
|
||||
.map((note) => `* ${note.title.trim()} ${note.prNumber}`).join('\n')
|
||||
}
|
||||
|
||||
render () {
|
||||
return `
|
||||
# Release Notes
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
${this.list(this.breakingChanges)}
|
||||
|
||||
## Features
|
||||
|
||||
${this.list(this.features)}
|
||||
|
||||
## Fixes
|
||||
|
||||
${this.list(this.fixes)}
|
||||
|
||||
## Other Changes (E.g. Internal refactors or build system updates)
|
||||
|
||||
${this.list(this.other)}
|
||||
|
||||
## Documentation Updates
|
||||
|
||||
Some documentation updates, fixes and reworks: ${
|
||||
this.docs.length === 0
|
||||
? '_None in this release_'
|
||||
: this.docs.sort((a, b) => a.prNumber.localeCompare(b.prNumber)).map(note => note.prNumber).join(', ')
|
||||
}
|
||||
${this.unknown.filter(n => !n.reverted).length > 0
|
||||
? `## Unknown (fix these before publishing release)
|
||||
|
||||
${this.list(this.unknown)}
|
||||
` : ''}`
|
||||
}
|
||||
}
|
||||
|
||||
async function main () {
|
||||
if (!fs.existsSync(CACHE_DIR)) {
|
||||
fs.mkdirSync(CACHE_DIR)
|
||||
}
|
||||
const lastReleaseBranch = await getLastReleaseBranch()
|
||||
|
||||
const notes = new ReleaseNotes(lastReleaseBranch)
|
||||
const lastKnownReleaseInCurrentStream = await getLastKnownReleaseOnBranch(await getCurrentBranch())
|
||||
const currentBranchOff = await getBranchOffPoint(await getCurrentBranch())
|
||||
|
||||
const commits = await getCommitsMergedIntoCurrentBranchSincePoint(
|
||||
lastKnownReleaseInCurrentStream || currentBranchOff
|
||||
)
|
||||
|
||||
if (!lastKnownReleaseInCurrentStream) {
|
||||
// This means we are the first release in our stream
|
||||
// FIXME: This will not work for minor releases!!!!
|
||||
|
||||
const lastReleaseBranch = await getLastReleaseBranch()
|
||||
const lastBranchOff = await getBranchOffPoint(lastReleaseBranch)
|
||||
commits.push(...await getCommitsBetween(lastBranchOff, currentBranchOff))
|
||||
}
|
||||
|
||||
await notes.parseCommits(commits)
|
||||
|
||||
console.log(notes.render())
|
||||
|
||||
const badNotes = notes.unknown.filter(n => !n.reverted).length
|
||||
if (badNotes > 0) {
|
||||
throw new Error(`You have ${badNotes.length} unknown release notes, please fix them before releasing`)
|
||||
}
|
||||
}
|
||||
|
||||
if (process.mainModule === module) {
|
||||
main().catch((err) => {
|
||||
console.error('Error Occurred:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
193
script/release-notes/legacy-pr-semantic-map.csv
Normal file
193
script/release-notes/legacy-pr-semantic-map.csv
Normal file
@@ -0,0 +1,193 @@
|
||||
12884,fix
|
||||
12093,feat
|
||||
12595,doc
|
||||
12674,doc
|
||||
12577,doc
|
||||
12084,doc
|
||||
12103,doc
|
||||
12948,build
|
||||
12496,feat
|
||||
13133,build
|
||||
12651,build
|
||||
12767,doc
|
||||
12238,build
|
||||
12646,build
|
||||
12373,doc
|
||||
12723,feat
|
||||
12202,doc
|
||||
12504,doc
|
||||
12669,doc
|
||||
13044,feat
|
||||
12746,spec
|
||||
12617,doc
|
||||
12532,feat
|
||||
12619,feat
|
||||
12118,build
|
||||
12921,build
|
||||
13281,doc
|
||||
12059,feat
|
||||
12131,doc
|
||||
12123,doc
|
||||
12080,build
|
||||
12904,fix
|
||||
12562,fix
|
||||
12122,spec
|
||||
12817,spec
|
||||
12254,fix
|
||||
12999,vendor
|
||||
13248,vendor
|
||||
12104,build
|
||||
12477,feat
|
||||
12648,refactor
|
||||
12649,refactor
|
||||
12650,refactor
|
||||
12673,refactor
|
||||
12305,refactor
|
||||
12168,refactor
|
||||
12627,refactor
|
||||
12446,doc
|
||||
12304,refactor
|
||||
12615,breaking-change
|
||||
12135,feat
|
||||
12155,doc
|
||||
12975,fix
|
||||
12501,fix
|
||||
13065,fix
|
||||
13089,build
|
||||
12786,doc
|
||||
12736,doc
|
||||
11966,doc
|
||||
12885,fix
|
||||
12984,refactor
|
||||
12187,build
|
||||
12535,refactor
|
||||
12538,feat
|
||||
12190,fix
|
||||
12139,fix
|
||||
11328,fix
|
||||
12828,feat
|
||||
12614,feat
|
||||
12546,feat
|
||||
12647,refactor
|
||||
12987,build
|
||||
12900,doc
|
||||
12389,doc
|
||||
12387,doc
|
||||
12232,doc
|
||||
12742,build
|
||||
12043,fix
|
||||
12741,fix
|
||||
12995,fix
|
||||
12395,fix
|
||||
12003,build
|
||||
12216,fix
|
||||
12132,fix
|
||||
12062,fix
|
||||
12968,doc
|
||||
12422,doc
|
||||
12149,doc
|
||||
13339,build
|
||||
12044,fix
|
||||
12327,fix
|
||||
12180,fix
|
||||
12263,spec
|
||||
12153,spec
|
||||
13055,feat
|
||||
12113,doc
|
||||
12067,doc
|
||||
12882,build
|
||||
13029,build
|
||||
13067,doc
|
||||
12196,build
|
||||
12797,doc
|
||||
12013,fix
|
||||
12507,fix
|
||||
11607,feat
|
||||
12837,build
|
||||
11613,feat
|
||||
12015,spec
|
||||
12058,doc
|
||||
12403,spec
|
||||
12192,feat
|
||||
12204,doc
|
||||
13294,doc
|
||||
12542,doc
|
||||
12826,refactor
|
||||
12781,doc
|
||||
12157,fix
|
||||
12319,fix
|
||||
12188,build
|
||||
12399,doc
|
||||
12145,doc
|
||||
12661,refactor
|
||||
8953,fix
|
||||
12037,fix
|
||||
12186,spec
|
||||
12397,fix
|
||||
12040,doc
|
||||
12886,refactor
|
||||
12008,refactor
|
||||
12716,refactor
|
||||
12750,refactor
|
||||
12787,refactor
|
||||
12858,refactor
|
||||
12140,refactor
|
||||
12503,refactor
|
||||
12514,refactor
|
||||
12584,refactor
|
||||
12596,refactor
|
||||
12637,refactor
|
||||
12660,refactor
|
||||
12696,refactor
|
||||
12877,refactor
|
||||
13030,refactor
|
||||
12916,build
|
||||
12896,build
|
||||
13039,breaking-change
|
||||
11927,build
|
||||
12847,doc
|
||||
12852,doc
|
||||
12194,fix
|
||||
12870,doc
|
||||
12924,fix
|
||||
12682,doc
|
||||
12004,refactor
|
||||
12601,refactor
|
||||
12998,fix
|
||||
13105,vendor
|
||||
12452,doc
|
||||
12738,fix
|
||||
12536,refactor
|
||||
12189,spec
|
||||
13122,spec
|
||||
12662,fix
|
||||
12665,doc
|
||||
12419,feat
|
||||
12756,doc
|
||||
12616,refactor
|
||||
12679,breaking-change
|
||||
12000,doc
|
||||
12372,build
|
||||
12805,build
|
||||
12348,fix
|
||||
12315,doc
|
||||
12072,doc
|
||||
12912,doc
|
||||
12982,fix
|
||||
12105,doc
|
||||
12917,spec
|
||||
12400,doc
|
||||
12101,feat
|
||||
12642,build
|
||||
13058,fix
|
||||
12913,vendor
|
||||
13298,vendor
|
||||
13042,build
|
||||
11230,feat
|
||||
11459,feat
|
||||
12476,vendor
|
||||
11937,doc
|
||||
12328,build
|
||||
12539,refactor
|
||||
12127,build
|
||||
12537,build
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
if (!process.env.CI) require('dotenv-safe').load()
|
||||
require('colors')
|
||||
const args = require('minimist')(process.argv.slice(2))
|
||||
const assert = require('assert')
|
||||
const fs = require('fs')
|
||||
const { execSync } = require('child_process')
|
||||
const GitHub = require('github')
|
||||
@@ -16,18 +16,16 @@ const fail = '\u2717'.red
|
||||
const sumchecker = require('sumchecker')
|
||||
const temp = require('temp').track()
|
||||
const { URL } = require('url')
|
||||
const targetRepo = pkgVersion.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
|
||||
let failureCount = 0
|
||||
|
||||
assert(process.env.ELECTRON_GITHUB_TOKEN, 'ELECTRON_GITHUB_TOKEN not found in environment')
|
||||
|
||||
const github = new GitHub({
|
||||
followRedirects: false
|
||||
})
|
||||
github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
|
||||
const gitDir = path.resolve(__dirname, '..')
|
||||
|
||||
async function getDraftRelease (version, skipValidation) {
|
||||
let releaseInfo = await github.repos.getReleases({owner: 'electron', repo: 'electron'})
|
||||
let releaseInfo = await github.repos.getReleases({owner: 'electron', repo: targetRepo})
|
||||
let drafts
|
||||
let versionToCheck
|
||||
if (version) {
|
||||
@@ -62,16 +60,18 @@ async function validateReleaseAssets (release, validatingRelease) {
|
||||
})
|
||||
check((failureCount === 0), `All required GitHub assets exist for release`, true)
|
||||
|
||||
if (release.draft) {
|
||||
await verifyAssets(release)
|
||||
} else {
|
||||
await verifyShasums(downloadUrls)
|
||||
.catch(err => {
|
||||
console.log(`${fail} error verifyingShasums`, err)
|
||||
})
|
||||
if (!validatingRelease || !release.draft) {
|
||||
if (release.draft) {
|
||||
await verifyAssets(release)
|
||||
} else {
|
||||
await verifyShasums(downloadUrls)
|
||||
.catch(err => {
|
||||
console.log(`${fail} error verifyingShasums`, err)
|
||||
})
|
||||
}
|
||||
const s3Urls = s3UrlsForVersion(release.tag_name)
|
||||
await verifyShasums(s3Urls, true)
|
||||
}
|
||||
const s3Urls = s3UrlsForVersion(release.tag_name)
|
||||
await verifyShasums(s3Urls, true)
|
||||
}
|
||||
|
||||
function check (condition, statement, exitIfFail = false) {
|
||||
@@ -146,9 +146,15 @@ function s3UrlsForVersion (version) {
|
||||
}
|
||||
|
||||
function checkVersion () {
|
||||
if (args.skipVersionCheck) return
|
||||
|
||||
console.log(`Verifying that app version matches package version ${pkgVersion}.`)
|
||||
let startScript = path.join(__dirname, 'start.py')
|
||||
let appVersion = runScript(startScript, ['--version']).trim()
|
||||
let scriptArgs = ['--version']
|
||||
if (args.automaticRelease) {
|
||||
scriptArgs.unshift('-R')
|
||||
}
|
||||
let appVersion = runScript(startScript, scriptArgs).trim()
|
||||
check((pkgVersion.indexOf(appVersion) === 0), `App version ${appVersion} matches ` +
|
||||
`package version ${pkgVersion}.`, true)
|
||||
}
|
||||
@@ -179,7 +185,7 @@ function uploadNodeShasums () {
|
||||
function uploadIndexJson () {
|
||||
console.log('Uploading index.json to S3.')
|
||||
let scriptPath = path.join(__dirname, 'upload-index-json.py')
|
||||
runScript(scriptPath, [])
|
||||
runScript(scriptPath, [pkgVersion])
|
||||
console.log(`${pass} Done uploading index.json to S3.`)
|
||||
}
|
||||
|
||||
@@ -190,7 +196,7 @@ async function createReleaseShasums (release) {
|
||||
console.log(`${fileName} already exists on GitHub; deleting before creating new file.`)
|
||||
await github.repos.deleteAsset({
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
id: existingAssets[0].id
|
||||
}).catch(err => {
|
||||
console.log(`${fail} Error deleting ${fileName} on GitHub:`, err)
|
||||
@@ -209,7 +215,7 @@ async function createReleaseShasums (release) {
|
||||
async function uploadShasumFile (filePath, fileName, release) {
|
||||
let githubOpts = {
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
id: release.id,
|
||||
filePath,
|
||||
name: fileName
|
||||
@@ -244,7 +250,7 @@ function saveShaSumFile (checksums, fileName) {
|
||||
async function publishRelease (release) {
|
||||
let githubOpts = {
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
id: release.id,
|
||||
tag_name: release.tag_name,
|
||||
draft: false
|
||||
@@ -271,12 +277,13 @@ async function makeRelease (releaseToValidate) {
|
||||
let draftRelease = await getDraftRelease()
|
||||
uploadNodeShasums()
|
||||
uploadIndexJson()
|
||||
|
||||
await createReleaseShasums(draftRelease)
|
||||
// Fetch latest version of release before verifying
|
||||
draftRelease = await getDraftRelease(pkgVersion, true)
|
||||
await validateReleaseAssets(draftRelease)
|
||||
await tagLibCC()
|
||||
await publishRelease(draftRelease)
|
||||
await cleanupReleaseBranch()
|
||||
console.log(`${pass} SUCCESS!!! Release has been published. Please run ` +
|
||||
`"npm run publish-to-npm" to publish release to npm.`)
|
||||
}
|
||||
@@ -298,7 +305,7 @@ async function verifyAssets (release) {
|
||||
let downloadDir = await makeTempDir()
|
||||
let githubOpts = {
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
headers: {
|
||||
Accept: 'application/octet-stream'
|
||||
}
|
||||
@@ -444,24 +451,22 @@ async function validateChecksums (validationArgs) {
|
||||
`shasums defined in ${validationArgs.shaSumFile}.`)
|
||||
}
|
||||
|
||||
async function cleanupReleaseBranch () {
|
||||
console.log(`Cleaning up release branch.`)
|
||||
let errorMessage = `Could not delete local release branch.`
|
||||
let successMessage = `Successfully deleted local release branch.`
|
||||
await callGit(['branch', '-D', 'release'], errorMessage, successMessage)
|
||||
errorMessage = `Could not delete remote release branch.`
|
||||
successMessage = `Successfully deleted remote release branch.`
|
||||
return callGit(['push', 'origin', ':release'], errorMessage, successMessage)
|
||||
}
|
||||
|
||||
async function callGit (args, errorMessage, successMessage) {
|
||||
let gitResult = await GitProcess.exec(args, gitDir)
|
||||
if (gitResult.exitCode === 0) {
|
||||
console.log(`${pass} ${successMessage}`)
|
||||
return true
|
||||
async function tagLibCC () {
|
||||
const tag = `electron-${pkg.version}`
|
||||
const libccDir = path.join(path.resolve(__dirname, '..'), 'vendor', 'libchromiumcontent')
|
||||
console.log(`Tagging release ${tag}.`)
|
||||
let tagDetails = await GitProcess.exec([ 'tag', '-a', '-m', tag, tag ], libccDir)
|
||||
if (tagDetails.exitCode === 0) {
|
||||
let pushDetails = await GitProcess.exec(['push', '--tags'], libccDir)
|
||||
if (pushDetails.exitCode === 0) {
|
||||
console.log(`${pass} Successfully tagged libchromiumcontent with ${tag}.`)
|
||||
} else {
|
||||
console.log(`${fail} Error pushing libchromiumcontent tag ${tag}: ` +
|
||||
`${pushDetails.stderr}`)
|
||||
}
|
||||
} else {
|
||||
console.log(`${fail} ${errorMessage} ${gitResult.stderr}`)
|
||||
process.exit(1)
|
||||
console.log(`${fail} Error tagging libchromiumcontent with ${tag}: ` +
|
||||
`${tagDetails.stderr}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,32 +2,46 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import urllib2
|
||||
|
||||
from lib.config import PLATFORM, s3_config
|
||||
from lib.util import electron_gyp, execute, s3put, scoped_cwd
|
||||
|
||||
from lib.config import s3_config
|
||||
from lib.util import s3put, scoped_cwd, safe_mkdir
|
||||
|
||||
SOURCE_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
OUT_DIR = os.path.join(SOURCE_ROOT, 'out', 'D')
|
||||
|
||||
PROJECT_NAME = electron_gyp()['project_name%']
|
||||
PRODUCT_NAME = electron_gyp()['product_name%']
|
||||
BASE_URL = 'https://electron-metadumper.herokuapp.com/?version='
|
||||
|
||||
version = sys.argv[1]
|
||||
authToken = os.getenv('META_DUMPER_AUTH_HEADER')
|
||||
|
||||
def get_content(retry_count = 5):
|
||||
try:
|
||||
request = urllib2.Request(
|
||||
BASE_URL + version,
|
||||
headers={"Authorization" : authToken}
|
||||
)
|
||||
|
||||
return urllib2.urlopen(
|
||||
request
|
||||
).read()
|
||||
except Exception as e:
|
||||
if retry_count == 0:
|
||||
raise e
|
||||
return get_content(retry_count - 1)
|
||||
|
||||
def main():
|
||||
if not authToken or authToken == "":
|
||||
raise Exception("Please set META_DUMPER_AUTH_HEADER")
|
||||
# Upload the index.json.
|
||||
with scoped_cwd(SOURCE_ROOT):
|
||||
if sys.platform == 'darwin':
|
||||
electron = os.path.join(OUT_DIR, '{0}.app'.format(PRODUCT_NAME),
|
||||
'Contents', 'MacOS', PRODUCT_NAME)
|
||||
elif sys.platform == 'win32':
|
||||
electron = os.path.join(OUT_DIR, '{0}.exe'.format(PROJECT_NAME))
|
||||
else:
|
||||
electron = os.path.join(OUT_DIR, PROJECT_NAME)
|
||||
safe_mkdir(OUT_DIR)
|
||||
index_json = os.path.relpath(os.path.join(OUT_DIR, 'index.json'))
|
||||
execute([electron,
|
||||
os.path.join('tools', 'dump-version-info.js'),
|
||||
index_json])
|
||||
|
||||
new_content = get_content()
|
||||
|
||||
with open(index_json, "w") as f:
|
||||
f.write(new_content)
|
||||
|
||||
bucket, access_key, secret_key = s3_config()
|
||||
s3put(bucket, access_key, secret_key, OUT_DIR, 'atom-shell/dist',
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
if (!process.env.CI) require('dotenv-safe').load()
|
||||
|
||||
const GitHub = require('github')
|
||||
const github = new GitHub()
|
||||
github.authenticate({type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN})
|
||||
|
||||
if (process.argv.length < 5) {
|
||||
if (process.argv.length < 6) {
|
||||
console.log('Usage: upload-to-github filePath fileName releaseId')
|
||||
process.exit(1)
|
||||
}
|
||||
let filePath = process.argv[2]
|
||||
let fileName = process.argv[3]
|
||||
let releaseId = process.argv[4]
|
||||
let releaseVersion = process.argv[5]
|
||||
|
||||
const targetRepo = releaseVersion.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
|
||||
|
||||
let githubOpts = {
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
id: releaseId,
|
||||
filePath: filePath,
|
||||
name: fileName
|
||||
@@ -34,7 +39,7 @@ function uploadToGitHub () {
|
||||
console.log(`${fileName} already exists; will delete before retrying upload.`)
|
||||
github.repos.deleteAsset({
|
||||
owner: 'electron',
|
||||
repo: 'electron',
|
||||
repo: targetRepo,
|
||||
id: existingAssets[0].id
|
||||
}).then(uploadToGitHub).catch(uploadToGitHub)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user