/ src / command-installer.js
command-installer.js
  1  const path = require('path');
  2  const fs = require('fs-plus');
  3  
  4  module.exports = class CommandInstaller {
  5    constructor(applicationDelegate) {
  6      this.applicationDelegate = applicationDelegate;
  7    }
  8  
  9    initialize(appVersion) {
 10      this.appVersion = appVersion;
 11    }
 12  
 13    getInstallDirectory() {
 14      return '/usr/local/bin';
 15    }
 16  
 17    getResourcesDirectory() {
 18      return process.resourcesPath;
 19    }
 20  
 21    installShellCommandsInteractively() {
 22      const showErrorDialog = error => {
 23        this.applicationDelegate.confirm(
 24          {
 25            message: 'Failed to install shell commands',
 26            detail: error.message
 27          },
 28          () => {}
 29        );
 30      };
 31  
 32      this.installAtomCommand(true, (error, atomCommandName) => {
 33        if (error) return showErrorDialog(error);
 34        this.installApmCommand(true, (error, apmCommandName) => {
 35          if (error) return showErrorDialog(error);
 36          this.applicationDelegate.confirm(
 37            {
 38              message: 'Commands installed.',
 39              detail: `The shell commands \`${atomCommandName}\` and \`${apmCommandName}\` are installed.`
 40            },
 41            () => {}
 42          );
 43        });
 44      });
 45    }
 46  
 47    getCommandNameForChannel(commandName) {
 48      let channelMatch = this.appVersion.match(/beta|nightly/);
 49      let channel = channelMatch ? channelMatch[0] : '';
 50  
 51      switch (channel) {
 52        case 'beta':
 53          return `${commandName}-beta`;
 54        case 'nightly':
 55          return `${commandName}-nightly`;
 56        default:
 57          return commandName;
 58      }
 59    }
 60  
 61    installAtomCommand(askForPrivilege, callback) {
 62      this.installCommand(
 63        path.join(this.getResourcesDirectory(), 'app', 'atom.sh'),
 64        this.getCommandNameForChannel('atom'),
 65        askForPrivilege,
 66        callback
 67      );
 68    }
 69  
 70    installApmCommand(askForPrivilege, callback) {
 71      this.installCommand(
 72        path.join(
 73          this.getResourcesDirectory(),
 74          'app',
 75          'apm',
 76          'node_modules',
 77          '.bin',
 78          'apm'
 79        ),
 80        this.getCommandNameForChannel('apm'),
 81        askForPrivilege,
 82        callback
 83      );
 84    }
 85  
 86    installCommand(commandPath, commandName, askForPrivilege, callback) {
 87      if (process.platform !== 'darwin') return callback();
 88  
 89      const destinationPath = path.join(this.getInstallDirectory(), commandName);
 90  
 91      fs.readlink(destinationPath, (error, realpath) => {
 92        if (error && error.code !== 'ENOENT') return callback(error);
 93        if (realpath === commandPath) return callback(null, commandName);
 94        this.createSymlink(fs, commandPath, destinationPath, error => {
 95          if (error && error.code === 'EACCES' && askForPrivilege) {
 96            const fsAdmin = require('fs-admin');
 97            this.createSymlink(fsAdmin, commandPath, destinationPath, error => {
 98              callback(error, commandName);
 99            });
100          } else {
101            callback(error);
102          }
103        });
104      });
105    }
106  
107    createSymlink(fs, sourcePath, destinationPath, callback) {
108      fs.unlink(destinationPath, error => {
109        if (error && error.code !== 'ENOENT') return callback(error);
110        fs.makeTree(path.dirname(destinationPath), error => {
111          if (error) return callback(error);
112          fs.symlink(sourcePath, destinationPath, callback);
113        });
114      });
115    }
116  };