我们要写一个叫hello-cli
的脚手架工具,它要做的是从GitHub拉去项目模版代码,把项目信息填入模版内即可。这是一个简单但是很常见的脚手架功能。脚手架源码
准备
开发脚手架工具,我们需要用到几个工具库
- commander.js nodejs命令行完整解决方案,提供解析命令等功能,写脚手架必备库
- inquirer.js 命令行交互工具,支持大部分形式。写脚手架必备库
- download-git-repo 下载GitHub库
- metalsmith 用于处理下载好的模版文件
- handlebars 用户替换变量
- ora.js 命令行loading样式,非必须
- chalk.js 命令行文本加颜色背景等样式,非必须
- log-symbols.js 给
console.log
添加有颜色的级别,例如error
success
,非必须
- alphabetjs 字符文字样式,主要用于项目名称,非必须
开发
初始化项目,在package.json
里面写入bin
字段。key
定义cli的命令hello-cli
,value
定义了命令的入口文件。
1 2 3
| "bin": { "hello-cli": "bin/hello.js" }
|
入口文件hello.js
首先要在所有的命令入口文件前写#!/usr/bin/env node
声明使用当前系统的node
环境。这里会使用alphabetjs
把项目名字输出一个字符文字,然后再定义一下usage
description
version
脚手架的基础信息。command方法定义一个命令,命令的接收后处理需要写到一个名为 入口名-命令名.js 文件里面,这里我们需要在bin下面创建一个hello-create.js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #!/usr/bin/env node
const commander = require('commander') const alphabetjs = require('alphabetjs') const chalk = require('chalk') const title = alphabetjs('Hello Cli','stereo') const packageInfo = require('../package.json')
commander.addHelpText('beforeAll', chalk.greenBright(title));
commander .usage("[options]/[command]") .description('Hello World') .version(packageInfo.version) .command('create', 'Create an project') .parse(process.argv);
|
create命令入口文件hello-create.js
使用commander.parse()
方法解析输入的参数作为项目名,download
方法下载模版,generator
方法用来生成模版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| #!/usr/bin/env node
const commander = require('commander') const ora = require('ora') const chalk = require('chalk') const logSymbols = require('log-symbols') const download = require('../modules/download') const prompt = require('../modules/prompt') const generator = require('../modules/generator')
commander.parse(process.argv)
const projectName = commander.args[0]
const spinner = ora('downloading template');
if (!projectName) { program.help() return }
async function main (projectName) { const path = `${process.cwd()}/${projectName}` spinner.start() try { await download(path) } catch (error) { spinner.stop() console.log(logSymbols.error, chalk.red('download error')) console.log(chalk.red(error)) return } spinner.stop() const projectInfo = await prompt(projectName) try { await generator(projectInfo, path, path) } catch (error) { console.log(logSymbols.error, chalk.red('generator error')) console.log(chalk.red(error)) return } console.log(logSymbols.success, chalk.green('Finished successfully!')) }
main(projectName)
|
download.js
说明
download-git-repo
这个库可以下载Github
Gitlab
项目。默认下载GitHub
,我这里是下载blogwy
用户的cli-demo-template
库master
分支
1 2 3 4 5 6 7 8 9
| const download = require('download-git-repo')
module.exports = async function (path) { return new Promise((resolve, reject) => { download('blogwy/cli-demo-template#master', path, function (err) { err ? reject('Error') : resolve('Success') }) }) }
|
generator.js
说明
使用handlebars
解析模版文件,模版文件需要接收参数的地方使用{{}}
包裹,我这里把二进制文件ico png
排除掉,因为会报错。把vue
文件也排除掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const Metalsmith = require('metalsmith') const Handlebars = require('handlebars')
module.exports = function (metadata, src, dest) { if (!src) { return Promise.reject(new Error(`无效的source:${src}`)) } return new Promise((resolve, reject) => { Metalsmith(process.cwd()) .metadata(metadata) .clean(false) .source(src) .destination(dest) .use((files, metalsmith, done) => { const meta = metalsmith.metadata() Object.keys(files).forEach(fileName => { const fileNameArray = fileName.split('.') const disabledArray = ['vue', 'ico', 'png'] if (!disabledArray.includes( fileNameArray[fileNameArray.length - 1])) { const t = files[fileName].contents.toString() files[fileName].contents = Buffer.from(Handlebars.compile(t)(meta)) } }) done() }).build(err => { err ? reject(err) : resolve() }) }) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "name": "{{projectName}}", "version": "{{projectVersion}}", "description": "{{projectDescription}}", "category": "{{projectCategory}}", "poster": "{{projectPoster}}", "author": "{{projectAuthor}}", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "license": "ISC", "dependencies": {}, "devDependencies": {} }
|
测试
npm link
会把当前开发目录映射到全局,使用hello-cli
就可以测试,测试完后npm unlink
取消映射
发布
- 注册npm账户
- 执行
npm login
登录npm
- 执行
npm publish
发布包
安装
使用