Get in touch
Thank you
We will get back to you as soon as possible
.pdf, .docx, .odt, .rtf, .txt, .pptx (max size 5 MB)

21.9.2017

5 min read

Implementing CLI for Electron apps

Electron is gaining more and more popularity and practically became the go-to platform for building cross-platform desktop applications. It appears to be one of the most convenient options from both the developer and business sides of view - it helps drastically cut development costs by being truly cross-platform and is fun to deal with utilizing the latest web technologies.

I have been leading the desktop application development using Electron for over three years and a half now (my teammate covered our lessons of utilizing Electron in this article. We have been able to implement lots of functionality and a big part of it was not just adding more stuff to be rendered but also providing ways for users to interact with the app in various handy ways. One of them was through the console.

Why would you need CLI for a desktop app?

Well, this might be a tool for your power users. CLI can help automate some tasks so that one would not have to open the app and manually perform them. For example, convert file formats, import data or synchronize state.

Having a console interface gives the possibility to integrate your app as one step of a continuous integration process. This can greatly improve the impact your application has on your users’ productivity.

Furthermore, CLI is a convenient way to perform batch operations - i.e. perform the same action to an array of files. To open up even more possibilities for your users, you can also tweak your app to support Unix-style pipes, which would allow them to perform transformations on data in a language they already know.

CLI in Node.js

JavaScript has achieved an unparalleled position of being a truly universal language. It is executed on all kinds of devices and in all kinds of environments. Console applications are no exception. Node and npm have made it easy for us to develop applications with a console interface and install them into the system.

The main pillar of CLI is reading user input. There are plenty of modules helping you do this job and one of the most prominent is yargs. It allows you to declare commands and options your application accepts with a concise configuration. Here’s an example:

require('yargs')
    .command({
        command: 'print',
        desc: 'Print the documentation',
        builder: (yargs) => {
            yargs
                .options('file', {
                    describe: 'File to be printed'
                })
            })
        },
        handler: (argv) => {            
            if (argv.verbose) console.info(`Print :${argv.file}`)
            print(argv.file)
        }
    })
    .option('verbose', {
        alias: 'v',
        default: false
    })
    .argv;

I’ve written down more tips for creating CLI apps with Node.js in this other article. Feel free to take a look if you are interested.

Electron CLI implementation

So, essentially, Electron consists of 2 types of processes - main and renderer - and both of them are based on Node.js. Hence, we should be able to call our app through the command-line interface just as we would do with a regular Node.js app.

The basic check to learn if your application was called via CLI is as simple as checking the number of arguments passed when calling the application:

function checkIfCalledViaCLI(args){
    if(args && args.length > 1){
        return true;
    }
    return false;
}

You may want to add some more conditions due to the way your app works, but it will not get much more sophisticated anyway.

Then we should execute the condition on app ready to learn if it is indeed called from terminal:

app.whenReady().then(() => {
    let isCalledViaCLI = checkIfCalledViaCLI(process.argv);
    
    if(isCalledViaCLI) {
        mainWindow = new BrowserWindow({ show: false, width: 0, height: 0});
    } else {
        mainWindow = new BrowserWindow({ show: true, width: 1050, height: 600});
    }

    mainWindow.once('ready-to-show', () => {
        if(isCalledViaCLI) {
            mainWindow.hide();
        } else {
            mainWindow.show();
            mainWindow.maximize();
        }
    })

});

In case we want the app to function in CLI mode, we should just set the BrowserWindow show option to false and window dimensions to 0.

After adding these code snippets to your app, you will be able to run the app just like this:

yourapp --fabulous --options

However, to run the app correctly on Windows, you should tweak the command to look like this:

start /wait yourapp --fabulous --options

This will keep your application running within the same session until it explicitly stops the corresponding process.

And finally, on Mac things are a bit trickier, you should run the CLI in the following way:

yourapp.app/Contents/MacOS/yourapp --fabulous --options

Nice touches to add

Help

Have you ever found yourself staring at the cursor in the command line trying to remember what command you need to enter? It is only fair then to help your users recall the command without needing to leave the terminal window.

Take a look at how npm does it while having dozens of commands:

Implementing CLI for Electron apps-1

You can easily implement a similar help screen with the help of the yargs module we looked at above in the article.

Spinner

Your app most probably has some long-running operations. Normally, you would remedy it with a spinner. So why don’t you utilize one in a console as well? For example, take a look at this module.

Logging

In the absence of a graphical UI, the text is the only medium with which you can give feedback to your user. Therefore, you should add elaborate logging. You might also want to introduce log levels as an option in order to have the ability to choose just the right amount of output you want to see from the app:

1: don't log anything

2: log errors only

3: log errors and info

4: log errors and info and show a spinner

Debugging

Finally, you should help yourself by allowing debug options to be passed. Being built on top of Node and Chromium, Electron supports a rich pool of developer tools available at your disposal to easily debug the app. The most prominent of them is a Visual Studio Code for backend debugging (or any other app implementing Inspector or Debugging Protocol for Node).

To plug into your app, debuggers need to pass some CLI options to it. However, if you create your own handler for the command-line interface, their options will not be passed through. One way to achieve this is to simply search for these options in your arguments and, if found, execute the app in the normal mode (not CLI):

function checkIfCalledViaCLI(args){
    if(args && args.length > 1){
        if(args[1].includes('--debug-brk') ||
args[1].includes('--inspect-brk') || args[1].includes('--remote-debugging-port')){
            return false;
        }
        return true;
    }
    return false;
}

Now you know how easy it actually is to implement a CLI mode within an Electron app. In my opinion, it is one more testimony to the power of the JavaScript ecosystem.

0 Comments
name *
email *