【练习】使用node.js写一个简单的CLI工具

213 阅读4分钟

记录了我使用nodejs编写自己的cli工具,入门级别的,大神勿喷,谢谢。

创建cli工具项目

  • 创建一个文件夹,我就起名叫demo-cli吧,然后进入这个文件夹,使用npm命令创建一个package.json文件,并安装工具包
mkdir demo-cli
cd demo-cli
npm init -y

# 工具包可根据个人喜好选择安装,后边会对每个包的用处做解释
npm install commander figlet chalk clear ora download-git-repo open handlebars watch --save
  • 创建bin目录,然后在bin目录中创建一个入口文件
mkdir bin
cd bin
touch index.js
  • index.js文件中先打印个hello demo-cli
console.log('hello demo-cli')
  • package.json文件中增加
"demo": "./bin/index.js"
  • 然后在终端使用npm命令,将cli工具link到全局
npm link
  • 链接成功后在终端输入demo回车,如在终端输出hello demo-cli字样,说明demo-cli的命令就成功了
demo

hello demo-cli

配置cli版本号

  • 没有安装commander工具包的需要安装一下,这个工具包就是用来配置cli命令,我们下边先弄个cli版本号感受一下
npm i commander --save
  • bin/index.js文件中,删掉之前打印hello demo-cli的测试代码,引入commander包,配置版本号,终端使用demo --version可查看
// 导入package.json文件为了取其中的version
const pkg = require('./../package.json')
const commander = require('commander')
commander.version(pkg.version) // 这就配置了版本号

配置cli的init命令

commander
	.command('init <name>') // init命令后边name就是项目名称
	.description('init project') // init命令的描述
	.action(name => {         // init命令具体要做的事就写在这里
		console.log('init ' + name)
	})
  • 后边我们可以新建一个init.js文件,将action的回调抽离到init.js中编写,那么action就变成下边这样写
.action(require(./init.js))

配置cli的欢迎语(可选)

  • 这里会用到的工具包是figlet(个性字体)、chalk(字体颜色)、clear(清屏) 我们将
// 创建bin/init.js文件
const { promisify } = require('util') // node.js API直接用,不需要单独安装工具包
// figlet是用callback异步返回的,这里我们将它包装成返回一个promise
const figlet = promisify(require('figlet'))
const clear = require('clear')
const chalk = require('chalk')
// 重写一个log方法用来打印,是对console.log的包装,文本颜色这里用的是绿色
const log = (content) => console.log(chalk.green(content))
const init = async (name) => {
  // 清屏(可选),是否清屏幕个人喜好决定
  clear()
  // 使用figlet改变得到个性化字体文本
  const welcome = await figlet('demo-cli welcome')
  // 打印欢迎语
  log(welcome)
  log('init ' + name)
}
module.exports = init
  • 来看下效果吧,终端输入命令
demo init hehe

这样我们就看到了一个比较漂亮的欢迎界面

下载项目模板

  • 需要使用的工具包 download-git-repo(从GitHub下载模板项目)
  • 新建bin/download.js文件
const { promisify } = require('util')

const clone = async (repo, name) => {
  // 使用promisify包装一下
  const download = promisify(require('download-git-repo'))
  const ora = require('ora') // 类似loading的状态提示
  const p = ora(`下载:${repo}`) // 定义文本内容
  p.start() // 开始转
  await download(repo, name) // 从GitHub下载项目到指定{name}目录下
  p.succeed() // 变对号✅
}

module.exports = { clone }
  • init.js中导入download.js中的clone方法,在init方法中调用,并传入GitHubrepo地址和name项目名称
const { clone } = require('./download')

const init = async (name) => {
  // 清屏(可选)
  clear()
  // 使用figlet得到个性化字体文本
  const welcome = await figlet('demo-cli welcome')
  // 打印欢迎语
  log(welcome)
  log(`🚗 创建项目:${name} \n`)
  // 从GitHub下载项目模板,举个栗子🌰
  // 项目地址是这样:https://github.com/vipsunwei/cnode-react-demo
  // 我们需要的是去掉前边协议和域名后剩下的部分
  // 那么clone方法需要传的repo应该等于
  // github:vipsunwei/cnode-react-demo
  await clone('github:vipsunwei/cnode-react-demo', name)
  log(`\n🚗 创建完成`)
  // 这里可以做个小提示,进入项目所在目录进行安装项目依赖
  console.log(`-------------- \n`)
  log(`cd ${name} \n`)
  log(`npm install`)
  log('or')
  log(`yarn install \n`)
  console.log(`--------------`)
}
  • 好了我们试一下,执行命令我们看到了正在从GitHubclone项目模板下来
demo init hehe

  • clone完成后会看到下图:

自动安装项目依赖

  • node.js提供的child_process进行安装项目依赖
// 由于spawn是异步的,先用promise封装一下
const spawn = async (...args) => {
  const {spawn} = require('child_process')
  return new Promise(resolve => {
    const child = spawn(...args)
    // 将子进程的输出接到主进程上
    child.stdout.pipe(process.stdout) 
    // 将子进程的错误接到主进程上
    child.stderr.pipe(process.stderr) 
    // 当子进程关闭说明子进程执行完毕,执行resolve()
    child.on('close', () => resolve()) 
  })
}
  • 修改init方法的代码,在init方法中执行spawn进行安装项目依赖
const init = async (name) => {
  ...
  ...
  log(`🚗 创建项目:${name} \n`)
  // 从GitHub下载项目模板
  await clone('github:vipsunwei/cnode-react-demo', name)
  log(`\n🚗 创建完成`)
  log(`🔨 安装依赖`)
  await spawn('yarn', ['install'], {cwd: `./${name}`})
  // 这里的提示语根据你自己的模板项目使用的包管理器自行修改,我的包管理器使用的是 yarn
  // 执行启动命令根据你自己的模板项目 package.json -> scripts 中的定义自行修改
  log(`
👌 安装完成
➡️  启动项目
----------------
  cd ${name}
  yarn run start  
----------------

  `)
}
  • 我们试一下看看效果,记得把之前测试生成的项目先删掉
demo init hehe

自动启动项目并且打开浏览器

  • 上面我们给了手动启动项目的提示,我们也可以使用程序自动启动项目,并且也可以使用open自动打开浏览器(可选)
// 继续修改init方法
const init = async (name) => {
  ...
  ...
  log(`🔨 安装依赖`)
  await spawn('yarn', ['install'], {cwd: `./${name}`})
  log(`
👌 安装完成
➡️  启动项目
----------------
  cd ${name}
  yarn run start  
----------------

  `)

  // 自动启动项目
  await spawn('yarn', ['run', 'start'], {cwd: `./${name}`})

  // 自动打开浏览器(可选),如果你使用webpack也可以在模板项目的webpack-dev-server中配置
  require('open')()
}
  • 创建个项目下面看下效果
demo init hehe

  • 代码编译的同时浏览器也自动打开了,浏览器打开后会白屏loading,这是项目代码还没有完全编译完毕,编译完毕提示项目已经成功的在3000端口上运行起来了,浏览器会自动更新出首页内容