123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- #!/usr/bin/env node
- if (!process.env.CI) require('dotenv-safe').load()
- require('colors')
- const args = require('minimist')(process.argv.slice(2), {
- boolean: [
- 'validateRelease',
- 'skipVersionCheck',
- 'automaticRelease',
- 'verboseNugget'
- ],
- default: { 'verboseNugget': false }
- })
- const fs = require('fs')
- const { execSync } = require('child_process')
- const GitHub = require('github')
- 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')
- const targetRepo = pkgVersion.indexOf('nightly') > 0 ? 'nightlies' : 'electron'
- let failureCount = 0
- const github = new GitHub({
- followRedirects: false
- })
- github.authenticate({ type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN })
- async function getDraftRelease (version, skipValidation) {
- const releaseInfo = await github.repos.getReleases({ owner: 'electron', repo: targetRepo })
- let versionToCheck
- if (version) {
- versionToCheck = version
- } else {
- versionToCheck = pkgVersion
- }
- const 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, validatingRelease) {
- const requiredAssets = assetsForVersion(release.tag_name, validatingRelease).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 (!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)
- }
- }
- 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, validatingRelease) {
- const patterns = [
- `electron-${version}-darwin-x64-dsym.zip`,
- `electron-${version}-darwin-x64-symbols.zip`,
- `electron-${version}-darwin-x64.zip`,
- `electron-${version}-linux-arm64-symbols.zip`,
- `electron-${version}-linux-arm64.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-arm64.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`
- ]
- if (!validatingRelease) {
- patterns.push('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 () {
- if (args.skipVersionCheck) return
- console.log(`Verifying that app version matches package version ${pkgVersion}.`)
- const startScript = path.join(__dirname, 'start.py')
- const scriptArgs = ['--version']
- if (args.automaticRelease) {
- scriptArgs.unshift('-R')
- }
- const appVersion = runScript(startScript, scriptArgs).trim()
- check((pkgVersion.indexOf(appVersion) === 0), `App version ${appVersion} matches ` +
- `package version ${pkgVersion}.`, true)
- }
- function runScript (scriptName, scriptArgs, cwd) {
- const scriptCommand = `${scriptName} ${scriptArgs.join(' ')}`
- const 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.')
- const 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.')
- const scriptPath = path.join(__dirname, 'upload-index-json.py')
- runScript(scriptPath, [pkgVersion])
- console.log(`${pass} Done uploading index.json to S3.`)
- }
- async function createReleaseShasums (release) {
- const fileName = 'SHASUMS256.txt'
- const 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: targetRepo,
- id: existingAssets[0].id
- }).catch(err => {
- console.log(`${fail} Error deleting ${fileName} on GitHub:`, err)
- })
- }
- console.log(`Creating and uploading the release ${fileName}.`)
- const scriptPath = path.join(__dirname, 'merge-electron-checksums.py')
- const checksums = runScript(scriptPath, ['-v', pkgVersion])
- console.log(`${pass} Generated release SHASUMS.`)
- const 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) {
- const githubOpts = {
- owner: 'electron',
- repo: targetRepo,
- 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) {
- const githubOpts = {
- owner: 'electron',
- repo: targetRepo,
- 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) {
- if (releaseToValidate === true) {
- releaseToValidate = pkgVersion
- } else {
- console.log('Release to validate !=== true')
- }
- console.log(`Validating release ${releaseToValidate}`)
- const release = await getDraftRelease(releaseToValidate)
- await validateReleaseAssets(release, true)
- } 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)
- 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) {
- const downloadDir = await makeTempDir()
- const githubOpts = {
- owner: 'electron',
- repo: targetRepo,
- headers: {
- Accept: 'application/octet-stream'
- }
- }
- console.log(`Downloading files from GitHub to verify shasums`)
- const shaSumFile = 'SHASUMS256.txt'
- let filesToCheck = await Promise.all(release.assets.map(async (asset) => {
- githubOpts.id = asset.id
- const assetDetails = await github.repos.getAsset(githubOpts)
- await downloadFiles(assetDetails.meta.location, downloadDir, 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, targetName) {
- return new Promise((resolve, reject) => {
- const nuggetOpts = { dir: directory }
- nuggetOpts.quiet = !args.verboseNugget
- if (targetName) nuggetOpts.target = targetName
- nugget(urls, nuggetOpts, (err) => {
- if (err) {
- reject(err)
- } else {
- console.log(`${pass} all files downloaded successfully!`)
- resolve()
- }
- })
- })
- }
- async function verifyShasums (urls, isS3) {
- const fileSource = isS3 ? 'S3' : 'GitHub'
- console.log(`Downloading files from ${fileSource} to verify shasums`)
- const downloadDir = await makeTempDir()
- let filesToCheck = []
- try {
- if (!isS3) {
- await downloadFiles(urls, downloadDir)
- filesToCheck = urls.map(url => {
- const 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) => {
- const currentUrl = new URL(url)
- const dirname = path.dirname(currentUrl.pathname)
- const filename = path.basename(currentUrl.pathname)
- const s3VersionPathIdx = dirname.indexOf(s3VersionPath)
- if (s3VersionPathIdx === -1 || dirname === s3VersionPath) {
- if (s3VersionPathIdx !== -1 && filename.indexof('SHASUMS') === -1) {
- filesToCheck.push(filename)
- }
- await downloadFiles(url, downloadDir)
- } else {
- const subDirectory = dirname.substr(s3VersionPathIdx + s3VersionPath.length)
- const fileDirectory = path.join(downloadDir, subDirectory)
- try {
- fs.statSync(fileDirectory)
- } catch (err) {
- fs.mkdirSync(fileDirectory)
- }
- filesToCheck.push(path.join(subDirectory, filename))
- await downloadFiles(url, fileDirectory)
- }
- }))
- }
- } 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}.`)
- const shaSumFilePath = path.join(validationArgs.fileDirectory, validationArgs.shaSumFile)
- const 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}.`)
- }
- makeRelease(args.validateRelease)
|