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}`);
});
200_success
145k22 gold badges190 silver badges478 bronze badges
asked May 22, 2017 at 21:17
\$\endgroup\$

1 Answer 1

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?

answered May 22, 2017 at 21:53
\$\endgroup\$
1
  • \$\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\$ Commented May 22, 2017 at 22:11

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.