起步
bin目录下创建zh-cli.js
1 |
|
执行node bin/zh-cli.js
,即可输出
1 | "bin": { |
本地npm link
, 即可执行zhcli
, 等价于node bin/zh-cli.js
。
==npm是如何识别并执行对应的文件?==
commander
Nodejs 命令行解决方案。
使用
1 | const program = require("commander"); |
命令
.command()
.command()
的第一个参数为命令名称。命令参数可以跟在名称后面,也可以用.argument()
单独指定。参数可为必选的(尖括号表示)、可选的(方括号表示)或变长参数(点号表示,如果使用,只能是最后一个参数)。
1 | program |
.parse(params, [params])
1 | program.parse(process.argv); // 指明,按 node 约定 |
新增node命令:
1 | program |
终端输入zhcli -h
,可以查看新增的命令及注释。
通过commander对用户输入的参数进行解析,只接受一个参数,多余的参数不处理。然后对用户输入的projectName进行处理。
入口文件zh-cli.js
:
1 |
|
校验输入的projectName
validate-npm-package-name
检验字符串是否是一个有效的包命名。
包命名规则
包名不能是空字符串;
所有的字符串必须小写;
可以包含 连字符 - ;
包名不得包含任何非 url 安全字符;
包名不得以 . 或者 _ 开头;
包名首尾不得包含空格;
包名不得包含 ~)(‘!* 任意一个字符串;
包名不得与node.js/io.js 的核心模块 或者 保留名 以及 黑名单相同;
包名的长度不得超过 214;
使用
1 | const validateProjectName = require("validate-npm-package-name") |
命令行交互
我们的需求
我们需要提供几套模板给用户选择,比如:
- 纯前端工程(CSR模式,不带node)
- hobber + React前端模板
- SSR工程
- …
我们需要指定提示并获取用户输入的内容,比如:
- 项目描述
- 初始化版本号
- 是否自动安装依赖
- 前端默认端口
- PC端还是移动端
- …
了解了我们的需求,==inquirer==可以满足我们的需求。
inquirer:命令行交互问询。
Methods
inquirer.prompt(questions, answers) -> promise
启动提示接口。
questions(Array): Question Object
answers(Object)
Question Object:
使用
1 | var inquirer = require('inquirer'); |
执行结果如图所示:
预置模板 & 收集信息
我们先创建一个template的json串:==templatePreset.js==
1 | module.exports = [ |
当选择了CSR或者SSR之后,提供meta设置,通过==inquirer.prompt==用户输入提前预置的问题,并收集用户输入的所有answer,==meta.js==:
1 | module.exports = () => { |
当用户输入完信息之后,需要对用户的输入信息进行收集:
1 | // 收集meta问题用户的answer |
于此同时,需要去clone已经提前预置好的模板,我们内置了两套模板仓库,分别为CSR和SSR的模板。在==templatePreset.js==里已经内置了仓库地址。
Clone 模板到本地缓存
clone的方式分为https和ssh,这里我们也提供两套clone方式去使用。
https: download-git-repo
1 | download(`direct:${gitDir.git}`, tmpdir, {clone: true}, function (err) { |
- 注意事项,默认是master分支,非master分支:direct:${gitDir.git}/#${branchName}
ssh: child_process.spawn()
child_process.spawn(command[, args][, options])
为什么要用到child_process.spawn?
我们当前运行的命令是我们执行的主进程,不可被打断。如果我们要clone提前预置的仓库模板且不影响主进程,通过child_process开启子进程去运行clone命令。创建一个shell,然后在shell里执行命令。执行完成后,将stdout、stderr作为参数传入回调方法。
参数说明:
包装runCommand方法:
1 | function runCommand(cmd, args, options) { |
对clone的过程进行一个内容的输出:
1 | exports.gitClone = ({ cwd, gitSSH, tmpName }) => { |
执行clone的命令:
1 | const tmpdir = path.join(path.join(os.tmpdir(), "zhcli-presets-temp"), presetName); |
==os.tmpdir()==: 远程模板仓库clone成功后,暂存到本机的缓存目录里。
在缓存目录下可以看到模板仓库:
走到这一步已经能拿到的信息有:
1 | { |
下一步就是生成模板文件,如果只是个人使用的,就直接生成模板文件就可以了,如果是涉及到公司的流水线部署时,这里会涉及到集成CI/CD的一个流程,后面会以阿里云服务器为例进行讲解。
generator file
根据tmpdir的缓存模板目录,通过fs对文件进行递归遍历,拿到每一个目录下的所有的文件。
代码参考:
1 | function getFileList(dir) { |
对拿到的fileList通过async.each()进行异步遍历,用fs.readFileSync()拿到每一个文件的内容进行写操作。其中对每个文件的lastIndexOf(“/“)执行fs.mkdif()建立文件夹,绝对路径执行fs.writeFile()写操作。
其中基于当前主进程的路径组合绝对路径:当前路径 + appName + 每一个文件的相对路径。
1 | const path = path.join(process.cwd(), appName, relativePath); |
关键代码:
1 | /** |
1 | module.exports = ({ |
至此,缓存模板写入当前路径文件已完成。
相关知识点:模板引擎替换https://www.tabnine.com/code/javascript/functions/consolidate/Consolidate/handlebars
gif图实现效果图:
阿里云ecs申请购买服务器,安装nginx服务,配置安全组,开启80端口。
访问线上地址:
在本地启动demo项目
1 | npm run start |
修改其中一行代码:
package.json中配置的script命令:
build命令和deploy.sh发线上部署脚本都已经内置在脚手架中,创建新项目的时候,会一并附带来,将部署脚本集成到脚手架,且脚手架可能只多套脚本。
发版和部署:
1 | npm run build |
刷新线上地址: