background
Some time ago, I received a demand to develop a business component package to provide business group partners to use. Similar packages have been developed before, and aside from the difference in what the packages do, some of the content is pretty much the same. Git commit specifications, mandatory files such as SRC /index.js, etc.
If you want to create a new package project, most of the files mentioned above will be copied from the existing project to modify, and then based on the following development. And the next time there’s a new need like this, the cycle starts… Orz
In that case, why don’t I get a scaffolding tool to automate all this manual work
As the saying goes: the circle is better than... Go back and make your own web. Fuck!Copy the code
The preparatory work
Three simple steps to prepare
Step 1: Register an NPM account for sending packages step 2: mkdir cli-tool && CD cli-tool && NPM init -y Step 3: Then fill in the 'package.json' file with some basic informationCopy the code
{
"name": "cli-tool"."version": "1.0.0"."description": "A tool for cli creation"."main": "index.js"."scripts": {
"pub:beta": "npm publish --tag=beta"."pub": "npm publish"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"keywords": [
"create"."cli"]."author": "your name"."license": "ISC"
}
Copy the code
Start building the wheel
Step 1: Define the command
I’m sure you’ve all used scaffolding, and scaffolding tools often provide script commands to perform some operations. For example, –version for version number, –help for help information, and so on. In this step, we also define the commands for our own scaffolding.
Creating a script File
First, we create the bin directory under the root of the project, and then create the file ct.js that defines the script in that folder. Once created, define the file corresponding to the command in package.json:
{
// ...
"bin": {
"ct": "bin/ct.js"}}Copy the code
Where, ct specifies the command we need to input when using scaffolding in the future, bin/ct.js specifies the script file path to execute after the command is input.
Write a script
We define the following two basic commands in the script:
Run the --version/-v command to view the version information. 2. The command used to create the project: --create/-cCopy the code
#! /usr/bin/env node
const process = require('process')
const packageJson = require('.. /package.json')
/* * the first and second parameters of the command are path information, so we do not need to use the third parameter */
const argvs = process.argv.slice(2)
switch (argvs[0] | |'-c') {
/** View the current plug-in version */
case '-v':
case '--version':
console.log(`v${packageJson.version}`)
break
/** Create project */
case '-c':
case '--create':
console.log('Create project')
break
}
Copy the code
After writing, if you want to verify on the command line, you can type the following command:
npm link
Copy the code
Then enter it on the command line
ct -v
Copy the code
We can then see the printed version information in the command line tool:
This completes our basic command definition.
Step 2: Implement the project creation logic
Next we will start creating the project. Before we start implementing the concrete logic, we can outline the steps we might take:
Step 1: Create the project Step 2: Create the basic files and folders required for the project. For example, package.json file or SRC directory. Step 3: After the package is created, go to the corresponding directory to install the packageCopy the code
Based on the above steps, let’s implement the concrete logic.
Create a project
First, we create an index.js file in the SRC /create path to write the specific creation logic.
module.exports.run = () = > {}
Copy the code
We then introduce this method in the bin/ct.js file and call it when -c/–create is executed:
const { run } = require('.. /src/create/index')
/ /... Omit a lot of code
/** Create package project */
case '-c':
case '--create':
run()
break
/ /... Omit a lot of code
Copy the code
Following the vue-CLI model, when creating a project, we can use interactive commands to make the creation process more user-friendly. Here we will use one package: inquirer. It allows us to define interactive commands.
First, we install it locally
yarn add -D inquirer
Copy the code
After that, in the SRC/Constants directory, we create questions.js to define specific steps:
/** * Defines an interactive command line problem */
const DEFAULT_QUESTIONS = [{
type: 'input'.name: 'PROJECT_NAME'.message: 'Project name:'.validate: function (name) {
const done = this.async()
// If the directory already exists, prompt you to change the directory name
if ([' '.null.undefined].includes(name)) {
done('Please enter the project name! '.true)
return
}
if (fs.existsSync(name)) {
done(`The directory "${name}" is exist!! Please reset the dirname.`.true)
return
}
done(null.true)}}, {type: 'input'.name: 'PROJECT_DESCRIPTION'.message: 'Project description:'
}, {
type: 'input'.name: 'PROJECT_AUTHOR'.message: 'Project author:'
}]
module.exports = { DEFAULT_QUESTIONS }
Copy the code
Here is a brief explanation of the configuration items used. For more information about other configuration items, you can poke:
Type ${string} Interaction type name ${string} Key message ${string} interaction message validate ${function} verification methodCopy the code
Once created, introduce inquirer and the interaction steps defined above in SRC /create/index.js:
const inquirer = require('inquirer')
const { DEFAULT_QUESTIONS } = require('.. /constants/questions')
module.exports.run = async() = > {try {
// Get the input information
const answers = await inquirer.prompt(DEFAULT_QUESTIONS)
} catch (error) {
console.log(chalk.red(`Create objet defeat! The error message is: ${error}`))}}Copy the code
At this point, when we execute cT-c in the console, we can see the interactive input interface:
Once the input is complete, we print answers to the console:
The result is what we typed, and the corresponding key is the name property we set when we customized the problem. We can create a project folder for the PROJECT_NAME attribute we entered.
You can create folders using fs.mkdir provided in Nodejs, and I recommend zx, Google’s open source library. This library encapsulates a series of shell commands so that we can use commands as if they were in the console:
yarn add -D zx
Copy the code
Going back to the SRC /create/index.js file, we introduced Zx into it and created our project folder from it. Note: When using Zx, you need to specify #! At the top of the file. The/usr/bin/env zx:
#!/usr/bin/env zx
/ /... Omit a lot of code
const answers = await inquirer.prompt(questions)
// Create a new directory
await $`mkdir ${answers.PROJECT_NAME}`
/ /... Omit a lot of code
Copy the code
Create common files
Before we start this step, we can first think about what files are commonly used when creating a new project. I believe partners have their own answers in their hearts.
Here, I will create including package. Json,. Gitignore, SRC/index. The js file, such as the file content and advance the template definition in the SRC/create/constants/defaultInit in js:
// Define the contents of index.js
const INDEX_CONTENT = {
filename: 'src/index.js'.content: ' '
}
// Define the package.json content
const PACKAGE_CONTENT = {
filename: 'package.json'.content: JSON.stringify({
"version": "1.0.0"."main": "index.js"."scripts": {
"ca": "git add -A && git-cz -av"."commit": "git-cz"
},
"keywords": []."license": "ISC"."devDependencies": {
"husky": "^ 5.0.9"."commitizen": "^ holdings"."cz-conventional-changelog": "^ 3.3.0"."lint-staged": "^ 10.5.4"}})}// define.czrc content
const CZRC_CONTENT = {
filename: '.czrc'.content: '{ "path": "cz-conventional-changelog" }'
}
// define. Huskyrc content
const HUSKYRC_CONTENT = {
filename: '.huskyrc.yml'.content: `hooks: pre-commit: lint-staged commit-msg: 'commitlint -E HUSKY_GIT_PARAMS' `
}
Commitlintrc content
const COMMITLINTRC_CONTENT = {
filename: '.commitlintrc.yml'.content: `extends: - '@commitlint/config-conventional' `
}
// Define.lintStageDRC content
const LINTSTAGEDRC_CONTENT = {
filename: '.lintstagedrc.yml'.content: `'**/*.{js, jsx, vue}': - 'eslint --fix' - 'git add' '**/*.{less, md}': - 'prettier --write' - 'git add' `
}
/ / definition. Gitignore
const GIT_IGNORE_CONTENT = {
filename: '.gitignore'.content: '/node_modules'
}
module.exports = {
INDEX_CONTENT,
PACKAGE_CONTENT,
CZRC_CONTENT,
HUSKYRC_CONTENT,
COMMITLINTRC_CONTENT,
LINTSTAGEDRC_CONTENT,
GIT_IGNORE_CONTENT
}
Copy the code
Then, create default.js in SRC /create, import the file template, and write the logic to create the file. The file throws a method that accepts the answer to the question in the previous step and fills the file into the corresponding path accordingly. Without further ado, on the code:
#!/usr/bin/env zx
require('zx/globals')
const {
INDEX_CONTENT,
PACKAGE_CONTENT,
CZRC_CONTENT,
HUSKYRC_CONTENT,
COMMITLINTRC_CONTENT,
LINTSTAGEDRC_CONTENT,
GIT_IGNORE_CONTENT
} = require('.. /.. /constants/defaultInit')
/** * copy all files from the template directory to the specified directory *@param {Object} Answers Input information */
module.exports = (answers) = > {
const { PROJECT_NAME } = answers
const baseDir = `${process.cwd()}/${PROJECT_NAME}`
// Create the SRC directory and create the index.js file
fs.mkdir(`${baseDir}/src`, { recursive: true }, err= > {
if (err) {
console.log({err})
return
}
// Create a file
const files = [
INDEX_CONTENT,
PACKAGE_CONTENT,
CZRC_CONTENT,
HUSKYRC_CONTENT,
COMMITLINTRC_CONTENT,
LINTSTAGEDRC_CONTENT,
GIT_IGNORE_CONTENT
]
files.forEach(file= > {
const { filename, content } = file
let fileContent = content
// If it is package.json, fill in the corresponding information
if (filename === 'package.json') {
const { PROJECT_NAME, PROJECT_DESCRIPTION, PROJECT_AUTHOR } = answers
const packageContent = {
name: PROJECT_NAME,
author: PROJECT_AUTHOR,
description: PROJECT_DESCRIPTION, ... JSON.parse(content) } fileContent =JSON.stringify(packageContent, null.'\t')
}
fs.writeFile(`${baseDir}/${file.filename}`, fileContent, {
encoding: 'utf-8'
}, err= > {
err && console.log({ type: 'Create index.js failed: ', err })
})
})
})
}
Copy the code
Go back to the SCR /create/index.js file again and introduce the method and execute:
const initObject = require('.. /utils/default')
/ /... Omit a lot of code
// Get the input information
const answers = await inquirer.prompt(questions)
// Create a new directory
await $`mkdir ${answers.PROJECT_NAME}`
// Create a file
await initObject(answers)
/ /... Omit a lot of code
Copy the code
Go to the corresponding directory to perform the installation
This step is easy, just jump to the directory we created and install the required packages:
// src/create/index.js
/ /... Omit a lot of code
// Get the input information
const answers = await inquirer.prompt(questions)
// Create a new directory
await $`mkdir ${answers.PROJECT_NAME}`
// Create a file
await initObject(answers)
// Jump to the directory
cd(dirName)
// Run the installation command
await $`yarn`
/ /... Omit a lot of code
Copy the code
At this point, all the basic logic is complete
Step 3: Publish the tool to NPM
After writing, we can directly execute the pre-set pub command to publish.
Further reading
About Command parsing
In this article, we used process.argv to parse the input commands. If it was a simple project, it would be fine. Commander makes this process much easier and can be configured with a help message. If you are interested, go and try it
About the color of the printed message
Different types of console messages can be printed in different colors to make them more intuitive and aesthetically pleasing. In the zX mentioned above, Chalk is also packaged and can be used directly once zX is introduced. It allows you to “color” console output information. An 🌰 :
console.log(chalk.red(`Create objet defeat! The error message is: ${error}`))
Copy the code
More colors and use methods, friends can also go to study, let the console output colorful ~
conclusion
At this point, we’ve created a simple scaffolding tool of our own. We can continue to optimize it according to our own needs in the later stage. We can also think about what other partners need when using your scaffold, and how to help more partners improve their human efficiency.
About CLI tools, Cui da in B station also has a detailed explanation of the video, we can also go to reference: portal
The scaffolding tools used in this article have been uploaded to git and are welcome to ⭐️
At the same time, it has been uploaded to NPM, interested partners can try the global installation ~
See here, quickly start to weave a net cafe ~