Publishing a simple CLI tool on NPM
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.
- Add the scaffolding to source control and publish it to a service such as GitHub
- Make it easilly “installable” from anywhere.
The second piece is what I will the cover here.
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:
archetypes- Nothing new here
assets/sass- Some basic SASS code I find useful for pretty much all projects.
content- Includes the base frontmatter for the index page
layouts- Some useful base layouts and partials to get you started
static/media- Base static assets such as favicons and iconography
.eslintrc.json- A base ESLint config
.prettierrc- Default Prettier config file
config.toml- An expanded version of the default you get with
hugo new site
The final piece it contains is the utility that will allow us to easily add the above scaffolding to any new Hugo site.
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 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
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:
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.
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!
namefor this project is set to
@calavera/hugo-skeletonwhich 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
mainbranch 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
--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:
If all goes well your output should end with a confirmation like this:+ @firstname.lastname@example.org
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