#!/usr/bin/env node const program = require('commander') program.usage('<project-name>').parse(process.argv) // 根据输入,获取项目名称 let projectName = program.args[0] if (!projectName) { // project-name 必填 // 相当于执行命令的--help选项,显示help信息,这是commander内置的一个命令选项 program.help() return } go() function go () { // 预留,处理子命令 }
注意第一行 #!/usr/bin/env node 是干嘛的,有个关键词叫Shebang,不了解的可以去搜搜看
project-name 是必填参数,不过,我想对 project-name 进行一些自动化的处理。
当前目录为空,如果当前目录的名称和 project-name 一样,则直接在当前目录下创建工程,否则,在当前目录下创建以 project-name 作为名称的目录作为工程的根目录
当前目录不为空,如果目录中不存在与 project-name 同名的目录,则创建以 project-name 作为名称的目录作为工程的根目录,否则提示项目已经存在,结束命令执行。
根据以上设定,再对执行文件做一些完善
#!/usr/bin/env node const program = require('commander') const path = require('path') const fs = require('fs') const glob = require('glob') // npm i glob -D program.usage('<project-name>') // 根据输入,获取项目名称 let projectName = program.args[0] if (!projectName) { // project-name 必填 // 相当于执行命令的--help选项,显示help信息,这是commander内置的一个命令选项 program.help() return } const list = glob.sync('*') // 遍历当前目录 let rootName = path.basename(process.cwd()) if (list.length) { // 如果当前目录不为空 if (list.filter(name => { const fileName = path.resolve(process.cwd(), path.join('.', name)) const isDir = fs.stat(fileName).isDirectory() return name.indexOf(projectName) !== -1 && isDir }).length !== 0) { console.log(`项目${projectName}已经存在`) return } rootName = projectName } else if (rootName === projectName) { rootName = '.' } else { rootName = projectName } go() function go () { // 预留,处理子命令 console.log(path.resolve(process.cwd(), path.join('.', rootName))) }
随意找个路径下建一个空目录,然后在这个目录下执行咱们定义的初始化命令
node /[pathto]/macaw-cli/bin/macaw.js init hello-cli
正常的话,可以看到终端上打印出项目的路径。
使用download-git-repo下载模板
下载模板的工具用到另外一个node模块download-git-repo ,参照项目的README,对下载工具进行简单的封装。
在 lib 目录下创建一个 download.js
const download = require('download-git-repo') module.exports = function (target) { target = path.join(target || '.', '.download-temp') return new Promise(resolve, reject) { // 这里可以根据具体的模板地址设置下载的url,注意,如果是git,url后面的branch不能忽略 download('https://github.com:username/templates-repo.git#master', target, { clone: true }, (err) => { if (err) { reject(err) } else { // 下载的模板存放在一个临时路径中,下载完成后,可以向下通知这个临时路径,以便后续处理 resolve(target) } }) } }
download-git-repo模块本质上就是一个方法,它遵循node.js的CPS,用回调的方式处理异步结果。如果熟悉node.js的话,应该都知道这样处理存在一个弊端,我把它进行了封装,转换成现在更加流行的Promise的风格处理异步。
再一次对之前的 macaw-init.js 进行修改
const download = require('./lib/download') ... // 之前的省略 function go () { download(rootName) .then(target => console.log(target)) .catch(err => console.log(err)) }
下载完成之后,再将临时下载目录中的项目模板文件转移到项目目录中,一个简单的脚手架算是基本完成了。转移的具体实现方法就不细说了,可以参见node.js的API。你的node.js版本如果在8以下,可以用stream和pipe的方式实现,如果是8或者9,可以使用新的API——或者 。
but...
这个世界并非我们想象的那么简单。我们可能会希望项目模板中有些文件或者代码可以动态处理。比如:
新项目的 名称 、 版本号 、 描述 等信息等,可以通过脚手架的交互进行输入,然后将输入插入到模板中
项目模板并非所有文件都会用到,可以通过脚手架提供的选项移除掉那些无用的文件或者目录。
对于这类情况,我们还需要借助其他工具包来完成。
使用inquirer.js处理命令行交互
对于命令行交互的功能,可以用inquirer.js 来处理。用法其实很简单: