⚠️ This article is the first contract article in the nuggets community. It is prohibited to reprint without authorization

preface

In the first two articles, a simple React scaffolding and a preliminary CLI tool have been built respectively.

After the basic scaffolding is built, we can start the development of template management, after all, in the real environment, a set of scaffolding is far from enough, we will face a variety of business scenarios, and for these scenarios may have custom scaffolding appear.

So how do we manage the scaffolding?

Functional design

If the CLI needs to be updated every time the new scaffold is developed or the template is updated, the cost is not high, but the students who develop the template need to inform the students who develop the CLI to upgrade, and the students who use the template need to wait for the COMPLETION of CLI development before using it, which increases the cost of communication.

Second, business development students may only need one or a few types of templates, so if the CLI is a large and complete set of templates, it can be a burden for those students to quickly choose the template creation project, because it takes time to choose among many templates.

So our goal was to design a CLI tool with custom configuration and updatable templates.

Since the configuration is customized, users need to manually add, delete, and update their frequently used template information on the local PC. At the same time, users need to dynamically pull these templates instead of downloading the old version of the local PC.

According to the requirements, we can simply design the outline of the CLI template function:

  1. You need to save the address from which the template came
  2. Pull different template code depending on the user’s choice
  3. Save the template locally

Practical development

Then according to the above design ideas, we can develop the required functions step by step

Local save template address function

As a first step, if we need to save some information about the template locally, we need a conversational interaction to guide the user to enter the required information, so we can choose the Inquirer library.

Inquirerjs is a collection of tools used to implement command-line interactive interfaces. It helps us to realize interactive communication with users, such as asking users a question, users give us an answer, we do some things according to the answer of users, typical applications such as plop generator tools.

General pull code, we need to know the template address (URL to pull the corresponding template must be required), template alias (convenient for users to search), template description (convenient for users to understand template information)

In this way, you need to save the template information including the address, alias, and description, so that you can manage the corresponding template. Example code is as follows:

import inquirer from 'inquirer';
import { addTpl } from '@/tpl'

const promptList = [
  {
    type: 'input'.message: 'Please enter warehouse address :'.name: 'tplUrl'.default: 'https://github.com/boty-design/react-tpl'
  },
  {
    type: 'input'.message: 'Template title (default Git name as title):'.name: 'name'.default({ tplUrl }: { tplUrl: string }) {
      return tplUrl.substring(tplUrl.lastIndexOf('/') + 1)}}, {type: 'input'.message: 'description:'.name: 'desc',}];export default () => {
  inquirer.prompt(promptList).then((answers: any) = > {
    const { tplUrl, name, desc } = answers
    addTpl(tplUrl, name, desc)
  })
}
Copy the code

Inquirer has obtained the corresponding information, but it is not convenient to store the data in the cache due to various situations such as computer restart. The CLI tool is also overqualified to use the database for storage, so the information can be directly stored locally in the form of JSON files.

Example code is as follows:

import { loggerError, loggerSuccess, getDirPath } from '@/util' import { loadFile, writeFile } from '@/util/file' interface ITpl { tplUrl: string name: string desc: string } const addTpl = async (tplUrl: string, name: string, desc: string) => { const cacheTpl = getDirPath('.. /cacheTpl') try { const tplConfig = loadFile<ITpl[]>(`${cacheTpl}/.tpl.json`) let file = [{ tplUrl, name, desc }] if (tplConfig) { const isExist = tplConfig.some(tpl => tpl.name === name) if (isExist) { file = tplConfig.map(tpl => { if (tpl.name === name) { return { tplUrl, name, desc } } return tpl }) } else { file = [ ...tplConfig, ...file ] } } writeFile(cacheTpl, '.tpl.json', JSON.stringify(file, null, "\t")) loggerSuccess('Add Template Successful! ') } catch (error) { loggerError(error) } } export { addTpl, }Copy the code

Here we need to make a simple process decision on whether to save or update the template:

  1. Check whether the TPL cache file exists. If the cache file exists, merge it with the current template information. If not, create a file and save the obtained information in it.
  2. If a cache file exists, perform the following operations based on thenameIf it has been cached, use thenameUpdate the corresponding template information.

Now, let’s show you how it works.

After the CLI is built, run fe-cil add TPL to obtain the following result:

You can see that the template information has been cached in the corresponding path.

As mentioned above, we have completed a simple function of adding and modifying template information locally. Similarly, deleting template information is also a similar operation, which can be developed according to your actual needs.

Download the template

After saving the template, we need to select the corresponding template to download.

You can use download-git-repo as a plug-in for CLI download. This is a very useful plug-in that supports non-Clone downloading of corresponding templates, which is very suitable for our project.

Download git – download git repo is a repository of tool library, which provides short and direct: direct download url in two ways, but also provide direct download code and the function of the git clone, very the use and convenience.

Also, when downloading templates, we need to show the user the current list of saved templates, again using the Inquirer tool.

  1. useinquirerCreate list select interactive mode, read the local template list, let the user select the required template
export const selectTpl = () = > {
  const tplList = getTplList()
  const promptList = [
    {
      type: 'list'.message: 'Please select template download :'.name: 'name'.choices: tplList && tplList.map((tpl: ITpl) = > tpl.name)
    },
    {
      type: 'input'.message: 'Download Path :'.name: 'path'.default({ name }: { name: string }) {
        return name.substring(name.lastIndexOf('/') + 1)}}]; inquirer.prompt(promptList).then((answers: any) = > {
    const { name, path } = answers
    const select = tplList && tplList.filter((tpl: ITpl) = > tpl.name)
    const tplUrl = select && select[0].tplUrl || ' '
    loadTpl(name, tplUrl, path)
  })
}
Copy the code
  1. usedownload-git-repoDownload the corresponding template
export const loadTpl = (name: string, tplUrl: string, path: string) = > {
  download(`direct:${tplUrl}`, getCwdPath(`. /${path}`), (err: string) = > {
    if (err) {
      loggerError(err)
    } else {
      loggerSuccess(`Download ${name}Template Successful! `)}}}Copy the code

However, if you choose the direct mode and download a ZIP address instead of a normal Git address, the above address will not be valid, so you need to do a layer translation of the address before downloading the code.

The normal git address is https://github.com/boty-design/react-tpl. The actual download address is https://codeload.github.com/boty-design/react-tpl/zip/refs/heads/main, in making can see lot of contrast normal links, The domain name and link have been changed, but there must be project name and team name, so we can separate boty-design/ React-TPL for later assembly.

const { pathname } = new URL(tplUrl)
if (tplUrl.includes('github.com')) {
  reTpl.org = pathname.substring(1)
  reTpl.downLoadUrl = 'https://codeload.github.com'
}
Copy the code

As in the above code, the pathname obtained by parsing tplUrl is the information we need, and when we reassemble the download link in dowload template.

As shown in the figure above, we can download the public template locally, which is convenient for students to develop normally, but there is still a problem, that is, the above branch is the main branch, not every template has this branch, the controllability is poor, so how can we get all the branches of the project to download selectively.

Github Api

In Github, for open source projects that are not private, you can use Github Api to obtain the corresponding information without authorization token step.

Therefore, Github Api can be used to solve the problem mentioned above.

Obtain the branch link is https://api.github.com/repos/boty-design/react-tpl/branches, we can use the PostMan before development to test whether normal returns the result we need.

As you can see above, we can get the branch information we want through Github Api.

If the following error occurs, it doesn’t matter. Github is just limiting the frequency of access

In view of the above problems, we need to control the frequency, use conditional request or use token request Github Api to avoid, but in view of the template, generally the request frequency is not very high, but I need continuous requests to test during development, this problem will occur. If you are interested, you can try other solutions for yourself.

Branch code optimization

After pre-research on Github Api, it is necessary to encapsulate the obtained information. For example, if there is only one branch, users can directly download the template; if multiple branches are requested, branches should be displayed so that users can freely choose the corresponding branch to download the template. The overall business flow chart is shown as the above.

The main logic code is as follows:

export const selectTpl = async() = > {const prompts: any = new Subject();
  let select: ITpl
  let githubName: string
  let path: string
  let loadUrl: string

  try {
    const onEachAnswer = async (result: any) = > {const { name, answer } = result
      if (name === 'name') {
        githubName = answer
        select = tplList.filter((tpl: ITpl) = > tpl.name === answer)[0]
        const { downloadUrl, org } = select
        const branches = await getGithubBranch(select) as IBranch[]
        loadUrl = `${downloadUrl}/${org}/zip/refs/heads`
        if (branches.length === 1) {
          loadUrl = `${loadUrl}/${branches[0].name}`
          prompts.next({
            type: 'input'.message: 'Download Path :'.name: 'path'.default: githubName
          });
        } else {
          prompts.next({
            type: 'list'.message: 'Please select branch :'.name: 'branch'.choices: branches.map((branch: IBranch) = >branch.name) }); }}if (name === 'branch') {
        loadUrl = `${loadUrl}/${answer}`
        prompts.next({
          type: 'input'.message: 'Download Path :'.name: 'path'.default: githubName
        });
      }
      if (name === 'path') { path = answer prompts.complete(); }}const onError = (error: string) = > {
      loggerError(error)
    }

    const onCompleted = () = > {
      loadTpl(githubName, loadUrl, path)
    }

    inquirer.prompt(prompts).ui.process.subscribe(onEachAnswer, onError, onCompleted);

    const tplList = getTplList() as ITpl[]

    prompts.next({
      type: 'list'.message: 'Please select template :'.name: 'name'.choices: tplList.map((tpl: ITpl) = > tpl.name)
    });
  } catch (error) {
    loggerError(error)
  }
}
Copy the code

In the above code, we can see that RXJS is used to dynamically render the interaction problem, because some templates have only one project branch. If we need users to select branches every time, it will be redundant, so the fixed question interaction is no longer applicable. We need to dynamically add inquirer problems with the help of RXJS, and determine whether the option of selecting branches appears by obtaining the number of branches, so as to improve user experience.

Write in the last

In our first enterprise-level CLI development, we built a rudimentary CLI shelf together, providing basic functionality such as build, Eslint validation, and so on.

In the second article, we build a simple React scaffolding and use the CLI to take over the dev module of the React template. We also provide extended build configuration functions and complete the integration of a basic CLI template tool

In this article, we will use the scaffolding of the previous article as a template to further transform the CLI, so that the CLI can allow users to configure templates that meet their own needs and habits.

Through these three articles, the CLI has the following functions:

CLI command function
fe-cli eslint Eslint validates the current project
fe-cli webpack Build with the current Webapck project
fe-cli rollup Build with the Rollup current project
fe-cli git init Initialize git projects locally (currently supports some GitLab features)
fe-cli add tpl Custom add templates
fe-cli init tpl Initialize the added template locally

The whole CLI will be designed according to the requirements of the first chapter, and gradually build a universal and transformable tool, which can serve as a practical reference for students.

In the next article, CLI will be developed around modules of utility classes.

All the project codes have been uploaded to the project address. Interested students can refer to them. All the relevant codes for the following columns will be unified in BOTY DESIGN.