How I built my own command line interface with NodeJS


In this article I will show you how  I created my own command lite interface with NodeJs. In my case I’m doing a CLI for Stencil with the following features to begin with,

  • Create a new Stencil app from an app starter template.
  • Create a new Stencil component or library with the standalone component template.
  • Generate a new Stencil component in an existing project.
  • Shortcuts for npm commands.

First I created a new GitHub repository for the CLI (Command Line Interface).
Available over here:

Setup the NodeJS environment

Now when you have a git repository, git clone it down to your computer,

git clone stencil-cli

Since our project is basically empty at the moment let’s create a npm package project with the command ‘npm init‘. It will ask you a few questions about your project.

We will only need one dependency in our project and it’s called ‘shelljs‘ it’s a great npm package that makes it very easy to run shell commands in our NodeJS application.

Install it with command,

npm install --save shelljs

Now we will create the main JavaScript file that will do all the magic for us later on. Create a folder in your project named ‘bin’ and in the folder create the main js file, in my case I named it ‘stencil-cli.js’.

Now we need to update the package.json file with the information where NodeJS can find the main script file. Add the following to the package.json,

Replace the path to wherever you created your main script file.

Building the commands

The next step is to write the code for the commands that we want the CLI to be able to do.

First the script needs to initialize some dependencies and check that all required parameters is given.

The first line is to define that this is a node JavaScript file. The second line loads the shelljs library into the ‘shell’ variable. Then we also need to load the path library into a ‘path’ variable, it’s used to locate files on the filesystem among other files related tasks.

Then we define a ‘cliPath’ variable that will be used later to find the component template files. So the path.join takes the directory of the current script file and one folder above that to get the projects root directory.

The last check we need to do before we can start building any commands is a check if any command was given, otherwise the CLI have nothing to do and will quit itself. This is if you run the command ‘stencil’ without any arguments it will print out “Please tell me what you want me todo!”, an improvement here could be printing some kind of help page instead.

Create new app command

The first command we want is one that creates a new Stencil app project, the command for that will be ‘stencil start-app my-app’ where my-app is the name of your app.

It will look something like this:

The first thing we check is that the second argument of the command is ‘start-app’, then we know (assumes?) that the user want to create a new Stencil app.

Then we check the first requirement that the user needs to have git installed or else we will not be able to download the app starter project from GitHub.

Then we set the ‘projectName’ variable to the third argument of the command, if that argument is missing the script will quit with an error.

Now we have everything we need to start creating the project, the ‘shell.exec’ command runs a command in the same way you do in a normal terminal. So the first command we run is ‘git clone ‘ + projectName’, that clones down the stencil starter app to a folder with your project name.

The next command ‘’ we use to change directory to the newly created project folder. Then we remove the connection from the origin GitHub project, since we probably don’ want to update the template.

The ‘shell.echo’ by the way is just a way to print information to the end users shell window and ‘shell.exit’ is how we end the application, if we end it with 0 we tell the user and whatever is listening (Jenkins perhaps) that the process ended successfully, if we end it with a 1 it means that the command ended badly. When we end it with a failure it’s always a good idea to be as clear as possible to the end user about what went wrong and it’s better to share to many details than to share to few details.

So the next thing we do is finding all the ‘package.json’ files with ‘’, hopefully just one in this case. For each file we find we want to change the project name from ‘@stencil/starter’ to your given ‘projectName’.

At last we do an ‘npm install’ and we are ready to start working on that new app and that we won’t do in this article.

Create new standalone component command

The second command we want is one that creates a new Stencil component/library project, the command for that will be ‘stencil start-component my-component’ where my-component is the name of your component or library.

It will look something like this:

It’s pretty much the same logic here, just that we download another project template this time and have a few more files to replace our project name in.

Generate Stencil component

The third command we want is one that creates a new Stencil component inside an existing project, the command for that will be ‘stencil generate my-component’ where my-component is the name of your component.

It will look something like this:

Here we have a little more complex logic to figure out. Since a component have two different formats, we always have to convert the given component name to the formats, MyComponent and my-component. So we add the given name to two variables, ‘componentName’ setting the first character to uppercase, ‘componentTag’, making it all lowercase. Then we check if the given component have any dash in it, if it do we split it up by the dash sign and remove the dash and make every parts first character uppercase and then put it together again and with that we get the proper ‘componentName’. If there is no dash we instead assumes the user entered the component name in the format ‘MyComponent’, then we instead split the string at every uppercase character with the regexp ‘/(?=[A-Z])/’ and make them lowercase and add a dash infront of each part, so in my example we will get ‘my-component’.

Once we have figured out that detail we move on to creating the directory for the component in the projects ‘src/components/’ folder with the command ‘shell.mkdir’. Then we copy the template files with command ‘shell.cp’ to the correct locations. Then we update all the component files with the right class name and tag name and that’s it you can start modifying the empty component anyway you like (as long as it compiles)!

Command shortcuts

The last part I did was making shortcuts to some standard npm commands.

Command ‘stencil start’ runs a regular ‘npm start’ that starts the Stencil development server.

I planned to have a ‘stencil test’ command that would be running the Jest unit tests, but Jest did not like being run in a NodeJS sub-process apparently. So instead I added a warning there. If you have any suggestions on how to make it work, just let me know or make a pull request with the solution. 🙂

Then the last part is just a wildcard were you can run ‘stencil anything’ and it will run ‘npm run anything’.


That ends this article about creating command line interfaces using NodeJs, I hope you found some value in it. You can use my stencil-cli from right now over here:

You can take a look at the projects source over at GitHub on this URL:

If you now want to publish your new CLI to the nmpjs repository you can read my article about that here: How I created and published my first Stencil component



Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Create a website or blog at

Up ↑

%d bloggers like this: