1
\$\begingroup\$

I've been working a side project recently, when ever I have time, to make a small script that determines what Command Line Interface to load using node.js and its built-in child_process module.

The goal of the script is to determine the OS environment, determine that environments default shell, and use that shell to execute a command via node. The point is to make sure that it is cross-platform compatible.

I don't have much experience with JavaScript and can't help feeling there might be a better way to do it. Any feedback is appreciated.

terminal.js

// "linux": ["gnome-terminal", "konsole", "xfce4-terminal", "terminator", "xterm", "uxterm"],
// "win32": ["cmd","powershell","bash"],
// "darwin": ["bash"],

class Terminal {
  constructor() {
     this.get_linux_terminal = function () {
      switch (process.env.GDMSESSION) {
        // if session is using gtk
        case 'ubuntu':
        case 'ubuntu-2d':
        case 'gnome':
        case 'gnome-shell':
        case 'gnome-classic':
        case 'gnome-fallback':
        case 'cinnamon':
          return "gnome-terminal";
        // xfce session has its own terminal, xfce is gtk compatible
        case 'xfce':
          return "xfce4-terminal";
        // if session is using qt, kde and lxde are qt compatible
        case 'kde-plasma':
          return "konsole";
        case 'Lubuntu':
          return "lxterminal";
        // if unknown session, default to xterm
        default:
          // attempt to determine desktop session
          switch (process.env.XDG_CURRENT_DESKTOP) {
            case 'Unity':
            case 'GNOME':
            case 'X-Cinnamon':
              return "gnome-terminal";
            case 'XFCE':
              return "xfce4-terminal";
            case 'KDE':
              return "konsole";
            case 'LXDE':
              return "lxterminal";
            default:
              return "xterm";
          }
        return ""; // redundant LBYL response
      }
    };

    this.set_linux_terminal = function (shell) {
      // creates an object containing the environments
      // default shell and execute option
      switch (shell) {
        case 'gnome-terminal':
        case 'xfce4-terminal':
          return {
            'shell': shell,
            'option': '-x'
          };
        case 'konsole':
        case 'lxterminal':
        case 'xterm':
          return {
            'shell': shell,
            'option': '-e'
          };
        default:
          return {
            'shell': "xterm",
            'option': '-e'
          };
        return ""; // redundant LBYL response
      }
    };

    this.get_tty = function () {
      if ("linux" == process.platform) {
        // linux has a mass variety of environments and a variety of ways
        // for determining those environments. best to go for the base
        // environments by depending on the most common builtin shells instead.
        // https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
        return this.set_linux_terminal(
          this.get_linux_terminal()
        );
      }

      if ("darwin" == process.platform){
        // even though mac os x supports other shells, i assume that they
        // support the command option as $SHELL -c "command string".
        // some users report that $SHELL fails and that osascript works.
        // https://ss64.com/osx/osascript.html
        // return {
        //   'shell': process.env.SHELL,
        //   'option': '-c'
        // };
        return {
          'shell': 'osascript',
          'option': '-e',
          'command': 'tell app "Terminal" to do script',
        };
      }

      if ("win32" == process.platform) {
        // windows xp and up can be gaurenteed to have the cmd.exe shell.
        // %comspec% should default to %windir%\system32\cmd.exe unless
        // otherwise modified by the end user.
        // https://en.wikipedia.org/wiki/Environment_variable#Windows
        return {
          'shell': process.env.COMSPEC,
          'option': '/c',
          'command': 'start',
        };
      }

      return {}; // redundant LBYL response
    };
  }
}

exports.cli = cli = new Terminal()
exports.has_a_tty = cli.get_tty()

cprocess.js

const terminal = require('./terminal');

const spawn = require('child_process').spawn;

switch (process.platform) {
  case 'linux':
    var echo = spawn(
      terminal.has_a_tty.shell,
      [
        terminal.has_a_tty.option,
        "python",
        "-c",
        "print('Hello, World');input('...enter to continue...')",
      ]
    );
    break;
  case 'win32':
  case 'darwin':
    var echo = spawn(
      terminal.has_a_tty.shell,
      [
        terminal.has_a_tty.option,
        terminal.has_a_tty.command,
        "python",
        "-c",
        "print('Hello, World');input('...enter to continue...')",
      ]
    );
    break;
  default:
    console.error(
      new Error("Error: Could not determine the OS platform type.")
    );
    break;
}

echo.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);
});

echo.stderr.on('data', (data) => {
  console.log(`stderr: ${data}`);
});

echo.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});
\$\endgroup\$
1
\$\begingroup\$

This is honestly a little bit of a backwards approach in current programming culture, where it is now relatively easy (though virtualization, containerization, etc.) to guarantee environmental setup. The one exception being a case where you have a deployed application that must run in an end customer's environment and you aren't able to deploy a virtual container for it to run in. If this is not the case for you, I would guess you time might be better spent in learning/working with virtualization and/or containerization such that you learn to build consistent environments.

That being said, you seem to have opportunity for refactoring. Rather than all these switch cases, does it make sense to build environmental profiles that can be loaded based on appropriate data in process? Where perhaps Terminal is base class that can be extended by GnomeTerminal, etc. for platform specific behaviors?

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Use case isn't really what I'm worried about. I guess I assumed it was obvious that this is executed in an arbitrary local environment for a stand alone process. An example would be a desktop application. I guess you could use it server side as well, although, you would know the platform as you said your self and it wouldn't really matter since it would be easier to just specify it unless you were seeking generalization. \$\endgroup\$ – user52380 May 22 '17 at 22:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy