squirrel-update.js
1 let setxPath; 2 const { app } = require('electron'); 3 const fs = require('fs-plus'); 4 const getAppName = require('../get-app-name'); 5 const path = require('path'); 6 const Spawner = require('./spawner'); 7 const WinShell = require('./win-shell'); 8 const WinPowerShell = require('./win-powershell'); 9 10 const appFolder = path.resolve(process.execPath, '..'); 11 const rootAtomFolder = path.resolve(appFolder, '..'); 12 const binFolder = path.join(rootAtomFolder, 'bin'); 13 const updateDotExe = path.join(rootAtomFolder, 'Update.exe'); 14 const execName = path.basename(app.getPath('exe')); 15 16 if (process.env.SystemRoot) { 17 const system32Path = path.join(process.env.SystemRoot, 'System32'); 18 setxPath = path.join(system32Path, 'setx.exe'); 19 } else { 20 setxPath = 'setx.exe'; 21 } 22 23 // Spawn setx.exe and callback when it completes 24 const spawnSetx = (args, callback) => Spawner.spawn(setxPath, args, callback); 25 26 // Spawn the Update.exe with the given arguments and invoke the callback when 27 // the command completes. 28 const spawnUpdate = (args, callback) => 29 Spawner.spawn(updateDotExe, args, callback); 30 31 // Add atom and apm to the PATH 32 // 33 // This is done by adding .cmd shims to the root bin folder in the Atom 34 // install directory that point to the newly installed versions inside 35 // the versioned app directories. 36 const addCommandsToPath = callback => { 37 const atomCmdName = execName.replace('.exe', '.cmd'); 38 const apmCmdName = atomCmdName.replace('atom', 'apm'); 39 const atomShName = execName.replace('.exe', ''); 40 const apmShName = atomShName.replace('atom', 'apm'); 41 42 const installCommands = callback => { 43 const atomCommandPath = path.join(binFolder, atomCmdName); 44 const relativeAtomPath = path.relative( 45 binFolder, 46 path.join(appFolder, 'resources', 'cli', 'atom.cmd') 47 ); 48 const atomCommand = `@echo off\r\n"%~dp0\\${relativeAtomPath}" %*`; 49 50 const atomShCommandPath = path.join(binFolder, atomShName); 51 const relativeAtomShPath = path.relative( 52 binFolder, 53 path.join(appFolder, 'resources', 'cli', 'atom.sh') 54 ); 55 const atomShCommand = `#!/bin/sh\r\n"$(dirname "$0")/${relativeAtomShPath.replace( 56 /\\/g, 57 '/' 58 )}" "$@"\r\necho`; 59 60 const apmCommandPath = path.join(binFolder, apmCmdName); 61 const relativeApmPath = path.relative( 62 binFolder, 63 path.join(process.resourcesPath, 'app', 'apm', 'bin', 'apm.cmd') 64 ); 65 const apmCommand = `@echo off\r\n"%~dp0\\${relativeApmPath}" %*`; 66 67 const apmShCommandPath = path.join(binFolder, apmShName); 68 const relativeApmShPath = path.relative( 69 binFolder, 70 path.join(appFolder, 'resources', 'cli', 'apm.sh') 71 ); 72 const apmShCommand = `#!/bin/sh\r\n"$(dirname "$0")/${relativeApmShPath.replace( 73 /\\/g, 74 '/' 75 )}" "$@"`; 76 77 fs.writeFile(atomCommandPath, atomCommand, () => 78 fs.writeFile(atomShCommandPath, atomShCommand, () => 79 fs.writeFile(apmCommandPath, apmCommand, () => 80 fs.writeFile(apmShCommandPath, apmShCommand, () => callback()) 81 ) 82 ) 83 ); 84 }; 85 86 const addBinToPath = (pathSegments, callback) => { 87 pathSegments.push(binFolder); 88 const newPathEnv = pathSegments.join(';'); 89 spawnSetx(['Path', newPathEnv], callback); 90 }; 91 92 installCommands(error => { 93 if (error) return callback(error); 94 95 WinPowerShell.getPath((error, pathEnv) => { 96 if (error) return callback(error); 97 98 const pathSegments = pathEnv 99 .split(/;+/) 100 .filter(pathSegment => pathSegment); 101 if (pathSegments.indexOf(binFolder) === -1) { 102 addBinToPath(pathSegments, callback); 103 } else { 104 callback(); 105 } 106 }); 107 }); 108 }; 109 110 // Remove atom and apm from the PATH 111 const removeCommandsFromPath = callback => 112 WinPowerShell.getPath((error, pathEnv) => { 113 if (error != null) { 114 return callback(error); 115 } 116 117 const pathSegments = pathEnv 118 .split(/;+/) 119 .filter(pathSegment => pathSegment && pathSegment !== binFolder); 120 const newPathEnv = pathSegments.join(';'); 121 122 if (pathEnv !== newPathEnv) { 123 return spawnSetx(['Path', newPathEnv], callback); 124 } else { 125 return callback(); 126 } 127 }); 128 129 // Create a desktop and start menu shortcut by using the command line API 130 // provided by Squirrel's Update.exe 131 const createShortcuts = (locations, callback) => 132 spawnUpdate( 133 ['--createShortcut', execName, '-l', locations.join(',')], 134 callback 135 ); 136 137 // Update the desktop and start menu shortcuts by using the command line API 138 // provided by Squirrel's Update.exe 139 const updateShortcuts = callback => { 140 const homeDirectory = fs.getHomeDirectory(); 141 if (homeDirectory) { 142 const desktopShortcutPath = path.join( 143 homeDirectory, 144 'Desktop', 145 `${getAppName()}.lnk` 146 ); 147 // Check if the desktop shortcut has been previously deleted and 148 // and keep it deleted if it was 149 fs.exists(desktopShortcutPath, desktopShortcutExists => { 150 const locations = ['StartMenu']; 151 if (desktopShortcutExists) { 152 locations.push('Desktop'); 153 } 154 155 createShortcuts(locations, callback); 156 }); 157 } else { 158 createShortcuts(['Desktop', 'StartMenu'], callback); 159 } 160 }; 161 162 // Remove the desktop and start menu shortcuts by using the command line API 163 // provided by Squirrel's Update.exe 164 const removeShortcuts = callback => 165 spawnUpdate(['--removeShortcut', execName], callback); 166 167 exports.spawn = spawnUpdate; 168 169 // Is the Update.exe installed with Atom? 170 exports.existsSync = () => fs.existsSync(updateDotExe); 171 172 // Restart Atom using the version pointed to by the atom.cmd shim 173 exports.restartAtom = () => { 174 let args; 175 const atomCmdName = execName.replace('.exe', '.cmd'); 176 177 if (global.atomApplication && global.atomApplication.lastFocusedWindow) { 178 const { projectPath } = global.atomApplication.lastFocusedWindow; 179 if (projectPath) args = [projectPath]; 180 } 181 Spawner.spawn(path.join(binFolder, atomCmdName), args); 182 app.quit(); 183 }; 184 185 const updateContextMenus = callback => 186 WinShell.fileContextMenu.update(() => 187 WinShell.folderContextMenu.update(() => 188 WinShell.folderBackgroundContextMenu.update(() => callback()) 189 ) 190 ); 191 192 // Handle squirrel events denoted by --squirrel-* command line arguments. 193 exports.handleStartupEvent = squirrelCommand => { 194 switch (squirrelCommand) { 195 case '--squirrel-install': 196 createShortcuts(['Desktop', 'StartMenu'], () => 197 addCommandsToPath(() => 198 WinShell.fileHandler.register(() => 199 updateContextMenus(() => app.quit()) 200 ) 201 ) 202 ); 203 return true; 204 case '--squirrel-updated': 205 updateShortcuts(() => 206 addCommandsToPath(() => 207 WinShell.fileHandler.update(() => 208 updateContextMenus(() => app.quit()) 209 ) 210 ) 211 ); 212 return true; 213 case '--squirrel-uninstall': 214 removeShortcuts(() => 215 removeCommandsFromPath(() => 216 WinShell.fileHandler.deregister(() => 217 WinShell.fileContextMenu.deregister(() => 218 WinShell.folderContextMenu.deregister(() => 219 WinShell.folderBackgroundContextMenu.deregister(() => 220 app.quit() 221 ) 222 ) 223 ) 224 ) 225 ) 226 ); 227 return true; 228 case '--squirrel-obsolete': 229 app.quit(); 230 return true; 231 default: 232 return false; 233 } 234 };