进入正题
1、首先新建一个文件夹dva-native-cli,然后执行npm init,当然你也可以自己创建package.json文件
2、然后新建bin目录,用来存放执行文件,template目录用来存放项目模板
最后目录树应该是这样
- /bin
- dva-native.js
- /template
- test
- android
- app
- ios
- app.json
- index.js
- jsconfig.json
- package.json
- README.md
- packge.json
3、命令行工具,顾名思义,最重要的就是在终端输入的命令,以及如何让系统能够识别
- 如何让npm识别命令? 先在package.json文件里面增加以下字段
"bin": {
"dva-native": "./bin/dva-native.js"
}
dva-native
即为我们需要识别的命令,最好不要跟其他命令行工具重名该字段后面的值意思就是执行dva-native后需要运行的代码
值得一提的是,dva-native .js文件开头需要使用 ! /usr/bin/env node来指定使用node执行该文件
好了,最后在命令行下执行npm link,再输入dva-native就能被识别了
- 如何获得命令行下输入的参数?
很幸运,node可以很轻易的实现,使用process.argv即可,但是我们一般使用process.argv.slice(2) 前两个参数为node所在地址和正在执行文件的地址,对于本教程来说,就是dva-native.js,除此之外我们需要获得命令执行的位置,可以使用process.cwd()获得 然后我们就可以去实现 dva-native -v dva-native --help dva-native new app等命令了,第一个读取package.json里面的name字段打印即可,第二个捕获–help参数根据自己需要打印提示信息就OK了。
4、示例如下:
if (argv[0] === "--help") {
console.log("Use dva-native new project to create a new project!");
return;
}
if (argv[0] === "-v") {
console.log(require("../package.json").version);
return;
}
}
if (argv[0] !== "new") {
console.warn(
`You should use dva-native new to create you app, do not use dva-native ${
argv[0]
}`
);
return;
}
最后一个就是最重要的了,执行这个命令后需要在命令执行所在的地址下新建app文件夹,并将template文件夹下的所有文件拷贝过去,但是对于一个友好的cli工具来说,如果用户需要新建的文件夹已存在,需应该示用户是否需要覆盖该文件夹
5、如何进行命令行交互呢?
node又提供了一个强有力的模块readline
直接引入const readline = require("readline");
当目标文件夹已存在时,执行以下代码
const terminal = readline.createInterface({
input: process.stdin,
output: process.stdout
});
terminal.question(
"Floder is already exits, do you want override it ? yes/no ",
answer => {
if (answer === "yes" || answer === "y") {
resolve(true);
terminal.close();
} else {
process.exit();
}
}
);
当用户允许覆盖时,返回true,然后停止接受命令行的输入,否则退出进程
6、 如何拷贝整个文件夹?
function traverse(templatePath, targetPath) {
try {
const paths = fs.readdirSync(templatePath);
paths.forEach(_path => {
const _targetPath = path.resolve(targetPath, _path);
const _templatePath = path.resolve(templatePath, _path);
console.log("creating..." + _targetPath);
if (!fs.statSync(_templatePath).isFile()) {
fs.mkdirSync(_targetPath);
traverse(_templatePath, _targetPath);
} else {
copyFile(_targetPath, _templatePath);
}
});
} catch (error) {
console.log(error);
return false;
}
return true;
}
function copyFile(_targetPath, _templatePath) {
fs.writeFileSync(_targetPath, fs.readFileSync(_templatePath), "utf-8");
//fs.createReadStream(_targetPath).pipe(fs.createWriteStream(_templatePath));
}
由于后面我们需要执行npm intsall安装模块依赖,所以我们需要知道什么时候拷贝完成了,所以读取文件目录,写入文件均采用同步方式执行。使用readdirSync遍历目录,如果是目录,递归,否则进行拷贝操作,这里使用trycatch是为了捕获错误,有可能该文件夹下node并没用权限去进行操作,最后拷贝完成返回true
7、最后,如何执行上文提到的npm install呢?
提示用户自己输入吗?显然不是,这里我们借助const exec = require("child_process").exec;
来完成命令执行
exec(`cd ${projectName} && npm install`, err => {
if (err) {
console.log(err);
process.exit();
} else {
console.log(
`
Success! Created ${projectName} at ${cwdPath}.
Inside that directory, you can run several commands and more:
* npm start: Starts you project.
* npm test: Run test.
We suggest that you begin by typing:
cd ${projectName}
npm start
Happy hacking!`
);
}
});
其实还可以使用swapn来完成,区别参考https://www.cnblogs.com/xiaoniuzai/p/6889164.html,上述的文件拷贝操作也可以借助swapn来完成,只不过mac和windows下的操作有点不一样我就没有采用了,具体使用参考https://github.com/dvajs/dva-cli/blob/master/src/install.js#L3
至此,一个简单的cli工具已编写完成,但是我们没有考虑生成途中用户使用^C退出后该如何进行处理