一、什么是命令行工具
命令行的定义:能够在终端执行的系统命令。
比如:前端同学熟悉的npm、git、create-react-app以及linux系统的ls、vim等等。
使用 which 追根究底,发现它们实际执行的路径在某一个 bin 目录下
为什么这些命令可以直接使用,而不用输入它的完整路径?
原因:在环境变量PATH 中路径的命令可在其它任意地方执行,而这些 bin 目录就在环境变量 PATH 中
所以开发命令行的原理也是如此,就是将你开发的命令行工具脚本置于环境变量 PATH 下的路径之中
二、Node 全局命令行的原理
- npm 把包下载到某个路径下,比如 :/usr/local/lib/node_modules
- 根据该包的 package.json 中
bin字段的指示,把对应的命令行路径通过软连接的方式挂载到PATH - 给对应的二进制脚本添加可执行文件权限
三、最简单的命令行工具demo
- 新建一个cli项目,比如
learn-cli - 在learn-cli下新建
bin/index.js并输入下面内容
#!/usr/bin/env node # 告诉终端用node来运行这段代码
console.log("hello gdc");
#!加解释器,标明该文件使用/usr/bin/env node来执行
/usr/bin/env为 env 的绝对路径,用以在 PATH 路径中执行命令 (在各种不同的系统中,node 命令行的位置不同,因此使用env node找到路径并执行)
- 使用npm初始化项目
npm init -y
4. 修改package.json文件,添加下面代码
5. 给全局添加命令
sudo npm link
- 此时就可以全局使用learn-cli的命令了
通过which learn-cli,我们可以看到,命令learn-cli,实际指向.nvm/versions/node/v16.18.0/bin/下的一个名字叫learn-cli的软连接,而这个软连接又指向../lib/node_modules/learn-cli(learn-cli整个文件夹就是个软连接),这个软连接又指向了我们的项目。
同样通过npm i create-react-app -g全局安装的create-react-app命令,也是通过软连接的方式,指向实际的文件夹
window系统下,“软连接”的表现方式有所不同
粗读create-react-app.sp1的源码,可以看到window下软连接的表现形式,是power shell可执行文件。
四、命令行参数解析
node开发的命令行,可以通过process.argv来获取用户输入。比如:
当然解析参数也要参照 POSIX 兼容的基本规律: 格式、可选、必选、简写、说明、帮助等等。命令行工具命名协议 (opens new window)文章中已说的足够详细。
// 一个较为规整的命令行帮助
$ node --help
Usage: node [options] [ script.js ] [arguments]
node inspect [options] [ script.js | host:port ] [arguments]
Options:
- script read from stdin (default if no file name is provided,
interactive mode if a tty)
-- indicate the end of node options
--abort-on-uncaught-exception aborting instead of exiting causes a core file to be generated for
analysis
-c, --check syntax check script without executing
--completion-bash print source-able bash completion script
--cpu-prof Start the V8 CPU profiler on start up, and write the CPU profile to
disk before exit. If --cpu-prof-dir is not specified, write the profile
to the current working directory.
但是github上已经有大佬帮我们写好了相关的包,比如:tj大神的commander
commander的基本用法
- 文档:github.com/tj/commande…
- demo
const { program } = require("commander");
const commands = require("./commands/index");
const { name, version } = require("../package.json");
program
.name(name)
.description("使用node封装的个人cli工具")
.version(version, "-v, --version", "查看版本号")
.option("-h, --help", "查看所有的用法")
.helpOption(false)
.addHelpCommand(false);
commands.forEach((item) => {
program
.command(item.command)
.description(item.description)
.argument(item.argument)
.action(item.action);
});
program.parse(); // parse 默认值为process.argv
效果:
五、使用inquirer实现命令行可交互
1.安装
npm i inquirer
2.inquirer官方文档:github.com/SBoudrias/I…
3.设置问题和答案
#!/usr/bin/env node
const inquirer = require("inquirer");
const answerOptions = {
获取初始化页面模版: "init",
"启动项⽬": "start",
};
const question = [
{
type: "list" /* 上下选择框 */,
message: "请选择操作?",
name: "operation",
choices: Object.keys(answerOptions),
},
];
inquirer
.prompt(question)
.then((res) => {})
.catch((err) => {});
4.效果
六、使用git-pull-or-clone克隆项目代码
1.安装
npm i git-pull-or-clone
2.代码
const { promisify } = require("util");
const download = promisify(require("git-pull-or-clone"));
const { spawn } = require("child_process");
const spawnFn = (...args) => {
return new Promise((resolve) => {
const proc = spawn(...args);
proc.stdout.pipe(process.stdout);
proc.stderr.pipe(process.stderr);
proc.on("close", () => {
resolve();
});
});
};
module.exports = () => {
const name = "xxxx";
const repo = "git@gitlab.com:xxxx";
/**
* path.resolve(`./${name}`) ===> 执行命令行时的路径 + /xxx
* path.resolve(__dirname, `./${name}`) ===> 源代码所在的文件路径+/blog
*/
const desc = path.resolve(`./${name}`);
download(repo, desc)
.then((res) => {
console.log("安装依赖");
// spawnFn("npm", ["install"], { cwd: `${desc}` })
// cwd是package.json所在的路径,否则报spawn npm ENOENT错误
spawnFn("yarn", [""], { cwd: `${desc}` }).then((res) => {
console.log(`
安装完成:
To get Start:
===========================
cd ./${name}
yarn start:dev
===========================
`);
});
})
.catch((err) => {
console.log("try err", err);
});
};
3.效果
七、美化
1.打印欢迎画⾯
npm i figlet clear chalk
#!/usr/bin/env node
const { promisify } = require("util"); // util是node内置包;util.promisify() 这个方法,方便我们快捷的把原来的异步回调方法改成返回 Promise 实例的方法
const figlet = promisify(require("figlet"));
const clear = require("clear");
const chalk = require("chalk");
const log = (content) => console.log(chalk.green(content));
// 打印欢迎画⾯
clear(); // 先执行命令行的clear方法清屏
log(
figlet.textSync("hello gdc", {
width: 80,
})
);
2.下载loading
npm i ora@5.3.0
// 最新版的ora不支持commonJS引入,只支持es module,所以需要降版本到5.3.0
#!/usr/bin/env node
const ora = require("ora");
const progress = ora();
progress.start('下载中....');
setTimeout(()=>{
progress.succeed('下载成功');
}, 2000)
setTimeout(()=>{
progress.fail('下载失败');
}, 4000)