Bin and main

In our package.json we need to set the entry point of our app (main and bin). This will be our compiled index.js file in the lib folder: ./lib/index.js .

advanced-cli is the command which you use to eventually call you CLI.

Photo by Jack Cain on unsplash, code block by Carbon

Scripts

Now we need some scripts to make it easy for ourselves. We have ten scripts:

npm start —runs our build and global script together. Use this to start your CLI

—runs our and script together. Use this to start your CLI npm run build —runs our clean:some script and transpiles our TypeScript files to JavaScript files in lib folder

—runs our script and transpiles our TypeScript files to JavaScript files in folder npm run global — installs our CLI globally and runs the CLI

— installs our CLI globally and runs the CLI npm run clean:some —removes lib and docs folders

—removes and folders npm run clean:all —removes node_modules , package-lock.json , lib and docs folders and file

—removes , , and folders and file npm run refresh —runs our clean:all script and runs npm install

—runs our script and runs npm run tscov —checks for at least 90% type coverage

—checks for at least 90% type coverage npm run tscov:d —see missing type coverage

—see missing type coverage npm run publish-package —runs our build and npm publish scripts

—runs our and scripts npm run docs —generates automated documentation in docs folder

Write the following into the package.json :

TSconfig

For our CLI we some TypeScript configurations set in a file named tsconfig.json , create this file in the root and write the following configurations into it:

Environment

Create a file named advanced-cli in a folder called bin . With this content:

Coding time!

Folder structure — 1

Make a src folder with the following folders in it:

models

questions

templates

utils

Also make the following files in the src folder:

index.ts

cli.ts

Models — 2

Next we are going to start making some interfaces and enums inside our models folder, in the file named answer-choice.ts . I’ve chosen for the enums instead of constants, because of the properties in it have similarities with other properties. Therefore should be grouped together. The name of the Answer and choice interfaces are self-explaining.

The next file; filename.ts contains the filenames including their extension. In this case .md for markdown.

Our last file; path.ts does have the specific paths of where the file needs to be generated in. This is different for gitlab and github .

Questions — 3

Now we are going to use our enums and interfaces in our questions. Create a file named provider.question.ts inside our questions folder. Import inquirer , this provides us to ask questions in the command line and take actions based on an answer. We have an async function that contains a list and a prompt. It returns an answer.

Create another file called gitlab-file.question.ts :

And the final question file called github-file.question.ts :

When you have more files per provider, you can now easily add them to the listOfFiles .

Export the questions in an index.ts file:

Utils — 4

Optional: utils are optional. In my own project I used one util to check if a file/folder already exists, if not create a file/folder. Another util was for reusable and ‘pretty’ console logs (error, success, info, etc.). They can be found here.

Templates — 5

Inside our templates folder we create a folder called default . Inside the default folder we create a file default.template.ts . As the file name is saying, this will be our default template where different files will be generated. We make use of dependency injection with inversify . Our template can be Injected in to other templates because of the @injectable decorator. To create a file on your local machine, we use fs.writeFile from fs-extra .

Note: the original generateFile method contains if file/folder exists or not and option to overwrite files.

Create another folder in templates called gitlab . Inside this folder create a file called merge-request.template.ts . In our template class we have three properties;

fileName — we use our previously made enum for our file name with extension

hasPath — set to true if the generated file should be not in the root of your project

pathOfFile — specify the path of the file where it should belong

We again make use of the @injectable decorator, but now also of the @inject decorator to inject our default template.

A public method where we call the public reusable generateFile method of our default template. And a private method where we paste our markdown file content.

Next in our templates folder create a folder called github . Inside this folder create a file called feature-request.template.ts . This file is very similar to the previous one. But has different property values and file content. We can now easily make more templates for future files.

cli.ts — 6

The second to last file is cli.ts . We are injecting our two templates in the constructor and call the executeCLI method. In this method we ask the user about which provider he want to choose. If he choose for Github, then our githubActions method will be called. In here the second question is asked; which file a user want to generate. In this case we only have one file. After choosing the file it will be generated. The same applies if we had chosen Gitlab.

index.ts — 7

Our final file index.ts , is where our CLI is initialized. We need reflect-metadata for the custom decorators used in this project. The index function contains a container, to this we bind all the classes (which contain the injectable decorator) as a singleton scope. At the end we return the CLI where our questions will be asked.