Creating a project generator with Node

In this post, I’ll walk you through how to create a simple project generator built with NodeJS that can be installed globally on your computer and used to create a starter project wherever you want, whenever you want.

By the end of this tutorial, you’ll be able to type $ generate anywhere on your computer, choose which starter project you want to create and what name you’d like to give it, and it will be created for you.

Before you begin, you should be familiar with:

  • NodeJS
  • JavaScript (including ES6 arrow functions and Promises)
  • NPM
  • Basic Linux command line

Step 1: Get your project templates ready

First things first, gather your project templates together. If you haven’t got any templates, it’s time to make one. I started with a simple NPM project with a spec/index.spec.js file, an index.js file, a package.json, and a .gitignore. I filled these files with generic starter code.

These files live inside a directory named npm-project which lives in a templates directory in the root of your new project.

Step 2: Use the inquirer module for command line interactivity

I used the npm package inquirer to provide command line interactivity. I want to be able to run $ generate from the command line and be asked to choose from a list of possible projects, and give my new project a specific name.

Here we use the fs module (no need to install it with NPM, it comes with Node) to read the contents of your templates directory, returning an array of the names of the starter project(s) inside. Notice that in the fs documentation there are often two versions of methods — an asynchronous one and a synchronous one (e.g. readdir versus readdirSync). Here I’m choosing the synchronous version for simplicity, allowing us to write code in a more linear way. The disadvantage is that we can’t do anything else whilst we’re reading that directory but that’s fine because there’s nothing else I’d really want to do anyway.

Then we setup an array of questions in the format described by the inquirer documentation.

Finally, we call inquirer.prompt , passing in the array of questions. prompt returns a Promise that receives the answers that the user provided, and we can log them to the console.

To test this file, we simply need to run $ node index.js — we can’t run $ generate from our command line yet because we haven’t installed the program, we’re just testing it.

Step 3: Make a new directory and copy the template files

In the future, this project will be installed elsewhere in your computer, along with all your other global npm packages, but the user could be running the program from anywhere and we need to know where. We can use process.cwd() to give us this path.

Inside the current working directory, we create a new directory using the name for the project the user gave us, then we hand over to the function createDirectoryContents which starts by finding out what files/directories are inside the desired template folder.

It will give us an array like this: ['index.js', '.gitignore', 'package.json', 'spec'] — i.e. it only goes one level deep. If we want to figure out what’s inside the spec directory we’d have to call readdirSync again with the path to the spec folder.

For now, we’ll just copy the contents of any files and ignore any directories. We’ll then write the contents back out to another file in the place the user wants their starter project to be.

Step 4: Recursively call createDirectoryContents for any nested directories

If the file in question is in fact not a file but a directory, we’re in luck because we already have a function that knows how to copy the contents of a directory if we tell it where the template files are coming from and where the output files are going. We can just call this function again.

This should be working pretty well when we run it with $ node index.js so let’s install it globally on our computer and get it working properly.

Step 5: Globally installing

We just have a few things to do to prepare for installing our program globally. First, we should add the comment #!/usr/bin/env/ node to the top of our index.js file to indicate that the following script should be interpreted by Node, otherwise it will be interpreted as a shell script. If you have any problems with this, try removing the / from the end:#!/usr/bin/env node

In our package.json we should add the section:

"bin": {
   "generate": "./index.js"
}

where the key “generate” is the command we want to be able to run from the terminal its value is the route to the file that should be executed.

Finally, we should add an .npmignore file in the root of our project which can basically contain the same stuff you’d put in a .gitignore file. If you don’t have an .npmignore file, NPM will turn your .gitignore file into one, which is fine if you want to let it do that.

However, regardless of whether you have an .npmignore file in the root of your project or not, when you do the global install NPM will also recursively go through nested directories in your project and if it finds any .gitignore files it will unhelpfully rename them to .npmignore. This is super annoying if you wanted to have some .gitignore files in your templates.

The simplest workaround to this is just to let NPM go ahead and do this and to force a rename of the file at the point at which we copy it:

And now we can globally install our project, which is really easy! We just need to run: $ npm install -g from the root of the project and then we should have access from anywhere on our computer to the $ generate command. 🎉