手拉手CLI工具开发(完结)

318 阅读2分钟

前言

本文是对上篇cli的完善

本篇文章对应的项目地址: (github.com/DIVINER-onlys/…)

首先献上整个实现的流程图,我们看着流程图来一步一步实现

上篇文章的实现已经到了定义指令,我们将继续下面的流程

1. 检查name的合法性

在上篇文章中,我们在create指令下注释掉了这个逻辑,现在需要调用这个方法

// bin/efox.js

// 下面这个逻辑后面会说
// require('../lib/create')(name, options)

关于name的合法性我们用到validate-npm-package-name,执行

npm i validate-npm-package-name

新建 lib/create.js文件

// lib/create.js

const validateAppName = require('validate-npm-package-name') // 检查名字是否合法

async function create(appName, options) {
    let name = appName || 'efox'
    
  // 验证项目名称合法性
  const result = validateAppName(name)
  if (!result.validForNewPackages) {
    console.error(chalk.red(`非法的项目名: ${name}`))
    result.errors && result.errors.forEach(err => console.error(chalk.red.dim(`Error: ${err}`)))
    result.warnings && result.warnings.forEach(warn => console.warn(chalk.yellow.dim(`Warnings: ${warn}`)))
    exit('命名非法')
    process.exit(1)
  }
}
function exit(err) {
  console.log(`${chalk.red('运行错误!❌ 终止运行!')}`)
  console.log(`${chalk.white(err)}`)
}

当我们输入非法项目名时,提示错误并退出程序运行

2. 检查文件目录是否存在文件名

判断文件名用到 fs-extra',执行npm i fs-extra

为了能在命令行中进行交互使用inquirer,执行npm i inquirer

// lib/create.js

const fs = require('fs-extra')  // fs-extra是fs的一个扩展
const inquirer = require('inquirer') // 命令行交互行工具
const cwd = typeof process.cwd === 'function' ? process.cwd() :  process.cwd // 当前工作路径
const targetDir = path.resolve(cwd, name)  // 目标路径
// 存在同名目录
  if (fs.existsSync(targetDir)) {
    // 强制生成则直接删除原有目录
    if (options.force) {
      await fs.remove(targetDir)
    } else {
      const { action } = await inquirer.prompt([
        {
          name: 'action',
          type: 'list',
          message: `使用的项目名 ${chalk.cyan(targetDir)} 已存在。 选择以下操作:`,
          choices: [
            {
              name: '覆写',
              value: 'overwrite'
            }, 
            // {
            //   name: '合并',
            //   value: 'merge'
            // },
            {
              name: '取消',
              value: false
            }
          ]
        }
      ])
      if (!action) {
        return
      } else if (action === 'overwrite') {
        console.log(`\n${chalk.green('删除')} ${chalk.cyan(targetDir)}\n`)
        await fs.remove(targetDir)
      }
    }
  }
  
  // 下面开始加载config配置文件
  // const creator = new Creator(name, targetDir, path.resolve(cwd))
  // await creator.create(options)

若存在同名文件夹,则选择文件操作

3. 加载config配置文件

为能运行git命令,安装git-promise, 执行 npm i git-promise

新建 lib/Creator.js 文件

// lib/Creator.js

const git = require('git-promise') // 运行git命令

async getConfig(localPath, projectName) {
    // Efox/cli 脚手架模板库地址
    const httpPath = `https://github.com/DIVINER-onlys/efox-cli-config.git`
    return downloadRepo(httpPath, localPath, projectName, '')
  }
  
async function downloadRepo(repoPath, localPath, appName, branch) {
  const _branch = branch ? `-b ${branch} --` : '--'
  const _repoPath = `clone ${_branch} ${repoPath} ${localPath}`
  // console.log('\ngit:', _repoPath)
  return git(_repoPath)
}

接下来调用getConfig 方法就能加载到我们的config配置文件,到这里应该有同学知道后续也是同样的操作,从github上加载对应的文件

4. 通过config内容选择项目

// lib/Creator.js

const ora = require('ora') // 实现node.js命令行环境的loading效果,和显示各种状态的图标等
const inquirer = require('inquirer')
const fs = require('fs')

async create(cliOptions = {}, preset = null) {
    // console.log('开始', cliOptions, this.context, this.name, this.cwd)
    const spinner = ora('加载efox-cli-config配置文件').start()
    const params = await this.getConfig(this.context, this.name).then(
      async log => {
        const res = fs.readFileSync(`${this.context}/config.json`, 'utf8')
        spinner.succeed('efox-cli-config配置文件加载完成')
        const config = JSON.parse(res)
        let selectData = {}
        if(cliOptions.project) {
          if (!config.modules[cliOptions.project]) {
            logger.info(`你所选项目组 ^^${cliOptions.project}^^ 不存在任何模板,请重新选择`)
            selectData = await this.selectProject(config, true)
          }
        } else {
          selectData = await this.selectProject(config)
        }

        return {
          project: selectData.project,
          module: selectData.module,
          localPath: this.context,
          projectName: this.name
        }
      }
    )
  }
  
  async selectProject(moduleConfig, isAgain) {
    const res = await inquirer.prompt([
      {
        name: 'project',
        type: 'list',
        message: `${isAgain !== undefined ? '重新':''}选择模板所在的项目组:`,
        choices: [...moduleConfig.projects]
      }
    ]).then(async answers => {
      const { project } = answers
      return { project }
    })
    return res
  }

5. 移除config文件

我们已经从config文件中获取到需要的数据,这时候需要把config文件夹移除,执行npm i shelljs

// lib/Creator.js

const shell = require('shelljs')
const { project, module, localPath, projectName } = params  // params是上面返回的
shell.rm('-rf', path.join(localPath))

6. 通过选择项目拉取对应的脚手架 并且 移除.git依赖

执行 npm i simple-git

// lib/Creator.js

spinner.start('开始加载模板文件')
await this.getBaseProject(localPath, project, module)
const git = require('simple-git')(localPath)
git.pull('origin', 'master', (err, result) => {
  if (!err) {
    shell.rm('-rf', path.join(localPath, '.git'))
    spinner.succeed('加载模板文件完成')
  }
})

async getBaseProject(localPath, project, module) {
    const httpPath = `https://github.com/DIVINER-onlys/${project}.git`
    return downloadRepo(httpPath, localPath, project, '')
  }

执行 efox-cli c snn,就能拉取对应的脚手架了

最后

  • npm login
  • npm publish 可以发布自己的npm包

如果本文对你有帮助的话,给本文点个赞吧