Convert remaining coffee-script code in main process code to javascript

This commit is contained in:
Max Brunsfeld
2018-08-24 11:17:38 -07:00
parent 1668617129
commit ce18e1b7d6
11 changed files with 573 additions and 427 deletions

View File

@@ -1,143 +0,0 @@
autoUpdater = null
{EventEmitter} = require 'events'
path = require 'path'
IdleState = 'idle'
CheckingState = 'checking'
DownloadingState = 'downloading'
UpdateAvailableState = 'update-available'
NoUpdateAvailableState = 'no-update-available'
UnsupportedState = 'unsupported'
ErrorState = 'error'
module.exports =
class AutoUpdateManager
Object.assign @prototype, EventEmitter.prototype
constructor: (@version, @testMode, @config) ->
@state = IdleState
@iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
initialize: ->
if process.platform is 'win32'
archSuffix = if process.arch is 'ia32' then '' else '-' + process.arch
@feedUrl = "https://atom.io/api/updates#{archSuffix}?version=#{@version}"
autoUpdater = require './auto-updater-win32'
else
@feedUrl = "https://atom.io/api/updates?version=#{@version}"
{autoUpdater} = require 'electron'
autoUpdater.on 'error', (event, message) =>
@setState(ErrorState, message)
@emitWindowEvent('update-error')
console.error "Error Downloading Update: #{message}"
autoUpdater.setFeedURL @feedUrl
autoUpdater.on 'checking-for-update', =>
@setState(CheckingState)
@emitWindowEvent('checking-for-update')
autoUpdater.on 'update-not-available', =>
@setState(NoUpdateAvailableState)
@emitWindowEvent('update-not-available')
autoUpdater.on 'update-available', =>
@setState(DownloadingState)
# We use sendMessage to send an event called 'update-available' in 'update-downloaded'
# once the update download is complete. This mismatch between the electron
# autoUpdater events is unfortunate but in the interest of not changing the
# one existing event handled by applicationDelegate
@emitWindowEvent('did-begin-downloading-update')
@emit('did-begin-download')
autoUpdater.on 'update-downloaded', (event, releaseNotes, @releaseVersion) =>
@setState(UpdateAvailableState)
@emitUpdateAvailableEvent()
@config.onDidChange 'core.automaticallyUpdate', ({newValue}) =>
if newValue
@scheduleUpdateCheck()
else
@cancelScheduledUpdateCheck()
@scheduleUpdateCheck() if @config.get 'core.automaticallyUpdate'
switch process.platform
when 'win32'
@setState(UnsupportedState) unless autoUpdater.supportsUpdates()
when 'linux'
@setState(UnsupportedState)
emitUpdateAvailableEvent: ->
return unless @releaseVersion?
@emitWindowEvent('update-available', {@releaseVersion})
return
emitWindowEvent: (eventName, payload) ->
for atomWindow in @getWindows()
atomWindow.sendMessage(eventName, payload)
return
setState: (state, errorMessage) ->
return if @state is state
@state = state
@errorMessage = errorMessage
@emit 'state-changed', @state
getState: ->
@state
getErrorMessage: ->
@errorMessage
scheduleUpdateCheck: ->
# Only schedule update check periodically if running in release version and
# and there is no existing scheduled update check.
unless /-dev/.test(@version) or @checkForUpdatesIntervalID
checkForUpdates = => @check(hidePopups: true)
fourHours = 1000 * 60 * 60 * 4
@checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours)
checkForUpdates()
cancelScheduledUpdateCheck: ->
if @checkForUpdatesIntervalID
clearInterval(@checkForUpdatesIntervalID)
@checkForUpdatesIntervalID = null
check: ({hidePopups}={}) ->
unless hidePopups
autoUpdater.once 'update-not-available', @onUpdateNotAvailable
autoUpdater.once 'error', @onUpdateError
autoUpdater.checkForUpdates() unless @testMode
install: ->
autoUpdater.quitAndInstall() unless @testMode
onUpdateNotAvailable: =>
autoUpdater.removeListener 'error', @onUpdateError
{dialog} = require 'electron'
dialog.showMessageBox {
type: 'info'
buttons: ['OK']
icon: @iconPath
message: 'No update available.'
title: 'No Update Available'
detail: "Version #{@version} is the latest version."
}, -> # noop callback to get async behavior
onUpdateError: (event, message) =>
autoUpdater.removeListener 'update-not-available', @onUpdateNotAvailable
{dialog} = require 'electron'
dialog.showMessageBox {
type: 'warning'
buttons: ['OK']
icon: @iconPath
message: 'There was an error checking for updates.'
title: 'Update Error'
detail: message
}, -> # noop callback to get async behavior
getWindows: ->
global.atomApplication.getAllWindows()

View File

@@ -0,0 +1,178 @@
const {EventEmitter} = require('events')
const path = require('path')
const IdleState = 'idle'
const CheckingState = 'checking'
const DownloadingState = 'downloading'
const UpdateAvailableState = 'update-available'
const NoUpdateAvailableState = 'no-update-available'
const UnsupportedState = 'unsupported'
const ErrorState = 'error'
let autoUpdater = null
module.exports =
class AutoUpdateManager extends EventEmitter {
constructor (version, testMode, config) {
super()
this.onUpdateNotAvailable = this.onUpdateNotAvailable.bind(this)
this.onUpdateError = this.onUpdateError.bind(this)
this.version = version
this.testMode = testMode
this.config = config
this.state = IdleState
this.iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
}
initialize () {
if (process.platform === 'win32') {
const archSuffix = process.arch === 'ia32' ? '' : `-${process.arch}`
this.feedUrl = `https://atom.io/api/updates${archSuffix}?version=${this.version}`
autoUpdater = require('./auto-updater-win32')
} else {
this.feedUrl = `https://atom.io/api/updates?version=${this.version}`;
({autoUpdater} = require('electron'))
}
autoUpdater.on('error', (event, message) => {
this.setState(ErrorState, message)
this.emitWindowEvent('update-error')
console.error(`Error Downloading Update: ${message}`)
})
autoUpdater.setFeedURL(this.feedUrl)
autoUpdater.on('checking-for-update', () => {
this.setState(CheckingState)
this.emitWindowEvent('checking-for-update')
})
autoUpdater.on('update-not-available', () => {
this.setState(NoUpdateAvailableState)
this.emitWindowEvent('update-not-available')
})
autoUpdater.on('update-available', () => {
this.setState(DownloadingState)
// We use sendMessage to send an event called 'update-available' in 'update-downloaded'
// once the update download is complete. This mismatch between the electron
// autoUpdater events is unfortunate but in the interest of not changing the
// one existing event handled by applicationDelegate
this.emitWindowEvent('did-begin-downloading-update')
this.emit('did-begin-download')
})
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseVersion) => {
this.releaseVersion = releaseVersion
this.setState(UpdateAvailableState)
this.emitUpdateAvailableEvent()
})
this.config.onDidChange('core.automaticallyUpdate', ({newValue}) => {
if (newValue) {
this.scheduleUpdateCheck()
} else {
this.cancelScheduledUpdateCheck()
}
})
if (this.config.get('core.automaticallyUpdate')) this.scheduleUpdateCheck()
switch (process.platform) {
case 'win32':
if (!autoUpdater.supportsUpdates()) {
this.setState(UnsupportedState)
}
break
case 'linux':
this.setState(UnsupportedState)
}
}
emitUpdateAvailableEvent () {
if (this.releaseVersion == null) return
this.emitWindowEvent('update-available', {releaseVersion: this.releaseVersion})
}
emitWindowEvent (eventName, payload) {
for (let atomWindow of this.getWindows()) {
atomWindow.sendMessage(eventName, payload)
}
}
setState (state, errorMessage) {
if (this.state === state) return
this.state = state
this.errorMessage = errorMessage
this.emit('state-changed', this.state)
}
getState () {
return this.state
}
getErrorMessage () {
return this.errorMessage
}
scheduleUpdateCheck () {
// Only schedule update check periodically if running in release version and
// and there is no existing scheduled update check.
if (!/-dev/.test(this.version) && !this.checkForUpdatesIntervalID) {
const checkForUpdates = () => this.check({hidePopups: true})
const fourHours = 1000 * 60 * 60 * 4
this.checkForUpdatesIntervalID = setInterval(checkForUpdates, fourHours)
checkForUpdates()
}
}
cancelScheduledUpdateCheck () {
if (this.checkForUpdatesIntervalID) {
clearInterval(this.checkForUpdatesIntervalID)
this.checkForUpdatesIntervalID = null
}
}
check ({hidePopups} = {}) {
if (!hidePopups) {
autoUpdater.once('update-not-available', this.onUpdateNotAvailable)
autoUpdater.once('error', this.onUpdateError)
}
if (!this.testMode) autoUpdater.checkForUpdates()
}
install () {
if (!this.testMode) autoUpdater.quitAndInstall()
}
onUpdateNotAvailable () {
autoUpdater.removeListener('error', this.onUpdateError)
const {dialog} = require('electron')
dialog.showMessageBox({
type: 'info',
buttons: ['OK'],
icon: this.iconPath,
message: 'No update available.',
title: 'No Update Available',
detail: `Version ${this.version} is the latest version.`
}, () => {}) // noop callback to get async behavior
}
onUpdateError (event, message) {
autoUpdater.removeListener('update-not-available', this.onUpdateNotAvailable)
const {dialog} = require('electron')
dialog.showMessageBox({
type: 'warning',
buttons: ['OK'],
icon: this.iconPath,
message: 'There was an error checking for updates.',
title: 'Update Error',
detail: message
}, () => {}) // noop callback to get async behavior
}
getWindows () {
return global.atomApplication.getAllWindows()
}
}

View File

@@ -1,62 +0,0 @@
{EventEmitter} = require 'events'
SquirrelUpdate = require './squirrel-update'
class AutoUpdater
Object.assign @prototype, EventEmitter.prototype
setFeedURL: (@updateUrl) ->
quitAndInstall: ->
if SquirrelUpdate.existsSync()
SquirrelUpdate.restartAtom(require('electron').app)
else
require('electron').autoUpdater.quitAndInstall()
downloadUpdate: (callback) ->
SquirrelUpdate.spawn ['--download', @updateUrl], (error, stdout) ->
return callback(error) if error?
try
# Last line of output is the JSON details about the releases
json = stdout.trim().split('\n').pop()
update = JSON.parse(json)?.releasesToApply?.pop?()
catch error
error.stdout = stdout
return callback(error)
callback(null, update)
installUpdate: (callback) ->
SquirrelUpdate.spawn(['--update', @updateUrl], callback)
supportsUpdates: ->
SquirrelUpdate.existsSync()
checkForUpdates: ->
throw new Error('Update URL is not set') unless @updateUrl
@emit 'checking-for-update'
unless SquirrelUpdate.existsSync()
@emit 'update-not-available'
return
@downloadUpdate (error, update) =>
if error?
@emit 'update-not-available'
return
unless update?
@emit 'update-not-available'
return
@emit 'update-available'
@installUpdate (error) =>
if error?
@emit 'update-not-available'
return
@emit 'update-downloaded', {}, update.releaseNotes, update.version, new Date(), 'https://atom.io', => @quitAndInstall()
module.exports = new AutoUpdater()

View File

@@ -0,0 +1,88 @@
const {EventEmitter} = require('events')
const SquirrelUpdate = require('./squirrel-update')
class AutoUpdater extends EventEmitter {
setFeedURL (updateUrl) {
this.updateUrl = updateUrl
}
quitAndInstall () {
if (SquirrelUpdate.existsSync()) {
SquirrelUpdate.restartAtom(require('electron').app)
} else {
require('electron').autoUpdater.quitAndInstall()
}
}
downloadUpdate (callback) {
SquirrelUpdate.spawn(['--download', this.updateUrl], function (error, stdout) {
let update
if (error != null) return callback(error)
try {
// Last line of output is the JSON details about the releases
const json = stdout.trim().split('\n').pop()
const data = JSON.parse(json)
const releasesToApply = data && data.releasesToApply
if (releasesToApply.pop) update = releasesToApply.pop()
} catch (error) {
error.stdout = stdout
return callback(error)
}
callback(null, update)
})
}
installUpdate (callback) {
SquirrelUpdate.spawn(['--update', this.updateUrl], callback)
}
supportsUpdates () {
SquirrelUpdate.existsSync()
}
checkForUpdates () {
if (!this.updateUrl) throw new Error('Update URL is not set')
this.emit('checking-for-update')
if (!SquirrelUpdate.existsSync()) {
this.emit('update-not-available')
return
}
this.downloadUpdate((error, update) => {
if (error != null) {
this.emit('update-not-available')
return
}
if (update == null) {
this.emit('update-not-available')
return
}
this.emit('update-available')
this.installUpdate(error => {
if (error != null) {
this.emit('update-not-available')
return
}
this.emit(
'update-downloaded',
{},
update.releaseNotes,
update.version,
new Date(),
'https://atom.io',
() => this.quitAndInstall()
)
})
})
}
}
module.exports = new AutoUpdater()

View File

@@ -1,24 +0,0 @@
{Menu} = require 'electron'
module.exports =
class ContextMenu
constructor: (template, @atomWindow) ->
template = @createClickHandlers(template)
menu = Menu.buildFromTemplate(template)
menu.popup(@atomWindow.browserWindow, {async: true})
# It's necessary to build the event handlers in this process, otherwise
# closures are dragged across processes and failed to be garbage collected
# appropriately.
createClickHandlers: (template) ->
for item in template
if item.command
item.commandDetail ?= {}
item.commandDetail.contextCommand = true
item.commandDetail.atomWindow = @atomWindow
do (item) =>
item.click = =>
global.atomApplication.sendCommandToWindow(item.command, @atomWindow, item.commandDetail)
else if item.submenu
@createClickHandlers(item.submenu)
item

View File

@@ -0,0 +1,33 @@
const {Menu} = require('electron')
module.exports =
class ContextMenu {
constructor (template, atomWindow) {
this.atomWindow = atomWindow
this.createClickHandlers(template)
const menu = Menu.buildFromTemplate(template)
menu.popup(this.atomWindow.browserWindow, {async: true})
}
// It's necessary to build the event handlers in this process, otherwise
// closures are dragged across processes and failed to be garbage collected
// appropriately.
createClickHandlers (template) {
template.forEach(item => {
if (item.command) {
if (!item.commandDetail) item.commandDetail = {}
item.commandDetail.contextCommand = true
item.commandDetail.atomWindow = this.atomWindow
item.click = () => {
global.atomApplication.sendCommandToWindow(
item.command,
this.atomWindow,
item.commandDetail
)
}
} else if (item.submenu) {
this.createClickHandlers(item.submenu)
}
})
}
}

View File

@@ -1,36 +0,0 @@
ChildProcess = require 'child_process'
# Spawn a command and invoke the callback when it completes with an error
# and the output from standard out.
#
# * `command` The underlying OS command {String} to execute.
# * `args` (optional) The {Array} with arguments to be passed to command.
# * `callback` (optional) The {Function} to call after the command has run. It will be invoked with arguments:
# * `error` (optional) An {Error} object returned by the command, `null` if no error was thrown.
# * `code` Error code returned by the command.
# * `stdout` The {String} output text generated by the command.
# * `stdout` The {String} output text generated by the command.
#
# Returns `undefined`.
exports.spawn = (command, args, callback) ->
stdout = ''
try
spawnedProcess = ChildProcess.spawn(command, args)
catch error
# Spawn can throw an error
process.nextTick -> callback?(error, stdout)
return
spawnedProcess.stdout.on 'data', (data) -> stdout += data
error = null
spawnedProcess.on 'error', (processError) -> error ?= processError
spawnedProcess.on 'close', (code, signal) ->
error ?= new Error("Command failed: #{signal ? code}") if code isnt 0
error?.code ?= code
error?.stdout ?= stdout
callback?(error, stdout)
# This is necessary if using Powershell 2 on Windows 7 to get the events to raise
# http://stackoverflow.com/questions/9155289/calling-powershell-from-nodejs
spawnedProcess.stdin.end()

View File

@@ -0,0 +1,43 @@
const ChildProcess = require('child_process')
// Spawn a command and invoke the callback when it completes with an error
// and the output from standard out.
//
// * `command` The underlying OS command {String} to execute.
// * `args` (optional) The {Array} with arguments to be passed to command.
// * `callback` (optional) The {Function} to call after the command has run. It will be invoked with arguments:
// * `error` (optional) An {Error} object returned by the command, `null` if no error was thrown.
// * `code` Error code returned by the command.
// * `stdout` The {String} output text generated by the command.
// * `stdout` The {String} output text generated by the command.
exports.spawn = function (command, args, callback) {
let error
let spawnedProcess
let stdout = ''
try {
spawnedProcess = ChildProcess.spawn(command, args)
} catch (error) {
process.nextTick(() => callback && callback(error, stdout))
return
}
spawnedProcess.stdout.on('data', data => { stdout += data })
spawnedProcess.on('error', processError => { error = processError })
spawnedProcess.on('close', (code, signal) => {
if (!error && code !== 0) {
error = new Error(`Command failed: ${signal != null ? signal : code}`)
}
if (error) {
if (error.code == null) error.code = code
if (error.stdout == null) error.stdout = stdout
}
callback && callback(error, stdout)
})
// This is necessary if using Powershell 2 on Windows 7 to get the events to raise
// http://stackoverflow.com/questions/9155289/calling-powershell-from-nodejs
return spawnedProcess.stdin.end()
}

View File

@@ -1,162 +0,0 @@
fs = require 'fs-plus'
path = require 'path'
Spawner = require './spawner'
WinShell = require './win-shell'
WinPowerShell = require './win-powershell'
appFolder = path.resolve(process.execPath, '..')
rootAtomFolder = path.resolve(appFolder, '..')
binFolder = path.join(rootAtomFolder, 'bin')
updateDotExe = path.join(rootAtomFolder, 'Update.exe')
exeName = path.basename(process.execPath)
if process.env.SystemRoot
system32Path = path.join(process.env.SystemRoot, 'System32')
setxPath = path.join(system32Path, 'setx.exe')
else
setxPath = 'setx.exe'
# Spawn setx.exe and callback when it completes
spawnSetx = (args, callback) ->
Spawner.spawn(setxPath, args, callback)
# Spawn the Update.exe with the given arguments and invoke the callback when
# the command completes.
spawnUpdate = (args, callback) ->
Spawner.spawn(updateDotExe, args, callback)
# Add atom and apm to the PATH
#
# This is done by adding .cmd shims to the root bin folder in the Atom
# install directory that point to the newly installed versions inside
# the versioned app directories.
addCommandsToPath = (callback) ->
installCommands = (callback) ->
atomCommandPath = path.join(binFolder, 'atom.cmd')
relativeAtomPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'atom.cmd'))
atomCommand = "@echo off\r\n\"%~dp0\\#{relativeAtomPath}\" %*"
atomShCommandPath = path.join(binFolder, 'atom')
relativeAtomShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'atom.sh'))
atomShCommand = "#!/bin/sh\r\n\"$(dirname \"$0\")/#{relativeAtomShPath.replace(/\\/g, '/')}\" \"$@\"\r\necho"
apmCommandPath = path.join(binFolder, 'apm.cmd')
relativeApmPath = path.relative(binFolder, path.join(process.resourcesPath, 'app', 'apm', 'bin', 'apm.cmd'))
apmCommand = "@echo off\r\n\"%~dp0\\#{relativeApmPath}\" %*"
apmShCommandPath = path.join(binFolder, 'apm')
relativeApmShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'apm.sh'))
apmShCommand = "#!/bin/sh\r\n\"$(dirname \"$0\")/#{relativeApmShPath.replace(/\\/g, '/')}\" \"$@\""
fs.writeFile atomCommandPath, atomCommand, ->
fs.writeFile atomShCommandPath, atomShCommand, ->
fs.writeFile apmCommandPath, apmCommand, ->
fs.writeFile apmShCommandPath, apmShCommand, ->
callback()
addBinToPath = (pathSegments, callback) ->
pathSegments.push(binFolder)
newPathEnv = pathSegments.join(';')
spawnSetx(['Path', newPathEnv], callback)
installCommands (error) ->
return callback(error) if error?
WinPowerShell.getPath (error, pathEnv) ->
return callback(error) if error?
pathSegments = pathEnv.split(/;+/).filter (pathSegment) -> pathSegment
if pathSegments.indexOf(binFolder) is -1
addBinToPath(pathSegments, callback)
else
callback()
# Remove atom and apm from the PATH
removeCommandsFromPath = (callback) ->
WinPowerShell.getPath (error, pathEnv) ->
return callback(error) if error?
pathSegments = pathEnv.split(/;+/).filter (pathSegment) ->
pathSegment and pathSegment isnt binFolder
newPathEnv = pathSegments.join(';')
if pathEnv isnt newPathEnv
spawnSetx(['Path', newPathEnv], callback)
else
callback()
# Create a desktop and start menu shortcut by using the command line API
# provided by Squirrel's Update.exe
createShortcuts = (locations, callback) ->
spawnUpdate(['--createShortcut', exeName, '-l', locations.join(',')], callback)
# Update the desktop and start menu shortcuts by using the command line API
# provided by Squirrel's Update.exe
updateShortcuts = (callback) ->
if homeDirectory = fs.getHomeDirectory()
desktopShortcutPath = path.join(homeDirectory, 'Desktop', 'Atom.lnk')
# Check if the desktop shortcut has been previously deleted and
# and keep it deleted if it was
fs.exists desktopShortcutPath, (desktopShortcutExists) ->
locations = ['StartMenu']
locations.push 'Desktop' if desktopShortcutExists
createShortcuts locations, callback
else
createShortcuts ['Desktop', 'StartMenu'], callback
# Remove the desktop and start menu shortcuts by using the command line API
# provided by Squirrel's Update.exe
removeShortcuts = (callback) ->
spawnUpdate(['--removeShortcut', exeName], callback)
exports.spawn = spawnUpdate
# Is the Update.exe installed with Atom?
exports.existsSync = ->
fs.existsSync(updateDotExe)
# Restart Atom using the version pointed to by the atom.cmd shim
exports.restartAtom = (app) ->
if projectPath = global.atomApplication?.lastFocusedWindow?.projectPath
args = [projectPath]
app.once 'will-quit', -> Spawner.spawn(path.join(binFolder, 'atom.cmd'), args)
app.quit()
updateContextMenus = (callback) ->
WinShell.fileContextMenu.update ->
WinShell.folderContextMenu.update ->
WinShell.folderBackgroundContextMenu.update ->
callback()
# Handle squirrel events denoted by --squirrel-* command line arguments.
exports.handleStartupEvent = (app, squirrelCommand) ->
switch squirrelCommand
when '--squirrel-install'
createShortcuts ['Desktop', 'StartMenu'], ->
addCommandsToPath ->
WinShell.fileHandler.register ->
updateContextMenus ->
app.quit()
true
when '--squirrel-updated'
updateShortcuts ->
addCommandsToPath ->
WinShell.fileHandler.update ->
updateContextMenus ->
app.quit()
true
when '--squirrel-uninstall'
removeShortcuts ->
removeCommandsFromPath ->
WinShell.fileHandler.deregister ->
WinShell.fileContextMenu.deregister ->
WinShell.folderContextMenu.deregister ->
WinShell.folderBackgroundContextMenu.deregister ->
app.quit()
true
when '--squirrel-obsolete'
app.quit()
true
else
false

View File

@@ -0,0 +1,187 @@
let setxPath
const fs = require('fs-plus')
const path = require('path')
const Spawner = require('./spawner')
const WinShell = require('./win-shell')
const WinPowerShell = require('./win-powershell')
const appFolder = path.resolve(process.execPath, '..')
const rootAtomFolder = path.resolve(appFolder, '..')
const binFolder = path.join(rootAtomFolder, 'bin')
const updateDotExe = path.join(rootAtomFolder, 'Update.exe')
const exeName = path.basename(process.execPath)
if (process.env.SystemRoot) {
const system32Path = path.join(process.env.SystemRoot, 'System32')
setxPath = path.join(system32Path, 'setx.exe')
} else {
setxPath = 'setx.exe'
}
// Spawn setx.exe and callback when it completes
const spawnSetx = (args, callback) => Spawner.spawn(setxPath, args, callback)
// Spawn the Update.exe with the given arguments and invoke the callback when
// the command completes.
const spawnUpdate = (args, callback) => Spawner.spawn(updateDotExe, args, callback)
// Add atom and apm to the PATH
//
// This is done by adding .cmd shims to the root bin folder in the Atom
// install directory that point to the newly installed versions inside
// the versioned app directories.
const addCommandsToPath = callback => {
const installCommands = callback => {
const atomCommandPath = path.join(binFolder, 'atom.cmd')
const relativeAtomPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'atom.cmd'))
const atomCommand = `@echo off\r\n\"%~dp0\\${relativeAtomPath}\" %*`
const atomShCommandPath = path.join(binFolder, 'atom')
const relativeAtomShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'atom.sh'))
const atomShCommand = `#!/bin/sh\r\n\"$(dirname \"$0\")/${relativeAtomShPath.replace(/\\/g, '/')}\" \"$@\"\r\necho`
const apmCommandPath = path.join(binFolder, 'apm.cmd')
const relativeApmPath = path.relative(binFolder, path.join(process.resourcesPath, 'app', 'apm', 'bin', 'apm.cmd'))
const apmCommand = `@echo off\r\n\"%~dp0\\${relativeApmPath}\" %*`
const apmShCommandPath = path.join(binFolder, 'apm')
const relativeApmShPath = path.relative(binFolder, path.join(appFolder, 'resources', 'cli', 'apm.sh'))
const apmShCommand = `#!/bin/sh\r\n\"$(dirname \"$0\")/${relativeApmShPath.replace(/\\/g, '/')}\" \"$@\"`
fs.writeFile(atomCommandPath, atomCommand, () =>
fs.writeFile(atomShCommandPath, atomShCommand, () =>
fs.writeFile(apmCommandPath, apmCommand, () =>
fs.writeFile(apmShCommandPath, apmShCommand, () => callback())
)
)
)
}
const addBinToPath = (pathSegments, callback) => {
pathSegments.push(binFolder)
const newPathEnv = pathSegments.join(';')
spawnSetx(['Path', newPathEnv], callback)
}
installCommands(error => {
if (error) return callback(error)
WinPowerShell.getPath((error, pathEnv) => {
if (error) return callback(error)
const pathSegments = pathEnv.split(/;+/).filter(pathSegment => pathSegment)
if (pathSegments.indexOf(binFolder) === -1) {
addBinToPath(pathSegments, callback)
} else {
callback()
}
})
})
}
// Remove atom and apm from the PATH
const removeCommandsFromPath = callback =>
WinPowerShell.getPath((error, pathEnv) => {
if (error != null) { return callback(error) }
const pathSegments = pathEnv.split(/;+/).filter(pathSegment => pathSegment && (pathSegment !== binFolder))
const newPathEnv = pathSegments.join(';')
if (pathEnv !== newPathEnv) {
return spawnSetx(['Path', newPathEnv], callback)
} else {
return callback()
}
})
// Create a desktop and start menu shortcut by using the command line API
// provided by Squirrel's Update.exe
const createShortcuts = (locations, callback) => spawnUpdate(['--createShortcut', exeName, '-l', locations.join(',')], callback)
// Update the desktop and start menu shortcuts by using the command line API
// provided by Squirrel's Update.exe
const updateShortcuts = (callback) => {
const homeDirectory = fs.getHomeDirectory()
if (homeDirectory) {
const desktopShortcutPath = path.join(homeDirectory, 'Desktop', 'Atom.lnk')
// Check if the desktop shortcut has been previously deleted and
// and keep it deleted if it was
fs.exists(desktopShortcutPath, (desktopShortcutExists) => {
const locations = ['StartMenu']
if (desktopShortcutExists) { locations.push('Desktop') }
createShortcuts(locations, callback)
})
} else {
createShortcuts(['Desktop', 'StartMenu'], callback)
}
}
// Remove the desktop and start menu shortcuts by using the command line API
// provided by Squirrel's Update.exe
const removeShortcuts = callback => spawnUpdate(['--removeShortcut', exeName], callback)
exports.spawn = spawnUpdate
// Is the Update.exe installed with Atom?
exports.existsSync = () => fs.existsSync(updateDotExe)
// Restart Atom using the version pointed to by the atom.cmd shim
exports.restartAtom = (app) => {
let args
if (global.atomApplication && global.atomApplication.lastFocusedWindow) {
const {projectPath} = global.atomApplication.lastFocusedWindow
if (projectPath) args = [projectPath]
}
app.once('will-quit', () => Spawner.spawn(path.join(binFolder, 'atom.cmd'), args))
app.quit()
}
const updateContextMenus = callback =>
WinShell.fileContextMenu.update(() =>
WinShell.folderContextMenu.update(() =>
WinShell.folderBackgroundContextMenu.update(() => callback())
)
)
// Handle squirrel events denoted by --squirrel-* command line arguments.
exports.handleStartupEvent = (app, squirrelCommand) => {
switch (squirrelCommand) {
case '--squirrel-install':
createShortcuts(['Desktop', 'StartMenu'], () =>
addCommandsToPath(() =>
WinShell.fileHandler.register(() =>
updateContextMenus(() => app.quit())
)
)
)
return true
case '--squirrel-updated':
updateShortcuts(() =>
addCommandsToPath(() =>
WinShell.fileHandler.update(() =>
updateContextMenus(() => app.quit())
)
)
)
return true
case '--squirrel-uninstall':
removeShortcuts(() =>
removeCommandsFromPath(() =>
WinShell.fileHandler.deregister(() =>
WinShell.fileContextMenu.deregister(() =>
WinShell.folderContextMenu.deregister(() =>
WinShell.folderBackgroundContextMenu.deregister(() => app.quit())
)
)
)
)
)
return true
case '--squirrel-obsolete':
app.quit()
return true
default:
return false
}
}

View File

@@ -0,0 +1,44 @@
let powershellPath
const path = require('path')
const Spawner = require('./spawner')
if (process.env.SystemRoot) {
const system32Path = path.join(process.env.SystemRoot, 'System32')
powershellPath = path.join(system32Path, 'WindowsPowerShell', 'v1.0', 'powershell.exe')
} else {
powershellPath = 'powershell.exe'
}
// Spawn powershell.exe and callback when it completes
const spawnPowershell = function (args, callback) {
// Set encoding and execute the command, capture the output, and return it
// via .NET's console in order to have consistent UTF-8 encoding.
// See http://stackoverflow.com/questions/22349139/utf-8-output-from-powershell
// to address https://github.com/atom/atom/issues/5063
args[0] = `\
[Console]::OutputEncoding=[System.Text.Encoding]::UTF8
$output=${args[0]}
[Console]::WriteLine($output)\
`
args.unshift('-command')
args.unshift('RemoteSigned')
args.unshift('-ExecutionPolicy')
args.unshift('-noprofile')
Spawner.spawn(powershellPath, args, callback)
}
// Get the user's PATH environment variable registry value.
//
// * `callback` The {Function} to call after registry operation is done.
// It will be invoked with the same arguments provided by {Spawner.spawn}.
//
// Returns the user's path {String}.
exports.getPath = callback =>
spawnPowershell(['[environment]::GetEnvironmentVariable(\'Path\',\'User\')'], function (error, stdout) {
if (error != null) {
return callback(error)
}
const pathOutput = stdout.replace(/^\s+|\s+$/g, '')
return callback(null, pathOutput)
})