/ src / main-process / squirrel-update.js
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  };