脚手架cli入门,基于commander搭建

脚手架cli入门,基于commander搭建

八月 20, 2023 评论 10 阅读 504 点赞 0 收藏 0

背景

在搭建环境过程中,发现总有一些常用的工具,要经过繁琐的步骤才能生成。

思考着怎么简化这个工作。偶然想起各种脚手架挺方便的,那就需要看看cli是如何生成的。

笔者分享一下整个入门的过程中。

内容

了解到的node的脚手架,是基于Commander生成。

对应github: https://github.com/tj/commander.js

来看官网的介绍:

编写代码来描述你的命令行界面。 Commander 负责将参数解析为选项和命令参数,为问题显示使用错误,并实现一个有帮助的系统。

功能简介

可能可看到几个重要的入门点:

选择值,命令参数,判断,输入,拷贝文件,函数等。

实例扩展

1)新建项目: 安装cb

  • npm init,输入对应的数据,生成package.json
  • 定义bin命令,笔者的cli命令为cb

执行index,js的代码,故:

  "bin": {
    "cb": "index.js"
  },
  • 引入commander,分享最终的package.json:
      {
        "name": "cb-cli",
        "version": "1.0.0",
        "description": "分享搭建cli过程",
        "directories": {
          "lib": "lib"
        },
        "bin": {
          "cb": "index.js"
        },
        "scripts": {
          "build": "cb build"
        },
        "main": "index.js",
        "repository": {
          "type": "git",
          "url": "git+https://github.com/zhuangweizhan/cb-cli.git"
        },
        "keywords": [
          "cb",
          "cli"
        ],
        "author": "zhuangweizhan",
        "license": "ISC",
        "bugs": {
          "url": "https://github.com/zhuangweizhan/cb-cli/issues"
        },
        "dependencies": {
          "commander": "^6.2.1",
          "fs-extra": "^10.0.0",
          "execa": "^5.0.0",
          "inquirer": "^7.3.3",
          "minimist": "^1.2.6",
          "mustache": "^4.2.0"
        },
        "homepage": "https://github.com/zhuangweizhan/cb-cli#readme"
      }
    

值得一提的是,需要声明,如果都需要用es6的import,需要"type": "module", 不然后续会影响import的引入。

2)入门案例: cb build

上边配置的bin, key就是命令名称,而value就是对应的路径。

所以新建index.js:

#!/usr/bin/env node

const program = require('commander')

program.version('1.0.0')

program.command('build').description('打包cli成功')

program.parse(process.argv)

此时第一个cli已经写好,本地安装一下: npm link

即可执行:cb 与 cb build

3)命令传参: cb param

假设需要接收外部的参数,供脚手架使用,可以借助action的options获取,代码如下:

#!/usr/bin/env node


const program = require('commander')

program.version('1.0.0')

const getParams = options => {
  options || (options = [])
  return options.reduce((prev, cur) => {
    if (cur.includes('=')) {
      const array = cur.split('=')
      return { ...prev, [array[0]]: array[1] }
    } else {
      return prev
    }
  }, {})
}

program
  .command('build')
  .description('打包cli命令')
  .action((appName, options) => {
    // console.log(`打包cli成功, options`, options.template)
    const params = getParams(options)
    console.log(`打包cli成功, 对应的参数是`, params)
  })

program.parse(process.argv)

4)读取配置文件: cb config

如果参数角度的情况下,命令传参已经无法满足。此时需要借助配置文件,如常见的vue.config.js等。教程如下:

新建cb.config.js

module.exports = {
  name: '成功咯',
}

再新建脚本:

program
  .command('config')
  .description('读取配置文件')
  .action((appName, options) => {
    let config = {
      path: 'svg',
    }

    const configPath = CWD + '\\cb.config.js'
    // 如果使用了配置文件,则以配置文件为准
    if (existsSync(configPath)) {
      const userConfig = require(resolve(CWD, 'cb.config.js'))
      config = { ...config, ...userConfig }
      console.log(`存在配置文件cb.config.js,获取到的名字为`, config.name)
    } else {
      console.log(`不存在配置文件`)
    }
  })

5)条件/判断: cb condition

cli少不了,让用户快速选择,快速输入的情况。
如vue-cli,需要让你输入项目名称,选择是否需要eslint等。

这一般都是借助inquirer实现。直接查看案例:

#!/usr/bin/env node
const CWD = process.cwd()
import commander from 'commander'
import fs from 'fs'
import inquirer from 'inquirer'
import path from 'path'

const { resolve } = path
const { program } = commander

program
  .command('condition')
  .description('选择属性')
  .action((appName, options) => {
    inquirer
      .prompt([
        {
          type: 'confirm',
          name: 'language',
          message: '新建项目是否引入typescript?',
        },
        {
          type: 'input',
          name: 'desc',
          message: '请输入项目备注',
        },
      ])
      .then(result => {
        console.log('请输入', result)
      })
  })

program.parse(process.argv)

6)新建/拷贝页面: cb create

实际脚手架中,有很多使用到的页面。如vue-cli,他自带了许多页面。那如何新建一个页面呢?

直接看代码:

program
  .command('create')
  .description('创建index文件')
  .action(async (appName, options) => {
    const copyPath = `/template/demo.vue`
    const pageName = 'new'
    console.log(path.join(CWD, copyPath))
    let template_content = await fs.readFile(path.join(CWD, copyPath))
    template_content = template_content.toString()
    const result = Mustache.render(template_content, {
      pageName,
    })
    //开始创建文件
    await fs.writeFile(path.join('./dist/', `${pageName}.vue`), result)
    console.log('\n页面创建成功!\n')
  })

program.parse(process.argv)

此时,执行cb create后,将产生新文件:

7)注入依赖: cb rely vue

我们还会经常遇到一个脚手架的方法,通常会帮你配置好package.json, 然后帮你npm install。这就是自动注入依赖。

我们需要借助execa来实现。

// 注入依赖
program
  .version('0.1.0')
  .command('rely <name>')
  .description('新建一个项目注入依赖')
  .action(name => {
    create(name)
  })
  
  

// create.js

const dir = process.cwd()

const execa = require('execa')
const fs = require('fs-extra')
const path = require('path')

async function create(projectPath) {
  // package.json 文件内容
  const packageObject = {
    name: projectPath,
    version: '0.1.0',
    dependencies: {
      vue: '^2.6.12',
    },
    devDependencies: {},
  }

  const packagePath = projectPath + '/package.json'

  const filePath = path.join(dir, packagePath)
  fs.ensureDirSync(path.dirname(filePath))
  fs.writeFileSync(filePath, JSON.stringify(packageObject))

  console.log('\n正在下载依赖...\n', filePath)
  // // 下载依赖
  execa('npm install', [], {
    cwd: path.join(dir, projectPath),
    stdio: ['inherit', 'pipe', 'inherit'],
  })

  console.log(`下载成功 ${projectPath}`)
  console.log(`cd ${projectPath}`)
  console.log(`npm run dev`)
}

module.exports = create

8)发布npm

此时,已经完成一个简单的脚手架,就剩下最后一步,发布npm。

这里暂不分享,下篇整理后为专文输出。

源码

源码地址:https://github.com/zhuangweizhan/cb-cli

记得切换v1分支。

*
*
*