preface
Creating a scaffold is essentially implementing a global package, but its function is to create projects based on configuration items
Implementation approach
- Configuration executable commands can be based on
commander
- To implement scaffolding do a command line interaction function can be based on
inquirer
- Project templates can be downloaded based on
download-git-repo
- Dynamically generated content based on user selection can be based on
metalsmith
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
- Splice download path
- 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