Publishing a simple CLI tool on NPM

Developers, like other artisans, are dependent on great tools. From complex tools such as code editors to tools that automate away the mundane, libraries, and simple command-line utilities. For a lot of these, in the JavaScript world, we look to package registries such as NPM. In this post we will look at a recent utility I published to NPM and go over the steps you need to take to publish your own.

calavera@hugo-skeleton

Before we dive into the steps of creating a utility and publishing it, let us take a quick detour and look at the utility I published.

I often build sites using Hugo, a static site generator written in Go. While Hugo has a bountifull collection of themes, I rarely use a theme, but instead build bespoke sites meant for a defined brand or product.

This does mean that there is some scaffolding needed when creating a new Hugo site. While running hugo new site ./sitename does generate some basics, it is really just the basics. In fact, other than config.toml and the default Markdown file in archetypes, it is pretty much a set of empty base folders.

Now, you could keep a folder of your “bootstrap” files on your local machine and copy and paste everything from source to destination but, where is the fun in that? 😀 Also, this is centralised to your current computer, cannot be easily accessed from a different context, cannot be used by others, nor con it be contributed to by the wider Hugo community.

We are then missing two pieces.

  1. Add the scaffolding to source control and publish it to a service such as GitHub
  2. Make it easilly “installable” from anywhere.

The second piece is what I will the cover here.

The files

While I won't go into the details of what make up Hugo Skeleton, you can see the source on GitHub. In essence though, it is made up of the following:

The final piece it contains is the utility that will allow us to easily add the above scaffolding to any new Hugo site.

The utility

The utility is a simple JavaScript file that walks the directory structure, filters out an ignored list of files and writes the resulting files an folders to the output folder the user specified.

To make this file executable by Node we add the following shebang line as the very first line in our JavaScript file:

#!/usr/bin/env node

Intead of going into the details here, the gist is:

the very first line in an executable plain-text file on Unix-like platforms that tells the system what interpreter to pass that file to for execution, via the command line following the magic #! prefix (called shebang).

For more details, please read this great answer on Stackoverflow.

Getting ready to publish

Now that we have our utility ready we need to prepare our entire package for publishing. Publishing here refers to publishing to the npm registry which is from where we will also install and run our utility.

In the project's package.json I have the following two entries:

"bin": "./src/index.js",

bin is where we tell npm that we have an executable that we would like added to the PATH when using npm.

NOTE: Seeing that I have a single executable this works just fine. Sometimes however, you might bundle a couple of executables as a single package. In those instances you would opt for the object style notation as shown in the docs linked above.

While we want to publish most of the content of our repository there are some things we want to exclude. To do this we have a couple of options. Firstly, if you already have a .gitignore that ignores all the assets you want to keep out of your published package, there is nothing more to do.

However, what you want to keep out of your published package does not always match what you want to keep, or keep out of, your code repository. In those instances, we can add an additional .npmignore file.

I my case I had only one file that was not currently added to my .gitignore and it just so happens I also do not want it to be added to the repo. For demonstration purposes though, let's say I wanted it in my repository but exclude it from the npm package. In that case you would add a new file and name it .npmignore and specify the file(s) ti ignore. For example:

.dccache

There are a lot of things npm will automatically exclude by default and also some files it will always inlcude. You can read the details in their docs.

Publish it!

Before we actually publish, it is good to make sure that it will actually install and work as intended. The docs provide a variety of options but for my purpose here, I tested it a little differently. Instead of using link I used npx directly. Here is what I did.

I first created a new Hugo site inside a tmp directory using hugo site new ./tmp and changed directory into this new directory. From here I ran:

npx ../calavera/hugo-skeleton --output=.

npx - tl;dr “Run a command from a local or remote npm package”

After running into and fixing a couple of bugs(that is what testing is for 😀), I am now ready to publish this.

If you do not already have an account on npmjs.org you will need to create one and then add and authenticate this user locally using the npm adduser command. You can confirm that you are logged in by running npm whoami which will output something like:

❯ npm whoami
schalkneethling

We are ready to publish!

NOTE: The name for this project is set to @calavera/hugo-skeleton which may be new to some. The reason for this is because I am publishing this as a scoped package.

Also, if you were following along with your CLI, make sure that everything has been commited and pushed to source control and that you are on the main branch of your repository.

After all of the above, publishing is a little anticlimactic 🙃

Run the publish command

If you want to get an idea of what will be published and other related details, you can first run npm publish --dry-run

npm publish --access public

The --acess public piece is required for scoped packages on initial publish to tell the registry that this is meant to be public and not private. If you do not specify this, you might see an error like the following:

402 Payment Required - PUT https://registry.npmjs.org/@project-calavera%2fhugo-skeleton - You must sign up for private packages

If all goes well your output should end with a confirmation like this:

+ @project-calavera/hugo-skeleton@0.0.1

You will also recieve a confirmation email at the email you address you signed up with on npmjs.com. If I now navigate to https://www.npmjs.com/package/@project-calavera/hugo-skeleton I can see that the package has indeed been published.

Only one thing left to do. Test the published package on a new Hugo site.

hugo new site tmp
cd tmp
npx @project-calavera/hugo-skeleton@latest output=.

🎉 ~ It works ~ 🎉

I hope you found this writeup useful and would love to hear about the tools and utilities you make. Direct message me on Twitter @schalkneethling