“This is the second day of my participation in the August More Text Challenge.

preface

This article will take you from scratch to implement a command-line scaffolding tool for initializing projects and publishing to NPM. First of all, VueCLI, which we are familiar with, uses the command line tool to quickly generate the project directory. In this way, we only need to input commands in the command line before each development project, and then we can quickly generate the project, which is very convenient. So how does such a convenient command-line tool come about? Now let’s get into the actual combat.

In actual combat

I will use strView-CLI, a scaffolding tool I developed before, to show how to implement it. The source address for this scaffold tool is as follows:

https://github.com/maomincoding/strview-cli
Copy the code

The first step

First, let’s create a folder called strviewCli.

mkdir strviewCli
Copy the code

Then, initialize the project in the folder

npm init
Copy the code

After that, a package.json file is automatically generated.

The second step

Let’s create another folder, bin.

mkdir bin
Copy the code

Next, in the folder we create an index.js file, config folder, utils folder.

touch index.js
Copy the code
mkdir config
Copy the code
mkdir utils
Copy the code

Finally, create an index.js file in the config file and a checkDire. Js file in the utils folder.

touch index.js
Copy the code
touch checkDire.js
Copy the code

Currently, our file directory structure is

- bin
 -- config
  --- index.js
 -- utils
  --- checkDire.js
 -- index.js
- package.json
Copy the code

Finally, in the root directory, we create a.gitignore file and readme.md.

Finally, the command line tool project directory is as follows:

- bin
 -- config
  --- index.js
 -- utils
  --- checkDire.js
 -- index.js
- package.json
- README.md
- .gitignore
Copy the code

The third step

In the previous two steps, we laid out the basic structure of our command line tool. Below, we will inject soul into each file one by one

First, the.gitignore file and readme.md, which won’t be covered here, can be added to suit your needs.

Second, the package.json file is introduced in detail. Here is package.json, which I wrote myself, with a few important fields to note.

  • Name: indicates the project name
  • Version: indicates the version number
  • Description: Project description
  • Main: entry file
  • Author: the author
  • Keywords:
  • Bin: script execution entry
  • Repository: code repository
  • License: license
  • Private: private
  • Dependencies: rely on
  • Browserslist: Specifies the scope of the target browser for the project
{
  "name": "strview-cli"."version": 1.1.1 ""."description": "Strview.js project scaffolding tools."."main": "index.js"."author": {
		"name": "maomincoding"."email": "[email protected]"."url": "https://www.maomin.club"
   },
  "keywords": [
	"strview"."strview.js"."strview-app"."strview-cli"]."bin": {
		"strview-cli": "./bin/index.js"
	},
  "repository": {
		"type": "git"."url": "https://github.com/maomincoding/strview-cli.git"
	},
  "license": "MIT"."private": false."dependencies": {
		"chalk": "^ 4.0.0"."commander": "^ 5.0.0"."fs-extra": "^ 9.0.0"."inquirer": "^ 7.1.0"
    },
	"browserslist": [
		"1%" >."last 2 versions"]}Copy the code

Json attributes such as Name, Version, Description, main, Author, keywords, Repository, and License can be defined according to your own requirements.

You may see several dependencies in the Dependencies property, namely Chalk, Commander, FS-extra, and Inquirer. They are mentioned here and discussed in more detail below. Note, however, that the FS-extra module adds file system methods that are not included in the native FS module and adds promise support to the FS method. It also uses elegant FS to prevent EMFILE errors. It should be a replacement for FS.

The fourth step

Next, we’ll go into the bin folder, and then we need to edit the config\index.js file first.

module.exports = {
	npmUrl: 'https://registry.npmjs.org/strview-cli'.promptTypeList: [{type: 'list'.message: 'Please select the template type to pull:'.name: 'type'.choices: [{name: 'strview-app'.value: {
						url: 'https://github.com/maomincoding/strview-app.git'.gitName: 'strview-app'.val: 'strview-app',},},],},};Copy the code

The above code exports an object with two attributes: npmUrl and promptTypeList.

The npmUrl property is the address that the command line tool submits to the NPM. How do I get this address? You need to follow these steps:

Login NPM

npm login
Copy the code

Enter your username, password, and email address in sequence.

Published to the NPM

npm publish
Copy the code

After the release is successful, the version number is displayed. Remember to change the version number with each release or you will get an error.

After normal publishing, you can go to the NPM site and search for the name of your command-line tool. For example, my command-line tool strview-CLI. Website: https://registry.npmjs.org/strview-cli.

The promptTypeList property has a Choices property, which is an array that you can configure for your remote project repository. Each element of an array is an object, like this one

{
	name: 'strview-app'.value: {
			url: 'https://github.com/maomincoding/strview-app.git'.gitName: 'strview-app'.val: 'strview-app',}}Copy the code

The name attribute is the project name of your project, and the value attribute is an object with three attributes: URL, gitName, and val, which represent the remote repository address, repository name, and value respectively. You can configure it according to your needs. Here is my own Strview-app.

This is the configuration of the config\index.js file.

Step 5

Next, while we are still in the bin folder, we will edit the utils\checkDire. Js file.

const fs = require("fs");
const chalk = require("chalk");

module.exports = function (dir, name) {
  let isExists = fs.existsSync(dir);
  if (isExists) {
    console.log(
      chalk.red(
        `The ${name} project already exists in  directory. Please try to use another projectName`)); process.exit(1); }};Copy the code

There are no custom parts to this file, you just need to use it directly. Here we see the introduction of two modules, namely FS and Chalk. The fs module built into Node.js is the file system module, which is responsible for reading and writing files. The Chalk module beautifies the command line output style so that the output command is not monotonous.

We see here that we export a function that takes two arguments: dir and name. Let’s forget about this function for a moment, but we just need to pass in two arguments.

Step 6

The bin\index.js file is a very important entry file for the command line tool. Again, you don’t need to customize it, you can just use it.


#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const commander = require('commander');
const inquirer = require('inquirer');
const checkDire = require('./utils/checkDire.js');
const { exec } = require('child_process');
const { version } = require('.. /package.json');
const { promptTypeList } = require('./config');

commander
	.version(version, '-v, --version')
	.command('init <projectName>')
	.alias('i')
	.description('Enter the project name and initialize the project template')
	.action(async (projectName) => {
		await checkDire(path.join(process.cwd(), projectName), projectName);
		inquirer.prompt(promptTypeList).then((result) = > {
			const { url, gitName, val } = result.type;
			console.log(
				'The template type information you selected is as follows: ' + val
			);
			console.log('Project initialization copy acquisition... ');
			if(! url) {console.log(
					chalk.red(`${val}This type is not supported at the moment... `)); process.exit(1);
			}
			exec('git clone ' + url, function (error, stdout, stderr) {
				if(error ! = =null) {
					console.log(chalk.red(`clone fail,${error}`));
					return;
				}
				fs.rename(gitName, projectName, (err) = > {
					if (err) {
						exec('rm -rf ' + gitName, function (err, out) {});console.log(
							chalk.red(`The ${projectName} project template already exist`)); }else {
						if (fs.existsSync(`. /${projectName}/.git/config`)) {
							exec('git remote rm origin', { cwd: `. /${projectName}` });
							console.log(
								chalk.green(
									` ✔ The${projectName} project template successfully create`)); }}}); }); }); }); commander.parse(process.argv);Copy the code

Starting at the beginning, we’ll see the introduction of FS, Path, chalk, COMMANDER, Inquirer, child_process.

The PATH module provides utilities for handling paths to files and directories.

Commander is the complete Node.js command-line solution. It can be used in a number of ways. For details, see the Commander Documentation:

https://github.com/tj/commander.js/blob/master/Readme_zh-CN.md
Copy the code

Inquirer is a collection of generic interactive command-line user interfaces. There was a lot of interaction with the user when you started creating package.json files with NPM init, and now most projects are built with scaffolding. The most obvious thing to do with scaffolding is to interact with the command line. If you want to build your own scaffolding or at some point interact with the user, This is where inquier.js comes in.

The child_process module is a child process module of NodeJS that can be used to create a child process and perform some tasks. For example, you can call shell commands directly from inside JS.

After the introduction of the modules, I will introduce the following code. You can see that most of the code below uses the Commander method.

First, the commander. Version (version, ‘-v, –version’),.version() methods can set the version, after which you can use either the -v or –version commands to view the version.

Commands can be configured with.command(‘init ‘). This means initializing your project name, which you can set according to your needs. Alias (‘ I ‘) can also be used to initialize the configuration command, NPM init .

.description(‘Enter the project name and initialize the project template’). The description method is a description of the project name that was initialized above.

.action((projectName, cmd) => {… } the action method is the callback function that defines the command, and we see that it uses the async/await keyword group to handle asynchracy. Await checkDire(path.join(process.cwd(), projectName), projectName); Pass in two parameters: the directory where the project resides and the project name. We’ll start by analyzing the checkDire method, which was used in the utils\checkDire.

module.exports = function (dir, name) {
  let isExists = fs.existsSync(dir);
  if (isExists) {
    console.log(
      chalk.red(
        `The ${name} project already exists in  directory. Please try to use another projectName`)); process.exit(1); }};Copy the code

Fs.existssync (dir) detects the existence of a directory synchronously. Return true if the directory exists, false if it does not. If so, execute the following prompts and exit the terminating process.

Next, we go back to the bin\index.js file. Moving down to the inquirer.prompt() method, the main function of this method is to start the prompt interface (query session). The first parameter is the problem (array) containing the problem object (using the reactive interface, you can also pass an rx.Observable instance). Here we pass in the promptTypeList property in the config\index.js file, which is an array.

	promptTypeList: [
		{
			type: 'list'.message: 'Please select the template type to pull:'.name: 'type'.choices: [{name: 'strview-app'.value: {
						url: 'https://github.com/maomincoding/strview-app.git'.gitName: 'strview-app'.val: 'strview-app',},},],},Copy the code

The inquirer. Prompt () method returns a Promise object, so the then() method is used to retrieve the returned data. Then we get url, gitName and val respectively through deconstruction.

const { url, gitName, val } = result.type;
Copy the code

Then, it’s time to print the command and execute it. I break down the rest of the code in two parts, the first of which is as follows:


console.log('The template type information you selected is as follows: ' + val);
console.log('Project initialization copy acquisition... ');

if(! url) {console.log(chalk.red(`${val}This type is not supported at the moment... `));
	process.exit(1);
}

Copy the code

Here we have a statement to determine whether the remote warehouse address is false, if it is false, execute the prompt and exit the termination process.

Next, we analyze the second part:

exec('git clone ' + url, function (error, stdout, stderr) {
	if(error ! = =null) {
		console.log(chalk.red(`clone fail,${error}`));
		return;
	}
	fs.rename(gitName, projectName, (err) = > {
		if (err) {
			exec('rm -rf ' + gitName, function (err, out) {});console.log(chalk.red(`The ${projectName} project template already exist`));
		} else {
			if (fs.existsSync(`. /${projectName}/.git/config`)) {
				exec('git remote rm origin', { cwd: `. /${projectName}` });
				console.log(
					chalk.green(` ✔ The${projectName} project template successfully create`)); }}}); }Copy the code

This part is mainly about executing orders, which is the most critical part. This section starts with the exec() method, whose first argument is the command to execute and the second argument is the callback function.

First, we execute exec(‘git clone ‘+ URL) to download the remote project, then we go to the callback function and exit if there is an error. Otherwise, the file is renamed using the fs.rename() method. Because the name of the remote repository we downloaded may not be the same as the name of our initial configuration.

If not, delete the remote project project directory. /${projectName}/.git/config = ‘git remote rm origin’, {CWD:./${projectName}}); Command to delete the remote warehouse address. This is because you need to customize the repository address instead of using the downloaded repository address directly. Finally, a message is displayed indicating that the creation succeeded.

Last row. commander.parse(process.argv); The first argument to.parse() in this line is an array of strings to parse, or you can omit the argument and use process.argv instead. Specified according to node convention.

Step 7

At this point, all the configuration files are configured. If you want to open source, you can generate a LICENSE file online. This file is a software license and can be automatically generated on Github.

Finally, we are ready to release our command-line tool. Note that you need to change your version number before releasing it. If it was 1.0.0, now it can be 2.0.0. There are different ways to define these three numbers. The first part is the major version number, which changes to indicate a large change that is incompatible with the previous version. The second part is the second version number, which changes to indicate new functionality and backward compatibility. The third part is the revision number, which changes to indicate a bug fix and backward compatibility.

npm publish
Copy the code

The release succeeded.

Step 8

Strview-cli is used as an example.

  1. You can install your scaffolding globally.
npm i strview-cli -g
Copy the code
  1. After the installation is complete, you can view the version.
strview-cli -v
Copy the code
  1. Finally, it’s time to initialize the project,<projectName>Is a custom project name.
strview-cli init <projectName>
Copy the code

or

strview-cli i <projectName>
Copy the code

conclusion

Thanks for reading. I hope I haven’t wasted your time.

You can encapsulate a common project project and initialize your project in this way. In this way, will appear to have forced case!! Ha ha ha ~

About the author

Author: Vam’s Golden Bean Road.

CSDN blog star of the Year 2019, CSDN blog has reached millions of visitors. Nuggets blog post repeatedly pushed to the home page, the total page view has reached hundreds of thousands.

In addition, my public number: front-end experience robbed road, the public continues to update the latest front-end technology and related technical articles. Welcome to pay attention to my public number, let us together in front of the road experience rob it! Go!