pulsar/tasks/update-atom-shell-task.coffee

211 lines
7.5 KiB
CoffeeScript

fs = require 'fs'
path = require 'path'
os = require 'os'
request = require 'request'
formidable = require 'formidable'
unzip = require 'unzip'
module.exports = (grunt) ->
{spawn, mkdir, rm, cp} = require('./task-helpers')(grunt)
accessToken = null
getTokenFromKeychain = (callback) ->
accessToken ?= process.env['ATOM_ACCESS_TOKEN']
if accessToken
callback(null, accessToken)
return
spawn {cmd: 'security', args: ['-q', 'find-generic-password', '-ws', 'GitHub API Token']}, (error, result, code) ->
accessToken = result.stdout unless error?
callback(error, accessToken)
callAtomShellReposApi = (path, callback) ->
getTokenFromKeychain (error, accessToken) ->
if error
callback(error)
return
options =
url: "https://api.github.com/repos/atom/atom-shell#{path}"
proxy: process.env.http_proxy || process.env.https_proxy
headers:
authorization: "token #{accessToken}"
accept: 'application/vnd.github.manifold-preview'
'user-agent': 'Atom'
request options, (error, response, body) ->
if not error?
body = JSON.parse(body)
error = new Error(body.message) if response.statusCode != 200
callback(error, response, body)
findReleaseIdFromAtomShellVersion = (version, callback) ->
callAtomShellReposApi '/releases', (error, response, data) ->
if error?
grunt.log.error('GitHub API failed to access atom-shell releases')
callback(error)
else
for release in data when release.tag_name is version
callback(null, release.id)
return
grunt.log.error("There is no #{version} release of atom-shell")
callback(false)
getAtomShellDownloadUrl = (version, releaseId, callback) ->
callAtomShellReposApi "/releases/#{releaseId}/assets", (error, response, data) ->
if error?
grunt.log.error("Cannot get assets of atom-shell's #{version} release")
callback(error)
else
filename = "atom-shell-#{version}-#{process.platform}.zip"
for asset in data when asset.name is filename and asset.state is 'uploaded'
callback(null, asset.url)
return
grunt.log.error("Cannot get url of atom-shell's release asset")
callback(false)
getAtomShellVersion = ->
versionPath = path.join('atom-shell', 'version')
if grunt.file.isFile(versionPath)
grunt.file.read(versionPath).trim()
else
null
getTempDir = ->
if process.platform is 'win32' then os.tmpdir() else '/tmp'
getCachePath = (version) ->
path.join(getTempDir(), 'atom-cached-atom-shells', version)
isAtomShellVersionCached = (version) ->
grunt.file.isFile(getCachePath(version), 'version')
getDownloadOptions = (version, url, callback) ->
options =
url: url
followRedirect: false
proxy: process.env.http_proxy || process.env.https_proxy
# Only set headers for GitHub host, the url could also be a S3 link and
# setting headers for it would make the request fail.
if require('url').parse(url).hostname is 'api.github.com'
getTokenFromKeychain (error, accessToken) ->
options.headers =
authorization: "token #{accessToken}"
accept: 'application/octet-stream'
'user-agent': 'Atom'
callback(error, options)
else
callback(null, options)
downloadAtomShell = (version, url, callback) ->
getDownloadOptions version, url, (error, options) ->
if error
callback(error)
return
inputStream = request(options)
inputStream.on 'response', (response) ->
if response.statusCode is 302
# Manually handle redirection so headers would not be sent for S3.
downloadAtomShell(version, response.headers.location, callback)
else if response.statusCode is 200
grunt.verbose.writeln("Downloading atom-shell version #{version.cyan}")
cacheDirectory = getCachePath(version)
rm(cacheDirectory)
mkdir(cacheDirectory)
form = new formidable.IncomingForm()
form.uploadDir = cacheDirectory
form.maxFieldsSize = 100 * 1024 * 1024
form.on 'file', (name, file) ->
cacheFile = path.join(cacheDirectory, 'atom-shell.zip')
fs.renameSync(file.path, cacheFile)
callback(null, cacheFile)
form.parse response, (error) ->
if error
grunt.log.error("atom-shell #{version.cyan} failed to download")
else
if response.statusCode is 404
grunt.log.error("atom-shell #{version.cyan} not found")
else
grunt.log.error("atom-shell #{version.cyan} request failed")
callback(false)
downloadAtomShellOfVersion = (version, callback) ->
findReleaseIdFromAtomShellVersion version, (error, releaseId) ->
if error?
callback(error)
else
getAtomShellDownloadUrl version, releaseId, (error, url) ->
if error?
callback(error)
else
downloadAtomShell version, url, callback
unzipAtomShell = (zipPath, callback) ->
grunt.verbose.writeln('Unzipping atom-shell')
directoryPath = path.dirname(zipPath)
if process.platform is 'darwin'
# The zip archive of darwin build contains symbol links, only the "unzip"
# command can handle it correctly.
spawn {cmd: 'unzip', args: [zipPath, '-d', directoryPath]}, (error) ->
rm(zipPath)
callback(error)
else
fileStream = fs.createReadStream(zipPath)
fileStream.on('error', callback)
zipStream = fileStream.pipe(unzip.Extract(path: directoryPath))
zipStream.on('error', callback)
zipStream.on 'close', ->
rm(zipPath)
callback(null)
rebuildNativeModules = (previousVersion, callback) ->
newVersion = getAtomShellVersion()
if newVersion and newVersion isnt previousVersion
grunt.verbose.writeln("Rebuilding native modules for new atom-shell version #{newVersion.cyan}.")
cmd = path.join('node_modules', '.bin', 'apm')
cmd += ".cmd" if process.platform is 'win32'
spawn {cmd, args: ['rebuild']}, (error) -> callback(error)
else
callback()
installAtomShell = (version) ->
rm('atom-shell')
cp(getCachePath(version), 'atom-shell')
grunt.registerTask 'update-atom-shell', 'Update atom-shell', ->
done = @async()
{atomShellVersion} = grunt.file.readJSON('package.json')
if atomShellVersion
atomShellVersion = "v#{atomShellVersion}"
currentAtomShellVersion = getAtomShellVersion()
if atomShellVersion isnt currentAtomShellVersion
if isAtomShellVersionCached(atomShellVersion)
grunt.verbose.writeln("Installing cached atom-shell #{atomShellVersion.cyan}")
installAtomShell(atomShellVersion)
rebuildNativeModules(currentAtomShellVersion, done)
else
downloadAtomShellOfVersion atomShellVersion, (error, zipPath) ->
if error?
done(error)
else if zipPath?
unzipAtomShell zipPath, (error) ->
if error?
done(error)
else
grunt.verbose.writeln("Installing atom-shell #{atomShellVersion.cyan}")
installAtomShell(atomShellVersion)
rebuildNativeModules(currentAtomShellVersion, done)
else
done(false)
else
done()
else
grunt.log.error("atom-shell version missing from package.json")
done(false)