不想写需求?摸鱼也要写 CLI 命令!(1)

1,719 阅读5分钟

「这是我参与2022首次更文挑战的第32天,活动详情查看:2022首次更文挑战」。

一、背景

预祝点赞的看官老爷成为尊贵的奥迪车主!

年初刚开工,由于年前啥都不发,年后连个开工红包也没有,我表示摸鱼愿望强烈,要不怎么对得起摸鱼专家的 title 呢?所以需求一个都不写,就是摸鱼,什么?上线怎么办?我也不知道怎么办,在线等,挺急的。。。

需求不写是可以的,但是代码不能停啊。

事情是这样的,我司有个用于开发环境查询短信验证码的平台,每天查询验证码需要一下几步:

  1. 打开平台网址;
  2. 登录你的账号;
  3. 接着进入到短信验证码查询;
  4. 接着输入测试号,当然可以从 input 的输入历史中选择一下或者贴进去;
  5. 再按下回车,然后等 loading 结束;
  6. 最后从页面上文字用鼠标选中、复制到剪贴板;
  7. 最后去 CV

这是以前的操作路数,后来不知道这个脑血栓晚期的平台发了什么并发症,他居然又在这之上加了一个选择业务的过程,而且没办法定制默认业务,每次打开都是人家的业务,更坑爹的是,选完业务它又要全局刷新页面,内部工具速度懂的都懂,慢的不要不要的。。。。

对于我这种急性子(摸鱼时从来都不),咋办?赞扬这平台的产品和开发都大聪明(MDZZ)?

这我已经做了好多次,但是并不会加速我验证码的获取速速。所以解决问题还得靠摸鱼!

恰逢我们做 E2E 测试时接入了这个平台的 open-api,当时就闪现了一个想法,何不写个命令行工具通过 terminal 去获取验证码,这不就可以绕开这些 GUI 界面,省下的时间不就可以用来摸鱼了吗。

二、需求

有了上面的背景,大家对需求也已经很清楚了,但是作为一个有追求的摸鱼专家,怎么能用 axios 发个请求行了呢?

  1. 写个全局的 CLI 命令,我们叫他 joymax(为啥joymax,超喜欢三阳joymaxZ300,上次写的一个工具名 rs7,源自奥迪RS7),调用后可以从工具平台拉取验证码;
  2. 得到验证码后直接写入到系统剪贴板,可以直接 CV(CMD + V)
  3. 拉取验证码时可以通过手机号,也可以提前调用命令为手机号设置的别名,比如我把 7384311234 设置为 tf
  4. 第二步骤需要一个设置别名的需要,所以还得有个命令设置别名,当然还得有地方保存;
  5. 我们团队内部业务很多,卷起来,当然得能够复用到别人家的业务,所以得命令要支持配置业务类型等参数;

三、技术点准备

先搞个技术选型吧,我选 TS + esbuild,选型过程:

  1. 举起右手;
  2. 朝自己的额头轻拍三下; 好了,选型完毕😂 😂,开玩笑的哈,前面我们搞 E2E 时写 SDK 同事推荐用 TSNode.js,用 TSNode.js 调试不方便,要实时编译成 js。至于用 esbuild,就是制造用的机会,我司业务用 webpack 打包,用 esbuild 属于没有机会就制造机会。

3.1 全局安装的命令

这个好解决,如果你读过我前面的 来吧,撸一个快速 git 提交工具 ,你就知道答案了。想让命令全局安装,只需要在 package.json 中正确配置 bin 字段,bin 字段对应可执行文件,如下:

{
  "name": "some-smscode",
  "version": "0.0.4",
  "description": "a cmd for challenge code",
  "main": "bin/joymax.js",
  "scripts": {
    "test": "bin/joymax.js"
  },
  "bin": {
    "joymax": "bin/joymax.js" // 这里
  },
  "keywords": [
    "challenge code"
  ],
  "author": "TouchFish",
  "license": "ISC",
  "dependencies": {
    "@xxxxx/some-tasks": "^1.0.3",
    "commander": "^9.0.0"
  },
  "devDependencies": {
    "chalk": "^5.0.0",
    "esbuild": "^0.14.21"
  }
}

some-smscodenpm i -g some-smscode,全局安装后就可以全局执行 joymax,如下:

$ joymax --help

3.2 命令设计

3.2.1 查询验证码的 q 命令

# 用手机号查
$ joymax q 7384311234

# 用别名查
$ joymax q moyu

3.2.2 添加别名 add 命令

  • 参数如下:
    • 2.1 -n,--name 电话号码别名
    • 2.2 -p,--phone 电话号码
$ joymax add -n moyu -p 7384311234

3.2.3 修改配置命令 ccfg 命令

  • 参数如下(这部分参数涉密了,我胆子小就瞎写几个了):
    • 3.1 -f, --fid, 平台 fid
    • 3.2 -k, --key, 平台 kid
    • 3.3 -a, --appid, 平台 appid
$ joymax ccfg -f 250
$ joymax ccfg -a f**k

3.2.4 查看配置 ls 命令

我们目前把所有的配置写到这个包的 cfg.json 中,这么做是有隐患的,这里先不说,看官老爷可以思考一下;

命令时通用的,能修改配置,配置多了谁知道现在是啥,所以就需要个 npm config ls 一样的一个命令,列出 cfg.json 的文件内容即可

$ joymax ls

3.3 解析命令和参数

这里我们使用开源的命令行支持工具 commander,这个包包是个好东西,他可以注册命令,解析命令行参数。

接着呢我们简单说一种生产环境常用的用法,这个简单粗暴,不过还是是推荐你去看官方文档。

3.3.1 命令组织

首先我们的上面的命令一个命令对应一个文件,其中主命令为 joymax,子命令 joymax q, joymax add, joymax ccfg, joymax ls

主命令的名字将作为子命令的文件前缀,所以最终的命令一共有以下几个文件:

  1. 对应 joymax 命令的 joymax.js,这个相当于是个入口,其余的子命令都在这里注册;
  2. 对应 joymax addjoymax-add.js
  3. 对应 joymax q 的 `joymax-q.js;
  4. 对应 joymax ccfgjoymax-ccfg.js
  5. 对应 joymax lsjoymax-ls.js

当然,这些 js 文件都要是可执行文件,并且都是由 ts 编译而来,这个是个后话;

3.4 esbuild

esbuild 是个新兴的打包工具,api 形式的调用,支持 ts,相对简单;

3.4.1 安装 esbuild

$ npm install esbuild --save-dev 

3.4.2 esbuild 的示例配置如下

#!/usr/bin/env node
let fs = require('fs');

let child_process = require('child_process');

// 跑命令用的,这个方法来自 webpack,我超级喜欢这个方法
const runCommand = async (cmd, args, needReturn = false) => {
  return new Promise((resolve, reject) => {
    if (!needReturn) {
      let execCmd = child_process.spawn(cmd, args, {
        shell: true,
        stdio: 'inherit'
      });
      execCmd.on('error', reject);
      execCmd.on('exit', (code) => +code === 0 ? resolve(code) : reject(code))
    } else {
      child_process.exec(cmd, args, (err, stdo, stde) => {
        if (err && err.code !== 0) reject({ code: err.code, data: null })
        else resolve({ code: 0, data: stdo })
      })
    }
  })
};

let esbuild = require('esbuild');

// 入口文件名
let entries = [
  'joymax',
  'joymax-q',
  'joymax-add',
  'joymax-ccfg',
  'joymax-ls'
];

let p = (item) => `./src/${item}.ts`

esbuild.build({
  entryPoints: entries.map(p),
  platform: 'node',
  target: 'es2015',
  bundle: true,
  banner: {
    js: '#!/usr/bin/env node' // 给每个输出文件增加脚本解析头,告诉系统该脚本由 node 解析执行
  },
  outdir: 'bin'
  // minify: true, // 压缩
  // sourcemap: true,
}).then(() => {
  // 打包结束会执行这个回调,在这里我们对所有输出 js 文件进行 +x 可执行权限的授权
  entries.forEach(async (item) => {
    await runCommand(`chmod +x ./bin/${item}.js`);
    console.log(`chmod + x ${item}.js done!`)
  });
  console.log('打包完成!')
}).catch(() => process.exit(1));

3.5 复制到剪贴板

pbcopy 命令可以复制所有写入到 std.in 中的内容

这里我就偷懒了,我搜了一下可以跨平台的剪贴板库,但是多少有点复杂,所以我选择放弃😂。所以我这里用了 MacOS 原生操作剪贴板的命令 pbcopy;简单粗暴,一行命令就搞定了。

但是,但是这里这个做法就不支持跨平台了,只能给 MacOS用;当然为了降低损失,我会把最后的验证码输出到命令行,给用户自己复制;

$ echo 010021 | pbcopy

四、总结

本篇小作文给大家描述了一个背景,分析了需求,最后给大家准备了实现这些功能的技术点:

  1. package.jsonbin 字段与去全局安装;
  2. 命令设计和 commander 组织命令并且解析命令参数;
  3. esbuild 的打包脚本;
  4. 复制到剪贴板的实现;

本篇为姊妹篇,每个命令实现请看下一篇:# 不想写需求?摸鱼也要写 CLI 命令!(2)

最后恭祝各位看官老爷新年发财,再次预祝点赞的老爷成为尊贵的奥迪车主;