学习笔记──搭建脚手架(三)──Ceate功能

undefined

今天研究Create功能

1、安装依赖[学习笔记──搭建脚手架(一)中都安装过了,没安装的自行安装]

2、修改入口文件里的list指令部分,需要传入模板数据。

program
  .command('create <projectName>')
  .description('create a new project')
  .option('-c, --create', '覆盖目标目录创建')
  .action((name, cmd) => {
    const options = cleanArgs(cmd)
    require('../command/create')(name, options)
  })
  
// commander passes the Command object itself as options,
// extract only actual options into a fresh object.
function cleanArgs (cmd) {
  const args = {}
  cmd.options.forEach(o => {
    const key = o.long.replace(/^--/, '')
    // if an option is not present and Command has a method with the same name
    // it should not be copied
    if (typeof cmd[key] !== 'function' && typeof cmd[key] !== 'undefined') {
      args[key] = cmd[key]
    }
  })
  return args
}

3、编写create功能文件

我在根目录下建立 \cli\command 文件夹,在里面建立一个 create.js 文件。这个 cli\command\create.js 文件是create功能文件。

const fs = require('fs-extra') // fs-extra是fs的一个扩展,提供了非常多的便利API
const path = require('path') // Node.js path 模块提供了一些用于处理文件路径的小工具
const chalk = require('chalk') // chalk,可以给终端的字体加上颜色
const inquirer = require('inquirer') // inquirer.js 给用户提供了一个漂亮的界面和提出问题流的方式。细则地址:https://www.javascriptcn.com/read-47603.html
const download = require('download-git-repo') // 下载并提取 git 仓库,用于下载项目模板。
const {error, stopSpinner, logWithSpinner, log} = require('../../publicUtils')
const validateProjectName = require('validate-npm-package-name') // 给定字符串是否是有效的npm包名称。这个软件包导出一个带有 string 作为输入并返回具有两个属性的对象的单个同步函数,解析:https://www.helplib.com/GitHub/article_120141
const templateName = require('../template')

async function create(projectName, options) {
  const inCurrent = projectName === '.' // '.'为在当前文件夹创建项目 inCurrent=true/false
  const name = inCurrent ? path.relative('../', process.cwd()) : projectName // 创建的项目名字
  // 知识点1、path.relative(from, to)用于将绝对路径转为相对路径,返回从 from 到 to 的相对路径。
  // 知识点2、process.cwd()返回 当前Node.js进程执行时的工作目录
  const targetDir = path.resolve(projectName || '.') // targetDir为创建项目的文件夹路径,如果创建时是. 那此路径是已存在的,如果创建的是新名字,那路径是不存在的。
  // path.resolve([from ...], to) 将 to 参数解析为绝对路径,给定的路径的序列是从右往左被处理的,后面每个 path 被依次解析,直到构造完成一个绝对路径。 
  // 例:path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif'); // 如果当前工作目录为 /home/myself/node,// 则返回 '/home/myself/node/wwwroot/static_files/gif/image.gif'
  const result = validateProjectName(name) // 判断名字是否有效,返回validForNewPackages:true/false false时会返回errors信息
  // 名字无效 打印errors信息并退出
  if (!result.validForNewPackages) {
    console.error(chalk.red(`Invalid project name无效的项目名称: "${projectName}"`))
    result.errors && result.errors.forEach(err => {
      console.error(chalk.red(err))
    })
    process.exit(1)
  }
  // fs.existsSync(path):如果路径存在,则返回 true,否则返回 false。
  if (fs.existsSync(targetDir)) {
    // 验证create命令中的option,.option('-c, --create', '覆盖目标目录创建')
    if (options.create) {
      await fs.remove(targetDir) // 移除当前同名文件目录
    } else {
      // 在'.'当前文件夹内创建项目
      if (inCurrent) {
        const {ok} = await inquirer.prompt([
          {
            name: 'ok',
            type: 'confirm', // Prompt types —— 问题类型:提问,回答为Y/N。若有default属性,则属性值应为Boolean类型
            message: `Generate project in current directory?在当前目录中生成项目?`
          }
        ])
        if (!ok) {
          return
        }
      } else {
        // 非'.'当前文件夹内创建项目
        const {action} = await inquirer.prompt([
          {
            name: 'action',
            type: 'list',
            message: `Target directory ${chalk.cyan(targetDir)} already exists. Pick an action:`,
            choices: [
              {name: 'Overwrite覆盖', value: 'overwrite'},
              {name: 'Cancel取消', value: false}
            ]
          }
        ])

        if (!action) {
          return
        } else if (action === 'overwrite') { // 覆盖——移除当前同名文件目录
          console.log(`\nRemoving ${chalk.cyan(targetDir)}...`)
          await fs.remove(targetDir)
        }
      }
    }
  }

  const {project} = await inquirer.prompt([
    {
      name: 'project',
      // type详细解析 https://blog.csdn.net/qq_26733915/article/details/80461257
      type: 'rawlist',// 与List类型类似,不同在于,list打印出来为无序列表,而rawlist打印为有序列表
      message: `which project do you want to install?请选择要使用的模板`,
      choices: [
        {name: 'Moban1'},
        {name: 'Moban2'},
      ]
    }
  ])
  if (!project) {
    return
  }
  logWithSpinner(` `, `Creating project in ${chalk.yellow(targetDir)}.`)
  // download(repository仓库地址, destination目的地, options, callback) 文档地址:https://www.javascriptcn.com/read-56681.html
  download(templateName[project].url, targetDir, {clone: true}, function (err) {
    if (!err) {
      stopSpinner()
      log(`Successfully created project ${chalk.yellow(project)}.`)
    } else {
      console.log(err)
    }

  })
}

module.exports = (...args) => {
  create(...args).catch(err => {
    stopSpinner(false) // do not persist
    error(err)
    process.exit(1)
  })
}

// type详细解析 https://blog.csdn.net/qq_26733915/article/details/80461257
// Prompt types —— 问题类型
// 【1】List
// {type: 'list'}
// 问题对象中必须有type,name,message,choices等属性,同时,default选项必须为默认值在choices数组中的位置索引(Boolean)
// 【2】Raw list
// {type: 'rawlist'}
// 与List类型类似,不同在于,list打印出来为无序列表,而rawlist打印为有序列表
// 【3】Expand
// {type: 'expand'}
// 同样是生成列表,但是在choices属性中需要增加一个属性:key,这个属性用于快速选择问题的答案。类似于alias或者shorthand的东西。同时这个属性值必须为一个小写字母
// 【4】Checkbox
// {type: 'checkbox'}
// 其余诸项与list类似,主要区别在于,是以一个checkbox的形式进行选择。同时在choices数组中,带有checked: true属性的选项为默认值。
// 【5】Confirm
// {type: 'confirm'}
// 提问,回答为Y/N。若有default属性,则属性值应为Boolean类型
// 【6】Input
// {type: 'input'}
// 获取用户输入字符串
// 【7】Password
// {type: 'password'}
// 与input类型类似,只是用户输入在命令行中呈现为XXXX
// 【8】Editor
// {type: 'editor'}
// 终端打开用户默认编辑器,如vim,notepad。并将用户输入的文本传回

4、在cli文件下同级下创建文件夹cli-publicUtils

创建spinner.js

const ora = require('ora') // 下载过程久的话,可以用于显示下载中的动画效果。解析地址:https://www.npmjs.com/package/ora
const chalk = require('chalk') // chalk,可以给终端的字体加上颜色

const spinner = ora()
let lastMsg = null

exports.logWithSpinner = (symbol, msg) => {
  if (!msg) {
    msg = symbol
    symbol = chalk.green('ok')
  }
  if (lastMsg) {
    spinner.stopAndPersist({
      symbol: lastMsg.symbol,
      text: lastMsg.text
    })
  }
  spinner.text = ' ' + msg
  lastMsg = {
    symbol: symbol + ' ',
    text: msg
  }
  spinner.start()
}

exports.stopSpinner = (persist) => {
  if (lastMsg && persist !== false) {
    spinner.stopAndPersist({
      symbol: lastMsg.symbol,
      text: lastMsg.text
    })
  } else {
    spinner.stop()
  }
  lastMsg = null
}

创建logger.js

const chalk = require('chalk')
const readline = require('readline')
const padStart = require('string.prototype.padstart')
const EventEmitter = require('events')

exports.events = new EventEmitter()

function _log (type, tag, message) {
  exports.events.emit('log', {
    message,
    type,
    tag
  })
}

const format = (label, msg) => {
  return msg.split('\n').map((line, i) => {
    return i === 0
      ? `${label} ${line}`
      : padStart(line, chalk.reset(label).length)
  }).join('\n')
}

const chalkTag = msg => chalk.bgBlackBright.white.dim(` ${msg} `)

exports.log = (msg = '', tag = null) => {
  tag ? console.log(format(chalkTag(tag), msg)) : console.log(msg)
  _log('log', tag, msg)
}

exports.info = (msg, tag = null) => {
  console.log(format(chalk.bgBlue.black(' INFO ') + (tag ? chalkTag(tag) : ''), msg))
  _log('info', tag, msg)
}

exports.done = (msg, tag = null) => {
  console.log(format(chalk.bgGreen.black(' DONE ') + (tag ? chalkTag(tag) : ''), msg))
  _log('done', tag, msg)
}

exports.warn = (msg, tag = null) => {
  console.warn(format(chalk.bgYellow.black(' WARN ') + (tag ? chalkTag(tag) : ''), chalk.yellow(msg)))
  _log('warn', tag, msg)
}

exports.error = (msg, tag = null) => {
  console.error(format(chalk.bgRed(' ERROR ') + (tag ? chalkTag(tag) : ''), chalk.red(msg)))
  _log('error', tag, msg)
  if (msg instanceof Error) {
    console.error(msg.stack)
    _log('error', tag, msg.stack)
  }
}

exports.clearConsole = title => {
  if (process.stdout.isTTY) {
    const blank = '\n'.repeat(process.stdout.rows)
    console.log(blank)
    readline.cursorTo(process.stdout, 0, 0)
    readline.clearScreenDown(process.stdout)
    if (title) {
      console.log(title)
    }
  }
}

运行结果

undefined

运行结果今天就到这里了,下一篇文章继续更新命令内部逻辑的编写。

本文仅是学习中的记录,有错误请指正。

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容