pnpm monorepo+ts制作《CLI 命令行工具》
前言

在卷卷的今天,我们可以看到前端框架和技术层出不穷,各种技术都伴有着一系列的生态诞生,说白了就是都有后台,有静态文档技术,有运行时依赖,有各种各样的轮子,当然也少不了 cli 这一套
对于大部分人来说最开始接触这个东西的应该是 vue-cli ,当时确实感觉很不错,如今发现很多脚手架和命令行工具完全可以使用 js / ts 来开发,都依赖着 nodejs npm 来运行
来吧:接下来我们也实践一个功能简单且常用的工具
一、仓库搭建

1. 介绍功能
简单介绍下上面仓库这个 cli 工具的功能吧
参数 Options
-v, --vers: 查看版本号
-h, --help: 查看自定义帮助信息
命令 Commands
init: 初始化一个模版(包含文档和博客风格的 vitepress 主题模版),也就是远程仓库模版下载和本地文件修改
list: 展示模版仓库各个模版的最新提交信息,请求了 GitHub API
git: 展示某人远程仓库的列表,请求了 Github API
ver: 展示模版仓库分支的版本号,请求了 Github API
2.开始搭建
我们使用比较新的 pnpm 搭建 monorepo 工程
package.json
这里就正常写就可以,不过 scripts 脚本可以变一下,使用 pnpm 支持的更多的脚本
pnpm run -C packages/cli xxx
上述命令表示执行子模块中的 xxx 命令,这样就可以再根目录操作子模块脚本了
依赖说明:
fs-extra: fs 模块升级版
rimraf: 用来删除目录,忽略一些系统差异
{
"name": "@vuetom-cli/root",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"build": "pnpm run -C packages/cli tsc",
"cli": "pnpm run -C packages/cli cli",
"cli:init": "pnpm run -C packages/cli cli:init",
"cli:list": "pnpm run -C packages/cli cli:list",
"ts": "pnpm run -C packages/cli ts",
"ts:init": "pnpm run -C packages/cli ts:init",
"ts:list": "pnpm run -C packages/cli ts:list"
},
"dependencies": {
},
"devDependencies": {
"fs-extra": "^10.1.0",
"rimraf": "^3.0.2",
"ts-node": "^10.9.1",
"typescript": "^4.8.2"
}
} pnpm-workspace.yaml
这里声明包含的模块
packages: - 'packages/*'
packages 包
这里面我们再分 3 个目录,一个是 cli 主项目目录
再搞两个模版(temp-docs / temp-blog)目录,用来模拟项目模版生成的功能
子模块结构
cli/package.json
该文件下包含了cli自己模块本身要用到了依赖包以及命令,最主要的是要看编译命令:
假设你以后的命令是 vuetom-cli
cli: 主命令,执行 pnpm cli 就相当于执行了 vuetom-cli
cli:init: command,相当于执行了 vuetom-cli init
cli:list: command,相当于执行了 vuetom-cli list
ts: 开发模式下执行的,方便变开发变测试
copy: 移动模版文件或者其他一些不需要经过编译的文件
tsc: 编译命令,将 ts 文件编译为 js
{
"name": "vuetom-cli",
"version": "0.2.5",
"description": "A simple CLI for vitepress-theme-vuetom",
"scripts": {
"cli": "node ./bin/vuetom-cli.js",
"cli:init": "node ./bin/vuetom-cli.js init",
"cli:list": "node ./bin/vuetom-cli.js list",
"ts": "ts-node ./src/vuetom-cli.ts",
"ts:init": "ts-node ./src/vuetom-cli-init.ts",
"ts:list": "ts-node ./src/vuetom-cli-list.ts",
"copy": "node ../../scripts/index",
"tsc": "pnpm copy && pnpm clean && tsc",
"clean": "rimraf bin"
},
"bin": {
"vuetom-cli": "./bin/vuetom-cli.js"
},
"files": [
"bin/",
"media",
"temp-blog",
"temp-docs",
".env",
"CHANGELOG.md",
"README.md"
],
"dependencies": {
"chalk": "^4.1.2",
"commander": "^9.4.0",
"dotenv": "^16.0.1",
"dotenv-expand": "^9.0.0",
"download-git-repo": "^3.0.2",
"envfile": "^6.17.0",
"inquirer": "^8.0.0",
"ora": "^5.4.1",
"request": "^2.88.2"
}
} temp-docs 和 temp-blog
这两个目录下都各有一个 package.json 和 README.md ,就假装是两个不同的项目吧
两个 package.json 简单书写一下就行
{
"name": "temp-blog",
"version": "0.0.1",
"description": "blog templates",
"scripts": {
},
"author": "lauset",
"license": "MIT"
}
项目简单的搭建完成了,详细代码在 github 上
二、开始编码
1. 简单命令
先列举一些简单的命令,比如版本、帮助、命令、参数判断等
cli/src/vuetom-cli.ts 文件名可以随意起,跟自己的主命令一样也可以,ts 文件内可以使用 es import 语法,也可以使用 node require 语法,也可以直接获取到 json,可混搭
const chalk = require('chalk')
const pkg = require('../package')
// const program = require('commander')
import { Command } from 'commander'
const program = new Command() 然后我们就获得了 program,这个就很无敌了已经,chalk 是控制台彩色输出,pkg 是你的 cli/package.json 文件内容
展示主要信息、帮助信息
先看效果
在 vuetom-cli.ts 内加入代码
program
.name(pkg.name)
.description(chalk.hex('#FA8072')(pkg.description))
.version(pkg.version, '-v, --vers', chalk.gray('output the current version'))
program
.helpOption('-h, --help', chalk.gray('display help for you'))
.addHelpCommand(true, chalk.gray('display help for command'))
.on('--help', () => require('./cmd/help').default()) 这样 -v 和 -h 就可以使用了
require('./cmd/help').default() 是调用了一个文件内默认导出的方法,我们把命令都拆分到 cli/cmd 目录下,或者你替换成 console.log('help info') 也是可以的,只是展示的帮助信息多少的问题
cmd/help.ts
const chalk = require('chalk')
function printHelp () {
console.log()
console.log(chalk.hex('#66CDAA')(' Examples:'))
console.log()
console.log(
chalk.gray(' # create a new project with an official template')
)
console.log(' $ vuetom-cli init project-name')
console.log()
console.log(chalk.gray(' # show github branch information'))
console.log(' $ vuetom-cli list temp-docs')
console.log()
console.log(chalk.gray(' # show github repository'))
console.log(' $ vuetom-cli git vuejs')
console.log()
}
export default printHelp 2.多国语言切换
首先这里我们使用一个 -l (--lang) 参数来切换语言,大概就是 vuetom-cli -l zh 就可与切换成中文
在 vuetom-cli.ts 里加入
program
.option('-l, --lang [language]', chalk.gray('change language'))
.action((options) => {
const { lang } = options
require('./cmd/lang').default(lang)
}) cmd/lang.ts
const logger = require('../logger')
import { t, useLang } from '../lang'
const { langs, showLang, changeLang } = useLang()
function handleLang (lang: string) {
if (lang) {
if (typeof lang === 'boolean') {
showLang()
return
}
if (langs.includes(lang)) {
changeLang(lang)
} else {
logger.error(
`${t('error.lang')} ${langs} \n`
)
}
}
}
export default handleLang 这里使用到了我们自己写的 useLang,lang 目录下代码较多,请跳转 代码
我们单输入 -l 那么就会调用 showLang 展示当前语言的方法
代码中所有的 t 函数效果就和 vue-i18n 中的 t 差不多一样
上面还使用到了 .env 文件,这个文件里存入变量,我们改语言后可以直接修改文本内容以达到永久修改的目的
cli/.env
THEME_VERSION=2.2.0 VUETOM_CLI_LANG=zh
3.请求api数据
这里我们做个展示某人远程仓库项目的功能,这样就用到了 api 的请求和响应数据的展示
也就是自定义 git 命令,后加用户名或者组织名,然后看你 github/gitee 仓库,下面展示一下 vuetom 这个组织内的仓库列表
看下效果
首先改 cli/vuetom-cli.ts 吧
program
.command('git')
.description(chalk.gray('show the list of github/gitee repository of a user'))
.argument('<user>', "your github/gitee 's username")
.argument('[type]', 'repo type: "github"&nbs***bsp;"gitee"', undefined)
.action((user, type) => {
require('./cmd/git').default(user, type)
}) 这样你 help git 命令就自动会生成一些帮助信息了,argument 里 <user> 表示必填,[type] 表示可选,默认的话 undefined,action 里就是要调用的方法
首先我们可以自定义参数校验
cmd/git.ts
function handleGit (user: string, type: string) {
if (type === undefined || type === 'github') {
type = `${user}'s github`
githubList(user ?? 'lauset', type)
} else if (type === 'gitee') {
type = `${user}'s gitee `
giteeList(user ?? 'lauset', type)
} else {
logger.error(
`"${type}" ${t('error.gitType')} \n`
)
}
} export default handleGit
这样默认第二个参数就是 github 了,也就是说要展示 github 的仓库列表,完整代码
4.生成模板操作
这个功能比较常见吧,就是很多工具里都有的 init 或 create 模版,其实模版可以从远程仓库拉取也可以内置本地工具里,这样速度就很快
当然我们也会以提问的方式友好地去让用户输入或者选择来完成个性化,包括输入内容后我们可以将用户输入的内容替换掉拉取的项目下 package.json 里的内容
看下效果
由于这个不是一个简单的命令,所以我们采用其他方式调用,单独写一个 vuetom-cli-init.ts 文件
pnpm ts:init 就可以直接来调用执行
在 vuetom-cli.ts 里加入以下内容
program
.usage('<command> [options]')
// .option('-i, init [name]', 'init vitepress-theme-vuetom theme')
.command(
'init <project-name>',
chalk.gray('generate a new project from a template')
) 代码实在是太多了,直接看 vuetom-cli-init 这个文件吧,大家有不懂的地方直接评论或者 issues 里发起提问就行
三、打包发布
首先打包来说,我们使用 tsc 将其输出到 cli/bin 目录下,之后发布就直接上传 bin 目录了,你也可以加 README.md 文件等等
当你自己的 cli 做好以后,就可以发布至 npmjs 远程仓库,在你注册账号后,可以在个人电脑终端使用 npm login 登录,然后对包进行 npm publish 即可
需要注意上传的文件,在 package.json 里需要注意 files 属性
"files": [ "bin/", "media", "temp-blog", "temp-docs", ".env", "CHANGELOG.md", "README.md" ],
推荐阅读
🎈🎈🎈 小码哥公众号:码出宇宙,纯技术干货分享,为圆梦大厂助力。( 最近更新IDEA的全版本激活码,再也不用担心IDEA编不码而烦恼而烦恼)
