preface

Creating a scaffold is essentially implementing a global package, but its function is to create projects based on configuration items

Implementation approach

  1. Configuration executable commands can be based oncommander
  2. To implement scaffolding do a command line interaction function can be based oninquirer
  3. Project templates can be downloaded based ondownload-git-repo
  4. Dynamically generated content based on user selection can be based onmetalsmith

The specific implementation

Start by building the basic configuration of the NPM package
Create an executable script and specify node as the runtime environment

/bin/start.js

#! /usr/bin/env node
Copy the code
Point the project execution script to the script you created

Configure the bin field in package.json

Link packages globally to execute directly from command
npm link
Copy the code

At this point, the package becomes a global package. The command is executed with the value of the name attribute, and the specified script file is executed by default

Then configure executable commands

Install dependencies

npm i command
Copy the code

Implement the logic in executing the script start.js

Commander const program = require('commander'); Program.command ('config [value]').description('inspect and modify the config').option('-g, --get <path>','get value from option') .option('-s, --set <path> <value>') .option('-d, --delete <path>','delete option from config') .action((value,opts)=>{ console.log(opts); // console.log(value,cleanArgs(cmd)); }) // create UI program.command (' UI ').description('start and open wzy-cli UI ').option('-p,--port <port>',' port used for the UI Server') .action(cmd => { console.log(cmd); Wzy-cli --version 'program. Version (' zhufeng-cli ${require('.. /package.json').version} ').usage(' <command> [option] ') // Execute a callback when the user enters' wzy-cli --help 'to prompt the user to view the specific command details program.on('--help',function (){ console.log(); console.log(`Run ${chalk.cyan('wzy-cli <command> --help ')} show details>`); console.log(); Parse (process.argv);Copy the code

If there is an interactive configuration, for example, when creating a project, you need to ask the user whether to overwrite the object file path. You can use Inquier

npm i inquier
Copy the code

The logical start. Js

const path = require('path');
const fs = require('fs-extra');
const Inquirer = require('inquirer');

// You need to support force-create functionality to create projects
program
        .command('create <app-name>')
        .description('create a new project')
        .option('-f, --force'.'overwrite target directory if it exists')
        .action((name,opts) = > {
            const cwd = process.cwd(); // Get the working directory where the current command is executed
            const targetDir = path.join(cwd,projectName); // Target directory
            async function overWrite() {
                 / / remove first
                return await fs.remove(targetDir)
            }
            // First check whether the project already exists
            if(fs.existsSync(targetDir)){
                // Then determine if it is mandatory overwrite mode
                if(opts.force){
                   await overWrite()
                }else {
                    // Prompts the user to determine whether to overwrite
                    let {action} = await Inquirer.prompt([
                        {
                            name: 'action'.type: 'list'.// There are many other types
                            message: 'Target directory already exited Pick an action'.choices: [{name: 'Overwrite'.value: 'overwrite'
                                },
                                {name: 'cancel'.value: false}}]]);console.log(action);
                    if(! action){return
                    }else if(action === 'overwrite') {
                        await overWrite()
                    }
                }
            }

        })
Copy the code
Full features: Select specified templates (warehouse and tag)

The scaffolding is set up, and we’re doing different logic processing under different commands, focusing on implementation

  • Create the project and dynamically generate the content of the project based on the user’s choice

We can create a class to download the template project. The general idea is as follows:

// 1. Pull the template under the current organization
		let repo = await this.fetchRepo();
// 2. Find the version number in the template
		let tag = await this.fetchTag();

/ / 3. Download
		let downloadUrl = await this.download(repo,tag);

// 4. Compile the template

Copy the code

Accordingly, we just need to find the platform where the template project is stored and download the exposed API. There are two steps in the middle

  • Realize the selection function, download template list, let the user choose which template to download
  • If the retransmission function is enabled, the download may fail due to uncontrollable influences such as the network. In this case, you need to add loading state and initiate a download request again

First, we can still use the Inquirer;

Second, we can use a third party library ora to add loading states. We can tryCatch the retransmission implementation.

Show me Code

Creator.js

const inquirer = require("inquirer");
const { fetchRepoList } = require("./request");
const { wrapLoading } = require("./util");

class Creator {
    constructor (projectName,target) {this.name = projectName;
        this.target = target;
    }

    async fetchRepo(){
        // Add loading and failed to reload
        let repos = await wrapLoading(fetchRepoList,'waiting fetch template');
        // let repos = await fetchRepoList();
        // // console.log(repos,' warehouse list ');
        if(! repos)return;
        repos = repos.map(item= > item.name);
        let { repo } = await inquirer.prompt({
            name: 'repo'.type: 'list'.choices: repos,
            message: 'please choose a template to create a project'
        })
        // // console.log(repo);
    }

    async fetchTag(){}async download(){}async create(){
        // 1. Pull the template under the current organization
        let repo = await this.fetchRepo();
        // 2. Find the version number in the template
        let tag = await this.fetchTag();

        / / 3. Download
        let downloadUrl = await this.download(repo,tag);

        // 4. Compile the template}}module.exports = Creator;
Copy the code

util.js

const ora = require('ora')
// Suspend the function
export async function sleep(n) {
    return new Promise((resolve,reject) = > {
        setTimeout(resolve,n)
    })
}

// Loading + Failed to initiate fetch again
export async function wrapLoading(fn,message) {
    const spinner = ora(message);
    spinner.start(); // Start loading

    try {
        let repos = await fn();
        spinner.succeed(); / / success
        return repos;
    } catch (error) {
        spinner.fail('request failed, refetch... ')
        await sleep(1000);
        console.log(error);
        returnwrapLoading(fn,message); }}Copy the code
Perfect function: download the selected template to the specified location

You can download the repository from Github using a third-party package called Download-git-repo

npm i download-git-repo
Copy the code

Then perform the following logic in the download function

  1. Splice download path
  2. Download the resource to the specified location (caching can be added later)
Show me Code

Creator.js

 // Download the specified template based on the repository and tag
    async download(repo,tag){
        // 1. Download path at splice
        let requestUrl = `zhu-cli/${repo}${tag ? The '#' + tag : ' '}`;
        // 2. Download the resource to the specified location (w-todo can be added later)
        await wrapLoading(this.downloadGitRepo,'waiting down repo', requestUrl,path.resolve(process.cwd(),`${repo}@${tag}`));

        return this.target;
    }
Copy the code

conclusion

So far, we have completed a simple version of the CLI tool, you can see, in fact, is the use of the package + clear ideas, the complete version of the code of the warehouse address, if you have any help, appreciate the small vegetables star bar, thank you for reading

This article is participating in the “Nuggets 2021 Spring Recruitment Campaign”, click to see the details of the campaign