Merge pull request #11778 from electron/parallel-releases-1-6-x

Allow multiple releases to run at once for 1-6-x
This commit is contained in:
John Kleinschmidt
2018-01-31 13:42:38 -05:00
committed by GitHub
6 changed files with 972 additions and 4 deletions

View File

@@ -5,14 +5,18 @@
"asar": "^0.11.0",
"browserify": "^13.1.0",
"colors": "^1.1.2",
"dugite": "^1.45.0",
"electabul": "~0.0.4",
"electron-docs-linter": "^2.3.3",
"electron-typescript-definitions": "1.2.7",
"github": "^9.2.0",
"heads": "^1.3.0",
"minimist": "^1.2.0",
"nugget": "^2.0.1",
"request": "*",
"standard": "^8.4.0",
"standard-markdown": "^2.1.1"
"standard-markdown": "^2.1.1",
"sumchecker": "^2.0.2"
},
"optionalDependencies": {
"runas": "^3.0.0"
@@ -46,8 +50,9 @@
"create-api-json": "electron-docs-linter docs --outfile=out/electron-api.json --version=$npm_package_version",
"create-typescript-definitions": "npm run create-api-json && electron-typescript-definitions --in=out/electron-api.json --out=out/electron.d.ts",
"preinstall": "node -e 'process.exit(0)'",
"prerelease": "node ./script/prerelease",
"release": "./script/upload.py -p",
"prepare-release": "node ./script/prepare-release.js",
"prerelease": "python ./script/bootstrap.py -v --dev && npm run build",
"release": "node ./script/release.js",
"repl": "python ./script/start.py --interactive",
"start": "python ./script/start.py",
"test": "python ./script/test.py"

209
script/ci-release-build.js Normal file
View File

@@ -0,0 +1,209 @@
const assert = require('assert')
const request = require('request')
const buildAppVeyorURL = 'https://windows-ci.electronjs.org/api/builds'
const jenkinsServer = 'https://mac-ci.electronjs.org'
const circleCIJobs = [
'electron-linux-arm',
'electron-linux-ia32',
'electron-linux-x64'
]
const jenkinsJobs = [
'electron-mas-x64-release',
'electron-osx-x64-release'
]
async function makeRequest (requestOptions, parseResponse) {
return new Promise((resolve, reject) => {
request(requestOptions, (err, res, body) => {
if (!err && res.statusCode >= 200 && res.statusCode < 300) {
if (parseResponse) {
const build = JSON.parse(body)
resolve(build)
} else {
resolve(body)
}
} else {
if (parseResponse) {
console.log('Error: ', `(status ${res.statusCode})`, err || JSON.parse(res.body), requestOptions)
} else {
console.log('Error: ', `(status ${res.statusCode})`, err || res.body, requestOptions)
}
reject(err)
}
})
})
}
async function circleCIcall (buildUrl, targetBranch, job, ghRelease) {
assert(process.env.CIRCLE_TOKEN, 'CIRCLE_TOKEN not found in environment')
console.log(`Triggering CircleCI to run build job: ${job} on branch: ${targetBranch} with release flag.`)
let buildRequest = {
'build_parameters': {
'CIRCLE_JOB': job
}
}
if (ghRelease) {
buildRequest.build_parameters.ELECTRON_RELEASE = 1
} else {
buildRequest.build_parameters.RUN_RELEASE_BUILD = 'true'
}
let circleResponse = await makeRequest({
method: 'POST',
url: buildUrl,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(buildRequest)
}, true).catch(err => {
console.log('Error calling CircleCI:', err)
})
console.log(`Check ${circleResponse.build_url} for status. (${job})`)
}
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')
let environmentVariables = {}
if (ghRelease) {
environmentVariables.ELECTRON_RELEASE = 1
} else {
environmentVariables.RUN_RELEASE_BUILD = 'true'
}
const requestOpts = {
url: buildAppVeyorURL,
auth: {
bearer: process.env.APPVEYOR_TOKEN
},
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
accountName: 'AppVeyor',
projectSlug: 'electron',
branch: targetBranch,
environmentVariables
}),
method: 'POST'
}
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}`)
}
function buildCircleCI (targetBranch, ghRelease, job) {
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)
} else {
circleCIJobs.forEach((job) => circleCIcall(circleBuildUrl, targetBranch, job, ghRelease))
}
}
async function buildJenkins (targetBranch, ghRelease, job) {
assert(process.env.JENKINS_AUTH_TOKEN, 'JENKINS_AUTH_TOKEN not found in environment')
assert(process.env.JENKINS_BUILD_TOKEN, 'JENKINS_BUILD_TOKEN not found in environment')
let jenkinsCrumb = await getJenkinsCrumb()
if (job) {
assert(jenkinsJobs.includes(job), `Unknown CI job name: ${job}.`)
callJenkinsBuild(job, jenkinsCrumb, targetBranch, ghRelease)
} else {
jenkinsJobs.forEach((job) => {
callJenkinsBuild(job, jenkinsCrumb, targetBranch, ghRelease)
})
}
}
async function callJenkins (path, requestParameters, requestHeaders) {
let requestOptions = {
url: `${jenkinsServer}/${path}`,
auth: {
user: 'build',
pass: process.env.JENKINS_AUTH_TOKEN
},
qs: requestParameters
}
if (requestHeaders) {
requestOptions.headers = requestHeaders
}
let jenkinsResponse = await makeRequest(requestOptions).catch(err => {
console.log(`Error calling Jenkins:`, err)
})
return jenkinsResponse
}
async function callJenkinsBuild (job, jenkinsCrumb, targetBranch, ghRelease) {
console.log(`Triggering Jenkins to run build job: ${job} on branch: ${targetBranch} with release flag.`)
let jenkinsParams = {
token: process.env.JENKINS_BUILD_TOKEN,
BRANCH: targetBranch
}
if (!ghRelease) {
jenkinsParams.RUN_RELEASE_BUILD = 1
}
await callJenkins(`job/${job}/buildWithParameters`, jenkinsParams, jenkinsCrumb)
.catch(err => {
console.log(`Error calling Jenkins build`, err)
})
let buildUrl = `${jenkinsServer}/job/${job}/lastBuild/`
console.log(`Jenkins build request successful. Check build status at ${buildUrl}.`)
}
async function getJenkinsCrumb () {
let crumbResponse = await callJenkins('crumbIssuer/api/xml', {
xpath: 'concat(//crumbRequestField,":",//crumb)'
}).catch(err => {
console.log(`Error getting jenkins crumb:`, err)
})
let crumbDetails = crumbResponse.split(':')
let crumbHeader = {}
crumbHeader[crumbDetails[0]] = crumbDetails[1]
return crumbHeader
}
function runRelease (targetBranch, options) {
if (options.ci) {
switch (options.ci) {
case 'CircleCI': {
buildCircleCI(targetBranch, options.ghRelease, options.job)
break
}
case 'AppVeyor': {
buildAppVeyor(targetBranch, options.ghRelease)
break
}
case 'Jenkins': {
buildJenkins(targetBranch, options.ghRelease, options.job)
break
}
}
} else {
buildCircleCI(targetBranch, options.ghRelease, options.job)
buildAppVeyor(targetBranch, options.ghRelease)
buildJenkins(targetBranch, options.ghRelease, options.job)
}
}
module.exports = runRelease
if (require.main === module) {
const args = require('minimist')(process.argv.slice(2))
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|Jenkins] [--ghRelease] TARGET_BRANCH
`)
process.exit(0)
}
runRelease(targetBranch, args)
}

116
script/merge-release.js Executable file
View File

@@ -0,0 +1,116 @@
#!/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 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 callGit(gitArgs, errorMessage, successMessage)
}
async function mergeReleaseIntoBranch (branchName) {
console.log(`Merging release branch into ${branchName}.`)
let mergeArgs = ['merge', 'release-1-7-x', '--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 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 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 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-1-7-x')
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()

182
script/prepare-release.js Executable file
View File

@@ -0,0 +1,182 @@
#!/usr/bin/env node
require('colors')
const args = require('minimist')(process.argv.slice(2))
const assert = require('assert')
const ciReleaseBuild = require('./ci-release-build')
const { execSync } = require('child_process')
const fail = '\u2717'.red
const { GitProcess, GitError } = require('dugite')
const GitHub = require('github')
const pass = '\u2713'.green
const path = require('path')
const pkg = require('../package.json')
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]` +
` (--stable) (--notesOnly)`)
process.exit(1)
}
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-1-7-x' ], 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)
}
}
function getNewVersion () {
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')
}
try {
let bumpVersion = execSync(scriptArgs.join(' '), {encoding: 'UTF-8'})
bumpVersion = bumpVersion.substr(bumpVersion.indexOf(':') + 1).trim()
let newVersion = `v${bumpVersion}`
console.log(`${pass} Successfully bumped version to ${newVersion}`)
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',
base: `v${pkg.version}`,
head: currentBranch
}
let 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 ` +
`${currentBranch}`, err)
process.exit(1)
})
commitComparison.data.commits.forEach(commitEntry => {
let commitMessage = commitEntry.commit.message
if (commitMessage.toLowerCase().indexOf('merge') > -1) {
releaseNotes += `${commitMessage} \n`
}
})
console.log(`${pass} Done generating release notes for ${currentBranch}.`)
return releaseNotes
}
async function createRelease (branchToTarget, isBeta) {
let releaseNotes = await getReleaseNotes(branchToTarget)
let newVersion = getNewVersion()
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)
})
let drafts = releases.data.filter(release => release.draft &&
release.tag_name === newVersion)
if (drafts.length > 0) {
console.log(`${fail} Aborting because draft release for
${drafts[0].tag_name} already exists.`)
process.exit(1)
}
console.log(`${pass} A draft release does not exist; creating one.`)
githubOpts.body = releaseNotes
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)}.`
githubOpts.name = `${githubOpts.name}`
githubOpts.prerelease = true
}
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.`)
}
async function pushRelease () {
let pushDetails = await GitProcess.exec(['push', 'origin', 'HEAD'], gitDir)
if (pushDetails.exitCode === 0) {
console.log(`${pass} Successfully pushed the release branch. Wait for ` +
`release builds to finish before running "npm run release".`)
} else {
console.log(`${fail} Error pushing the release branch: ` +
`${pushDetails.stderr}`)
process.exit(1)
}
}
async function runReleaseBuilds () {
await ciReleaseBuild('release-1-7-x', {
ghRelease: true
})
}
async function prepareRelease (isBeta, notesOnly) {
let currentBranch = await getCurrentBranch(gitDir)
if (notesOnly) {
let releaseNotes = await getReleaseNotes(currentBranch)
console.log(`Draft release notes are: ${releaseNotes}`)
} else {
await createReleaseBranch()
await createRelease(currentBranch, isBeta)
await pushRelease()
await runReleaseBuilds()
}
}
prepareRelease(!args.stable, args.notesOnly)

456
script/release.js Executable file
View File

@@ -0,0 +1,456 @@
#!/usr/bin/env node
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')
const { GitProcess } = require('dugite')
const nugget = require('nugget')
const pkg = require('../package.json')
const pkgVersion = `v${pkg.version}`
const pass = '\u2713'.green
const path = require('path')
const fail = '\u2717'.red
const sumchecker = require('sumchecker')
const temp = require('temp').track()
const { URL } = require('url')
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 drafts
let versionToCheck
if (version) {
versionToCheck = version
} else {
versionToCheck = pkgVersion
}
drafts = releaseInfo.data
.filter(release => release.tag_name === versionToCheck &&
release.draft === true)
const draft = drafts[0]
if (!skipValidation) {
failureCount = 0
check(drafts.length === 1, 'one draft exists', true)
if (versionToCheck.indexOf('beta') > -1) {
check(draft.prerelease, 'draft is a prerelease')
}
check(draft.body.length > 50 && !draft.body.includes('(placeholder)'), 'draft has release notes')
check((failureCount === 0), `Draft release looks good to go.`, true)
}
return draft
}
async function validateReleaseAssets (release) {
const requiredAssets = assetsForVersion(release.tag_name).sort()
const extantAssets = release.assets.map(asset => asset.name).sort()
const downloadUrls = release.assets.map(asset => asset.browser_download_url).sort()
failureCount = 0
requiredAssets.forEach(asset => {
check(extantAssets.includes(asset), asset)
})
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)
})
}
const s3Urls = s3UrlsForVersion(release.tag_name)
await verifyShasums(s3Urls, true)
}
function check (condition, statement, exitIfFail = false) {
if (condition) {
console.log(`${pass} ${statement}`)
} else {
failureCount++
console.log(`${fail} ${statement}`)
if (exitIfFail) process.exit(1)
}
}
function assetsForVersion (version) {
const patterns = [
`electron-${version}-darwin-x64-dsym.zip`,
`electron-${version}-darwin-x64-symbols.zip`,
`electron-${version}-darwin-x64.zip`,
`electron-${version}-linux-arm-symbols.zip`,
`electron-${version}-linux-arm.zip`,
`electron-${version}-linux-armv7l-symbols.zip`,
`electron-${version}-linux-armv7l.zip`,
`electron-${version}-linux-ia32-symbols.zip`,
`electron-${version}-linux-ia32.zip`,
`electron-${version}-linux-x64-symbols.zip`,
`electron-${version}-linux-x64.zip`,
`electron-${version}-mas-x64-dsym.zip`,
`electron-${version}-mas-x64-symbols.zip`,
`electron-${version}-mas-x64.zip`,
`electron-${version}-win32-ia32-pdb.zip`,
`electron-${version}-win32-ia32-symbols.zip`,
`electron-${version}-win32-ia32.zip`,
`electron-${version}-win32-x64-pdb.zip`,
`electron-${version}-win32-x64-symbols.zip`,
`electron-${version}-win32-x64.zip`,
`electron-api.json`,
`electron.d.ts`,
`ffmpeg-${version}-darwin-x64.zip`,
`ffmpeg-${version}-linux-arm.zip`,
`ffmpeg-${version}-linux-armv7l.zip`,
`ffmpeg-${version}-linux-ia32.zip`,
`ffmpeg-${version}-linux-x64.zip`,
`ffmpeg-${version}-mas-x64.zip`,
`ffmpeg-${version}-win32-ia32.zip`,
`ffmpeg-${version}-win32-x64.zip`,
`SHASUMS256.txt`
]
return patterns
}
function s3UrlsForVersion (version) {
const bucket = `https://gh-contractor-zcbenz.s3.amazonaws.com/`
const patterns = [
`${bucket}atom-shell/dist/${version}/iojs-${version}-headers.tar.gz`,
`${bucket}atom-shell/dist/${version}/iojs-${version}.tar.gz`,
`${bucket}atom-shell/dist/${version}/node-${version}.tar.gz`,
`${bucket}atom-shell/dist/${version}/node.lib`,
`${bucket}atom-shell/dist/${version}/win-x64/iojs.lib`,
`${bucket}atom-shell/dist/${version}/win-x86/iojs.lib`,
`${bucket}atom-shell/dist/${version}/x64/node.lib`,
`${bucket}atom-shell/dist/${version}/SHASUMS.txt`,
`${bucket}atom-shell/dist/${version}/SHASUMS256.txt`,
`${bucket}atom-shell/dist/index.json`
]
return patterns
}
function checkVersion () {
console.log(`Verifying that app version matches package version ${pkgVersion}.`)
let startScript = path.join(__dirname, 'start.py')
let appVersion = runScript(startScript, ['--version']).trim()
check((pkgVersion.indexOf(appVersion) === 0), `App version ${appVersion} matches ` +
`package version ${pkgVersion}.`, true)
}
function runScript (scriptName, scriptArgs, cwd) {
let scriptCommand = `${scriptName} ${scriptArgs.join(' ')}`
let scriptOptions = {
encoding: 'UTF-8'
}
if (cwd) {
scriptOptions.cwd = cwd
}
try {
return execSync(scriptCommand, scriptOptions)
} catch (err) {
console.log(`${fail} Error running ${scriptName}`, err)
process.exit(1)
}
}
function uploadNodeShasums () {
console.log('Uploading Node SHASUMS file to S3.')
let scriptPath = path.join(__dirname, 'upload-node-checksums.py')
runScript(scriptPath, ['-v', pkgVersion])
console.log(`${pass} Done uploading Node SHASUMS file to S3.`)
}
function uploadIndexJson () {
console.log('Uploading index.json to S3.')
let scriptPath = path.join(__dirname, 'upload-index-json.py')
runScript(scriptPath, [])
console.log(`${pass} Done uploading index.json to S3.`)
}
async function createReleaseShasums (release) {
let fileName = 'SHASUMS256.txt'
let existingAssets = release.assets.filter(asset => asset.name === fileName)
if (existingAssets.length > 0) {
console.log(`${fileName} already exists on GitHub; deleting before creating new file.`)
await github.repos.deleteAsset({
owner: 'electron',
repo: 'electron',
id: existingAssets[0].id
}).catch(err => {
console.log(`${fail} Error deleting ${fileName} on GitHub:`, err)
})
}
console.log(`Creating and uploading the release ${fileName}.`)
let scriptPath = path.join(__dirname, 'merge-electron-checksums.py')
let checksums = runScript(scriptPath, ['-v', pkgVersion])
console.log(`${pass} Generated release SHASUMS.`)
let filePath = await saveShaSumFile(checksums, fileName)
console.log(`${pass} Created ${fileName} file.`)
await uploadShasumFile(filePath, fileName, release)
console.log(`${pass} Successfully uploaded ${fileName} to GitHub.`)
}
async function uploadShasumFile (filePath, fileName, release) {
let githubOpts = {
owner: 'electron',
repo: 'electron',
id: release.id,
filePath,
name: fileName
}
return github.repos.uploadAsset(githubOpts)
.catch(err => {
console.log(`${fail} Error uploading ${filePath} to GitHub:`, err)
process.exit(1)
})
}
function saveShaSumFile (checksums, fileName) {
return new Promise((resolve, reject) => {
temp.open(fileName, (err, info) => {
if (err) {
console.log(`${fail} Could not create ${fileName} file`)
process.exit(1)
} else {
fs.writeFileSync(info.fd, checksums)
fs.close(info.fd, (err) => {
if (err) {
console.log(`${fail} Could close ${fileName} file`)
process.exit(1)
}
resolve(info.path)
})
}
})
})
}
async function publishRelease (release) {
let githubOpts = {
owner: 'electron',
repo: 'electron',
id: release.id,
tag_name: release.tag_name,
draft: false
}
return github.repos.editRelease(githubOpts)
.catch(err => {
console.log(`${fail} Error publishing release:`, err)
process.exit(1)
})
}
async function makeRelease (releaseToValidate) {
if (releaseToValidate) {
console.log(`Validating release ${args.validateRelease}`)
let release = await getDraftRelease(args.validateRelease)
await validateReleaseAssets(release)
} else {
checkVersion()
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 publishRelease(draftRelease)
await cleanupReleaseBranch()
console.log(`${pass} SUCCESS!!! Release has been published. Please run ` +
`"npm run publish-to-npm" to publish release to npm.`)
}
}
async function makeTempDir () {
return new Promise((resolve, reject) => {
temp.mkdir('electron-publish', (err, dirPath) => {
if (err) {
reject(err)
} else {
resolve(dirPath)
}
})
})
}
async function verifyAssets (release) {
let downloadDir = await makeTempDir()
let githubOpts = {
owner: 'electron',
repo: 'electron',
headers: {
Accept: 'application/octet-stream'
}
}
console.log(`Downloading files from GitHub to verify shasums`)
let shaSumFile = 'SHASUMS256.txt'
let filesToCheck = await Promise.all(release.assets.map(async (asset) => {
githubOpts.id = asset.id
let assetDetails = await github.repos.getAsset(githubOpts)
await downloadFiles(assetDetails.meta.location, downloadDir, false, asset.name)
return asset.name
})).catch(err => {
console.log(`${fail} Error downloading files from GitHub`, err)
process.exit(1)
})
filesToCheck = filesToCheck.filter(fileName => fileName !== shaSumFile)
let checkerOpts
await validateChecksums({
algorithm: 'sha256',
filesToCheck,
fileDirectory: downloadDir,
shaSumFile,
checkerOpts,
fileSource: 'GitHub'
})
}
function downloadFiles (urls, directory, quiet, targetName) {
return new Promise((resolve, reject) => {
let nuggetOpts = {
dir: directory
}
if (quiet) {
nuggetOpts.quiet = quiet
}
if (targetName) {
nuggetOpts.target = targetName
}
nugget(urls, nuggetOpts, (err) => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
}
async function verifyShasums (urls, isS3) {
let fileSource = isS3 ? 'S3' : 'GitHub'
console.log(`Downloading files from ${fileSource} to verify shasums`)
let downloadDir = await makeTempDir()
let filesToCheck = []
try {
if (!isS3) {
await downloadFiles(urls, downloadDir)
filesToCheck = urls.map(url => {
let currentUrl = new URL(url)
return path.basename(currentUrl.pathname)
}).filter(file => file.indexOf('SHASUMS') === -1)
} else {
const s3VersionPath = `/atom-shell/dist/${pkgVersion}/`
await Promise.all(urls.map(async (url) => {
let currentUrl = new URL(url)
let dirname = path.dirname(currentUrl.pathname)
let filename = path.basename(currentUrl.pathname)
let s3VersionPathIdx = dirname.indexOf(s3VersionPath)
if (s3VersionPathIdx === -1 || dirname === s3VersionPath) {
if (s3VersionPathIdx !== -1 && filename.indexof('SHASUMS') === -1) {
filesToCheck.push(filename)
}
await downloadFiles(url, downloadDir, true)
} else {
let subDirectory = dirname.substr(s3VersionPathIdx + s3VersionPath.length)
let fileDirectory = path.join(downloadDir, subDirectory)
try {
fs.statSync(fileDirectory)
} catch (err) {
fs.mkdirSync(fileDirectory)
}
filesToCheck.push(path.join(subDirectory, filename))
await downloadFiles(url, fileDirectory, true)
}
}))
}
} catch (err) {
console.log(`${fail} Error downloading files from ${fileSource}`, err)
process.exit(1)
}
console.log(`${pass} Successfully downloaded the files from ${fileSource}.`)
let checkerOpts
if (isS3) {
checkerOpts = { defaultTextEncoding: 'binary' }
}
await validateChecksums({
algorithm: 'sha256',
filesToCheck,
fileDirectory: downloadDir,
shaSumFile: 'SHASUMS256.txt',
checkerOpts,
fileSource
})
if (isS3) {
await validateChecksums({
algorithm: 'sha1',
filesToCheck,
fileDirectory: downloadDir,
shaSumFile: 'SHASUMS.txt',
checkerOpts,
fileSource
})
}
}
async function validateChecksums (validationArgs) {
console.log(`Validating checksums for files from ${validationArgs.fileSource} ` +
`against ${validationArgs.shaSumFile}.`)
let shaSumFilePath = path.join(validationArgs.fileDirectory, validationArgs.shaSumFile)
let checker = new sumchecker.ChecksumValidator(validationArgs.algorithm,
shaSumFilePath, validationArgs.checkerOpts)
await checker.validate(validationArgs.fileDirectory, validationArgs.filesToCheck)
.catch(err => {
if (err instanceof sumchecker.ChecksumMismatchError) {
console.error(`${fail} The checksum of ${err.filename} from ` +
`${validationArgs.fileSource} did not match the shasum in ` +
`${validationArgs.shaSumFile}`)
} else if (err instanceof sumchecker.ChecksumParseError) {
console.error(`${fail} The checksum file ${validationArgs.shaSumFile} ` +
`from ${validationArgs.fileSource} could not be parsed.`, err)
} else if (err instanceof sumchecker.NoChecksumFoundError) {
console.error(`${fail} The file ${err.filename} from ` +
`${validationArgs.fileSource} was not in the shasum file ` +
`${validationArgs.shaSumFile}.`)
} else {
console.error(`${fail} Error matching files from ` +
`${validationArgs.fileSource} shasums in ${validationArgs.shaSumFile}.`, err)
}
process.exit(1)
})
console.log(`${pass} All files from ${validationArgs.fileSource} match ` +
`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-1-7-x'], 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
} else {
console.log(`${fail} ${errorMessage} ${gitResult.stderr}`)
process.exit(1)
}
}
makeRelease(args.validateRelease)

View File

@@ -172,7 +172,7 @@ def get_text_with_editor(name):
def create_or_get_release_draft(github, releases, tag, tag_exists):
# Search for existing draft.
for release in releases:
if release['draft']:
if release['draft'] and release['tag_name'] == tag:
return release
if tag_exists: