The effect needed to be achieved

  • Implementations can run code directly from the command line, which is uploaded to NPM
  • Lb -cli install Installation command
  • Lb -cli config View the configuration file
  • The function such as

NPM init -y create package.json

2. Create a SRC folder that represents the scaffold source file and create a main.js entry

The main function of main.js is to print our command line

3, create a new folder/WWW file, indicating the use of the core file, run the WWW file, WWW file will reference a main module or a main file

Bin/WWW file

#! /usr/bin/env node // declares that this is an executable file that can be run and executed under the Node environment
require('.. /src/main.js'); // This file is automatically found when executed
Copy the code

4. Because some use ES6 syntax or the node version is very low, and some may use import, it cannot be run directly in Node by default. NPM install Babel -cli Babel -env -d Babel -cli Babel -env -d Babel -cli Babel -env

npm install babel-cli babel-env babel-core -D
Copy the code

After the package is installed, a.babelrc file is automatically generated, which is used to compile ES6 syntax

{ "presets": [/ / default, tell it with which package to go [" env "{" targets" : {/ / to which version of the "node" : "current" / / how much is the version number (current: the current version)}}], "react", "stage" 0]}Copy the code
  • If the Cannot use import statement outside a module message is displayed, the bable installation may not be successful or the configuration is incorrect

Create a command to package SRC files into the dist folder

  • Open package.json and add a script under scripts
{
    "name": "lb-cli"."version": "1.0.0"."description": ""."main": "index.js"."scripts"{// Use bable to translate SRC folder output to dist"compile":"bable src -d dist"
    },
    "keywords": []."author": ""."license": "ISC"."devDependencies": {
      "babel-cli": "^ 6.26.0"."babel-env": "^ against 2.4.1." "}}Copy the code
  • So we can run the NPM run compile directive and package the files from SRC into the dist folder
npm run compile
Copy the code

7, you can also add a command watch to monitor the SRC folder and produce new files whenever changes are made

{
    "script": {"watch":"npm run compile -- --watch"// -- represents the command to pass a monitoring parameter}}Copy the code

run

npm run watch
Copy the code

8. WWW files reference a main module or main file (mapping command line tools, configuring LB-CLI commands to be recognized by Node)

8.1. Add bin to package.json, the same as SRC, to describe which command to run

{
    "bin": {"lb-cli":"./bin/www"// lb-cli command can be customized,./bin/ WWW is specified to execute the file path}}Copy the code

8.2 run the NPM link in the root directory to link to the NPM global directory. When developing the NPM module locally, we can use the NPM link command to link the NPM module to the corresponding running project, which is convenient for debugging and testing the module

NPM link // After running this command, lB-CLI can be executed globallyCopy the code
  • If I get an error

Lb-cli –help (similar to node –help)

NPM install commander // Install command line manager tool, can help us with some command line namesCopy the code

After the installation is complete, import it in the main file under SRC and name it program

Var program = require('commander') program.version('V1.0','-v ') --version').parse(process.argv) // Configure the version number, directive. All user parameters are in argvCopy the code

After the configuration is complete, you can see the following effect

Bring in some of the variables from package.json and create a constant folder util under SRC with a constants.js inside

Configure the js

Const {version} = require(const {version} = require('.. /.. /package.json') // Import the package.json version numberexportConst VERSION = VERSION // ExportCopy the code

Import the version number in main.js

import { VERSION } from './utils/constants'// Import the version number program.version(version,'-v --version'Parse (process.argv) // Version numberCopy the code

10. Configure several common directives

lb-cli config set a 1
lb-cli install
Copy the code

Add configuration in main.js

program.command("install"// instruction.description ()"install template"// description.alias ("i"Action (()=>{// write function logic})Copy the code

If it is a lot of functions, you can wrap all the instructions into an object loop and write a mapping table before the previous code

let actionMap = {
    install:{
        alias:'i'// Abbreviate description:'install template'// describe examples'lb-cli i'.'lb-cli install'
        ]
    },
    config:{
        alias:'c',
        description:'config .lbclirc'// create examples of plugins :['lb-cli config set <k> <v>'// Set key and value'lb-cli config get <k>'.'lb-ci config remove <k>'}, // The last one must be *, indicating that the other instructions can not be found"*":{
        description:'not found' ,
        examples:[]
    }
}
Copy the code

Modify the configuration code to loop the configuration, add this code, and you can

Object.keys(actionMap).foreach (action=>{// traverses the Object, Action indicates the key program.description (actionMap[action].description) // description .alias(actionMap[action].alias).action(()=>{console.log(action)})})Copy the code

Print the usage as well, listening for a time and printing the usage when the user presses -h or –help

function help(){
    console.log('Here are some usage examples')
    Object.keys(actionMap).forEach(action= >{ // Iterate over the object. Action is the key of the key-value pair
        actionMap[action].examples.forEach(examples= >{
            // Print an example of how to use it
            console.log(examples)
        })
    })
}
program.on('-h',help)
program.on('--help',help)
Copy the code

11. Separate the function of each instruction ()

11.1 Creating index.js under SRC Index. js is used to execute code files based on the commands typed by users

// After the command line is obtained, this is the main flow controllet apply = ()=>{
    
}
export default apply
Copy the code

11.2 Imported in main.js

import main from './index.js'// Import function file //... Keys (actionMap).foreach (action=>{// iterate over the Object, Action indicates the key program.description (actionMap[action].description) // description .alias(actionMap[action].alias).action(()=>if(action==='config'){// Implementation can change configuration file main(action,... Process.argv. slice(3)) // Pass the parameters of this command to process.argv.slice(3)else if(action=='install'){// Download scaffolding main(acticon) // Install without input}})})Copy the code

11.3 Do some distribution of content in index.js, specifying each file for processing. Create the files config.js and install.js under SRC

  • config.js
// Manage.lbclirc files (in the current user directory)let config = (action,k,v)=>{
    console.log(action,k,v)
}
export default config
Copy the code
  • install.js
let install = ()=>{
    
}
export default install
Copy the code

11.4 Encapsulating public methods

  • Create common.js in the utils folder

Lable-env will convert import to require, and export default to module.export ={default: XXX}. There is a layer of object structure in the export process, so there is a unified encapsulation method for processing

export let betterRequire = (abspath)=>{
    let module = require(absPath)
    if(module.default){
        return module.default
    }
    return module
}
Copy the code
  • Used in index.js
Import {betterRequire} from'./utils/common'// Import the public method wrapper import {resolve} from'path'// Import the nodejs built-in path plug-inlet apply = ()=>{
    betterRequire(resolve(_dirname,`./${action}`)) (... Args) // convert to absolute path, call method, pass argument}export default apply
Copy the code

Lbclirc lbclirc lbclirc lbclirc

Add user root constants to the constants.js file

Github vue template address

Const HOME = process.env[process.platform===); // Use the node method to find the user root directory'win32'?'USERPROFILE':'HOME']
export  const RC = `${HOME}/. Lbclirc ` / / RC configuration download templates, for making the API to pull the project address] [making vue template (https://api.github.com/orgs/vuejs-templates/repos)export const DEFAULTS = {
    registry:'zhufeng-template'// Where do you want the warehouse to come fromtype:'USER', // indicates whether the definition is under the personal directory (users) or the user directory (orgs)}Copy the code

Create rC.js in the utils folder

Import {RC,DEFAULTS} from'./constants'// Define several common utility methodsexport let set= () = > {}export let get = ()=>{
    
}
export let remove = ()=>{
    
}
export let getAll = ()=>{
    
}
Copy the code

Import in config.js file and use switch to judge

import {set,get,romove,getAll} from './utils/rc'
let config = (action,k,v)=>{
    switch(action)
        case 'get':
            if(k){
                get(k)
            }else{getAll()}break
        case 'set'
            set(k,v)
            break
        case 'remove'
            remove(k)
            break
        
}
Copy the code

NPM install INI (a=b); this package can convert key = value into object. This package can convert key = value into object. You can also convert objects to this format, which has two methods, decode and encode, similar to json. parse and json.stringify

Import {RC,DEFAULTS} from'./constants'// Import key/valut and object conversion methods import {decode,encode} from'ini'// Import {promisify} from from node util as a promiser'util'Fs import fs from'fs'// Define methods that promisify can code into promise methodsletExists = promisify(fs.exists) // Whether existslet readFile = promisify(fs.readfile) // WriteletWriteFile = promisify(fs.writefile) // read // define several public utility methodsexport let set = async (k)=>{
    letHas =await exists(RC) // Determine whether the file existsletOpts // Define a variable to store the obtained fileif(has){
        opts = await readFile(RC,'utf8') opts = decode(opTS) // Decode the code using ini decode method and convert key/ Valut into objectsreturn opts
    }
    return ' '
}
export let get = async ()=>{
    letHas =await exists(RC) // Determine whether the file existsletOpts // Define a variable to store the obtained fileif(has){
        opts = await readFile(RC,'utf8'Opts = decode(opTS) // Decode key/valut by ini decode method. Assign (opts,{[k]:v})}else{
        opts = Object.assign(DEFAULTS,{[k]:v})
    }
    await writeFile(RC,encode(opts),'utf8')}export let remove = async ()=>{
    letHas =await exists(RC) // Determine whether the file existsletOpts // Define a variable to store the obtained fileif(has){
        opts = await readFile(RC,'utf8'Opts [k] await writeFile(RC,encode(opts),'utf8')}}export let getAll = async ()=>{
    letHas =await exists(RC) // Determine whether the file existsletOpts // Define a variable to store the obtained fileif(has){
        opts = await readFile(RC,'utf8') opts = decode(opTS) // Decode the code using ini decode method and convert key/ Valut into objectsreturn opts
    }
    return{}}Copy the code

Because rc file methods are promises, we should also add await when using these methods in config.js

import {set,get,romove,getAll} from './utils/rc'
let config = async (action,k,v)=>{
    switch(action)
        case 'get':
            if(k){
                let key = await get(k)
                console.log(key)
            }else{
                letForEach (key=>{console.log(' ')${key}=${obj[key]}`)})}break
        case 'set'
            await set(k,v)
            break
        case 'remove'
            await remove(k)
            break
        
}
Copy the code

13 Writing real logic for LB-CLI istall (essentially downloading templates)

Modify the js

letInstall = ()=>{// Download template select template use // Obtain template information through the configuration file (which templates are available)letList = await repoList() list = list.map(({name})=name) console.log(list)export default install
Copy the code

The core function is how to write the repolist method and the version number tagList method (how to get the template list information, and the version number list)

  • Create a remote repository getmethod file called git.js under utils
export let repoList = async()=>{
}
Copy the code

Install NPM install Request and import it in git.js

import request from 'request'// Import the crawler module import {getAll} from'./rc'// Get all of our config arguments // encapsulate a request methodletFetch = async (URL)=>{// encapsulates a Promisereturn new Promise((resolve,reject)=>{
        let config = {
            url,
            methods:'get',
            headers:{
                'user-agent':' '}} request(config,(err,response,body)=>{if(err){reject(err)} resolve(json.parse (body)) // Returns a string type that needs to be converted to JSON})})} // Configures the download version number listexport letTagList = async(repo)=>{// Get all config configurationsletConfig = await getAll() // dynamic configuration template download addresslet api = `https://api.github.com/repos/${config.registry}/${repo}/tags`
    returnAwait fetch(API)} // Configure the list of download addressesexport letRepoList = async()=>{// Get all config configurationsletConfig = await getAll() // dynamic configuration template download addresslet api = `https://api.github.com/${config.type}/${config.registry}/repos`
    return await fetch(api)
}
Copy the code

Import the address list method in install.js, and then download the template

  • To give the command line interface a sense of progress, you can add a command line animation to the template download and install the command action drawing plugin NPM install ORA
  • To enable the user to select templates, install the package NPM Install Inquirer that lets the user select templates
import {repoList, tagList} from './utils/git'// Import address list method and version list method import ora from'ora'// Import command action drawing plugin import inquirer from'inquirer'// Import the user selection template plug-inletInstall = ()=>{// Download template select template use // create command action paintinglet loading = ora('fetching template...... '// Run animation loading.start() // From the configuration file, get template information (which templates are available)letList = await repoList() // Close animation loading.succeed() list = list.map(({name})=name) // just name attribute console.log(list) // The result is an array of templates // The inquirer. Prompt method returns a promiselet answer = await inquirer.prompt([
        {
            type:'list',
            name:'project',
            choices:list,
            questions:'Pleace Choice Template'}]) console.log(answer.project) // Project nameletProject = answer.project // Get the current project version tag // create command action picturelet loading = ora('Fetching tag(obtain version)...... '// Run the animation loading.start() // through the configuration file, List = await tagList() // Close animation loading.succeed() list = list.map(({name})=name) // Just use the name attribute // The inquirer. Prompt method returns a promiselet answer = await inquirer.prompt([
        {
            type:'list',
            name:'tag',
            choices:list,
            questions:'Pleace Choice Tag (please select template)'}])let tag =  answer.tag
}
export default install
Copy the code

NPM install download-git-repo If you want to configure the cache directory, you need to configure the cache directory. If you want to configure the cache directory, you need to configure the cache directory, and the lB-CLI init command will copy the downloaded file to the desired location. You need to define the download cache directory in the constants.js file in the utils folder, in this case the.template folder in the user root directory, and you also need to wrap the download method in git.js

  • Added at the end of the constants.js file
// Download directoryexport const DOWNLOAD = `${HOME}/.template`
Copy the code
  • Add code in git.js
Import downLoadGit from'download-git-repo'Import {DOWNLOAD} from'./utils/constants'/ /... // Encapsulate the git download method with two parameters: where to download from and where to store itexport let download = async (src,dest)=>{
    return new Promise((resolve,reject)=>{
        downLoadGit(src,dest,(err)=>{
            if(err){reject()} resolve()})})} // Encapsulate git into a local methodexport let downloadLocal = async(project,version)=>{
    let conf = await getAll()
    let api = `${conf.registry}/${project)`
    if(version){
        api += `#{version} '// Hash the version number if there is one
    }
    return await download(api,DOWNLOAD+'/'+project) // Download address, store address and filename}Copy the code
  • Edit the code in install.js
import {repoList, tagList, downloadLocal} from './utils/git'// Import the address method and version number method and download git template to the local method import ora from'ora'// Import command action drawing plugin import inquirer from'inquirer'// Import the user selection template plug-inletInstall = ()=>{// Download template select template use // create command action paintinglet loading = ora('fetching template...... '// Run animation loading.start() // From the configuration file, get template information (which templates are available)letList = await repoList() // Close animation loading.succeed() list = list.map(({name})=name) // just name attribute console.log(list) // The result is an array of templates // The inquirer. Prompt method returns a promiselet answer = await inquirer.prompt([
        {
            type:'list',
            name:'project',
            choices:list,
            questions:'Pleace Choice Template'}]) console.log(answer.project) // Project nameletProject = answer.project // Get the current project version tag // create command action picturelet loading = ora('Fetching tag(obtain version)...... '// Run the animation loading.start() // through the configuration file, List = await tagList() // Close animation loading.succeed() list = list.map(({name})=name) // Just use the name attribute // The inquirer. Prompt method returns a promiselet answer = await inquirer.prompt([
        {
            type:'list',
            name:'tag',
            choices:list,
            questions:'Pleace Choice Tag (please select template)'}])letTag = answer.tag // call the download to local method, download the file // create the command action picturelet loading = ora('Fetching project(currently download)...... 'Load.start () await downloadLocal(project,tag) console.log()'Download feature'// Close animation loading.succeed()}export default install
Copy the code

14 At this point, a basic scaffolding is almost complete,

Some other functionality

  • Vue, for example, uses a template engine
  • For example, Vue Init can generate currently downloaded templates into the project directory
  • For example, vue uninstall
  • Such as the choice of technology…