electron-updater
Version:
Cross platform auto-updater for electron applications
239 lines (223 loc) • 7.27 kB
JavaScript
var util = require('util'),
fs = require('fs'),
async = require('async'),
semver = require('semver'),
checker = require('./check.js'),
updater = require('./update.js'),
context = require('./context.js'),
exists = require('./exists.js'),
copier = require('./copier.js'),
file = require('./file.js'),
Logger = require('./logger.js'),
directory = require('./directory.js'),
assert = require('assert'),
EventEmitter = require('events').EventEmitter,
spawn = require('child_process').spawn
function expect() {
var types = []
var args = arguments[arguments.length - 2]
var values = []
for(var i = 0, n = arguments.length - 1; i < n; i++) {
types.push(arguments[i])
}
var x = 0
for(var i = 0, n = types.length; i < n; i++) {
if (typeof args[x] === types[i]) {
values[i] = args[x++]
}
}
arguments[arguments.length - 1](values)
}
function ElectronCommands() {
}
function update(appDir, logger, callback) {
expect('string', 'object', 'function', arguments, function (values) {
appDir = values[0] || path.dirname(process.mainModule.filename)
logger = values[1] || Logger.default
assert.ok(callback = values[2])
})
check(appDir, logger, function (err, results) {
if(err) return callback(err)
if(!results) return callback()
logger.log('Updating...')
updater.update(results, logger, callback)
})
}
function watch(appDir, callback) {
}
function defined(item) {
return !!item
}
function check(appDir, logger, callback) {
expect('string', 'object', 'function', arguments, function (values) {
appDir = values[0] || path.dirname(process.mainModule.filename)
logger = values[1] || Logger.default
assert.ok(callback = values[2])
})
logger.log('Checking for updates.')
context.load(appDir, function (err, ctx) {
if(err) return callback(err)
async.map([
{ context: ctx, kind: 'app' },
{ context: ctx, kind: 'dependencies' },
{ context: ctx, kind: 'plugins' }
],
checker.check,
function (err, results) {
if(err) return callback(err)
var app = results[0]
var dependencies = results[1].filter(defined)
var plugins = results[2].filter(defined)
if(!app && !dependencies.length && !plugins.length) {
logger.log('Updates are not available.')
callback()
} else {
logger.log('Updates are available.')
callback(null, {
context: ctx,
app: app,
dependencies: dependencies,
plugins: plugins
})
}
})
})
}
function list(appDir, logger, callback) {
expect('string', 'object', 'function', arguments, function (values) {
appDir = values[0] || path.dirname(process.mainModule.filename)
logger = values[1] || Logger.default
assert.ok(callback = values[2])
})
logger.log('Listing dependencies.')
context.load(appDir, function (err, ctx) {
callback(err, {
name: ctx.name,
version: ctx.version,
dependencies: ctx.dependencies,
plugins: ctx.plugins,
})
})
}
function isValid(appDir, logger, callback) {
expect('string', 'object', 'function', arguments, function (values) {
appDir = values[0] || path.dirname(process.mainModule.filename)
logger = values[1] || Logger.default
assert.ok(callback = values[2])
})
logger.log('Checking validity.')
context.load(appDir, function (err, ctx) {
if(err) return callback(err)
async.map([
{ context: ctx, kind: 'dependencies' },
{ context: ctx, kind: 'plugins' }
],
exists.check,
function (err, results) {
if(err) return callback(err)
var r1 = results[0]
var r2 = results[1]
var valid = r1 && r2
callback(null, valid)
})
})
}
function fullUpdate(publisher, appName, logger, callback) {
copier.copy(process.execPath, publisher, appName, function (err, tmpExecPath) {
if(err) return callback(err)
var updateDir = path.resolve(path.join(__dirname, '..'))
var appDir = path.dirname(process.mainModule.filename)
var args = JSON.stringify({
appName: appName,
cwd: process.cwd(),
exe: process.execPath,
argv: process.argv.slice(1)
})
logger.log('Launching update process: ')
logger.log(' args: ' + util.inspect(args))
var encodedArgs = new Buffer(args).toString('base64')
var child = spawn(tmpExecPath, [updateDir, '--electron-update=' + encodedArgs], {
detached: true,
cwd: appDir,
stdio: ['ignore', 'ignore', 'ignore']
})
child.unref()
callback()
})
}
function start(appDir, logger, callback) {
var that = this
expect('string', 'object', 'function', arguments, function (values) {
appDir = values[0] || path.dirname(process.mainModule.filename)
logger = values[1] || new Logger(appDir, Logger.appendToFile, true)
callback = values[2] || function (err) { if(err) logger.error(err) }
})
logger.log('Starting...')
context.load(appDir, function (err, ctx) {
if(err) return callback(err)
if(ctx.updatePending) {
// If there is a pending update, do a full update instead of starting the app.
// This is set when a dependency update is available.
fullUpdate(ctx.publisher, ctx.name, logger, function (err) {
if(err) return callback(err)
that.emit('updateRequired')
callback()
})
} else if(ctx.updateInProgress) {
// This can happen if the user tries to re-launch the app while an update is
// currently in progress. In that case just report updateRequired so that
// the user closes the app right away.
that.emit('updateRequired')
callback()
} else {
isValid(appDir, logger, function (err, valid) {
if(err) return callback(err)
if(valid) {
// If the app is valid, then go ahead and startup what we have.
// After that, we will check for updates and notify user when they are available.
that.emit('ready')
// todo: watch not check
check(appDir, logger, function (err, result) {
if(err) return callback(err)
if(result && (result.app || result.dependencies.length)) {
// If a new version of the app is available or
// a dependency update is available, we must
// restart and do the full update process to update those.
var appData = directory.appDir(ctx.publisher, ctx.name)
var pendingUpdatePath = path.join(appData, '.update')
file.touch(pendingUpdatePath, 'PENDING', function (err) {
if(err) return callback(err)
that.emit('updateAvailable')
callback()
})
} else if (result && result.plugins.length) {
// If only plugin updates are available we can go ahead and update those
// right now and then notify the user that they can restart to apply them.
updater.update(result, logger, function (err) {
if(err) return callback(err)
that.emit('updateAvailable')
callback()
})
} else {
callback()
}
})
} else {
fullUpdate(ctx.publisher, ctx.name, logger, function (err) {
if(err) return callback(err)
that.emit('updateRequired')
callback()
})
}
})
}
})
}
util.inherits(ElectronCommands, EventEmitter)
ElectronCommands.prototype.update = update
ElectronCommands.prototype.watch = watch
ElectronCommands.prototype.check = check
ElectronCommands.prototype.list = list
ElectronCommands.prototype.start = start
ElectronCommands.prototype.isValid = isValid
module.exports = new ElectronCommands()