随着开发时间的增长,你积累的模版需要管理,不能老是复制粘贴。那么一个小小的cli 可以帮助你这个问题。它是你进行管理分类的管家,替你管理仓库和翻东西。
技术选型
- NodeJS
- TS
- pnpm
- unbuild : unbuild 是基于rollup 配置更加单的打包工具
- chalk : 输出颜色
- commander:命令管理
- inquirer:询问
- figlet:输出数字化字体
- standard-version: 版本管理
因为我是前端 node对于我来说比较友好,node 环境电脑一般都有,写这种cli js其实是比较好的选择,灵活高效。但是我还是想用TS 🐶。
思路
开发cli 的目的主要是为了管理模版 ,所以我们需要自定义输入模版,这就用到了本地存储,存储选用文件存储。
- 命名行输入创建命令
- 查询自己的模版
- 选择模版
- 利用git 命令clone
- node更改模版需要更改的名称和内容
- 完成
开发前准备工作
步骤如下
- 创建项目
- 设置入口,并已验证
- 添加依赖
创建项目
初始化项目package.json
设置入口
link 到全局验证测试
添加依赖
添加生产依赖
pnpm add chalk commander figlet inquirer
TS 配置
安装ts 和 类型等开发依赖:
"devDependencies": {
"@types/figlet": "^1.5.8",
"@types/inquirer": "^9.0.7",
"@types/node": "^20.9.5",
"typescript": "^5.3.2"
},
tsconfig 配置:
{
"include": ["src"],
"compilerOptions": {
"outDir": "dist",
"target": "ES2022",
"module": "ES2020",
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true,
"declaration": false,
"sourceMap": false,
"noUnusedLocals": true,
"esModuleInterop": true
}
}
打包工具
打包工具参考尤雨溪在vue-cli 使用的工具,unbuild 简单高效。相信尤大大没错。
安装依赖:
pnpm add unbuild -D
添加配置:
import { defineBuildConfig } from "unbuild";
export default defineBuildConfig({
entries: ["./src/index"],
clean: true,
rollup: {
inlineDependencies: true,
esbuild: {
target: 'node18',
minify: true,
},
}
});
package.json 添加脚本:
"scripts": {
"dev": "unbuild --stub",
"build": "unbuild"
},
功能开发
目的是快捷的创建自己的模版库,主要是增删改查,理想情况是有用户系统,远程维护。这么依赖比较麻烦,电脑换的还是比较少的,所以文件存储了。
创建新应用
// 创建命令
program
.command("create <project-name>")
.description("创建一个新应用")
.option("-f,--force", "强制覆盖已有项目")
.action(async (_projectName, cmd) => {
// 如果应用名称不规范,则提示用户输入
if (!isValidPackageName(_projectName)) {
console.log(chalk.red("应用名称不规范"));
return;
}
appName = _projectName;
// 如果强制覆盖且已存在相同命名工程,则提示用户输入
if (!cmd.force && existsSync(resolve(`./${appName}`))) {
console.log(chalk.red("已存在相同命名工程"));
return;
} else if (cmd.force && existsSync(resolve(`./${appName}`))) {
//删除当前文件夹
rmdirSync(resolve(`./${appName}`), { recursive: true });
}
// 获取默认模板列表
const templateList = await getTemplateList();
// 获取项目名称
const topTemplateList = templateList?.map((item) => item.name);
// 创建问题
const question = [
{
type: "list",
message: "请选择开发的应用类型:",
name: "appType",
default: "vue",
choices: topTemplateList,
},
];
inquirer.prompt(question).then((answer) => {
// 根据用户选择的模板,获取子模板列表
const template = templateList?.find(
(item) => item.name === answer.appType
);
if (!template) {
return;
}
const choices = template.children?.map((item) => item.name);
const question = [
{
type: template.type,
name: "appSubType",
message: template.message,
choices,
},
];
inquirer.prompt(question).then((answer) => {
// 根据用户选择的子模板,获取客户端列表
const subTemplate = template.children?.find(
(item) => item.name === answer.appSubType
);
if (!subTemplate || !subTemplate.git || !subTemplate.client) {
return;
}
// 使用子模板的git仓库,创建应用
cloneProject(subTemplate.git, appName, subTemplate.client);
});
});
});
自定义模版新增
program
.command("add <name> <gitUrl>")
.description("新增自定义模版")
.action(async (_name: string, _gitUrl: string) => {
// 获取模板列表
const tempList = await getTemplateList();
// 获取自定义组
const customGroup = tempList.pop();
// 查找自定义组中是否已经存在同名模板
const customTemplate = customGroup?.children?.find(
(item) => item.name === _name
);
if (customTemplate) {
// 如果存在,则返回错误信息
return console.log(chalk.redBright("名称已存在"));
}
// 向自定义组中添加模板
customGroup?.children?.push({
code: `${(customGroup?.children?.length ?? 0) + 1}`,
name: _name,
git: _gitUrl,
client: "pnpm",
});
if (customGroup) {
tempList.push(customGroup);
}
console.dir(tempList, { depth: null });
writeFile("repoList.txt", JSON.stringify(tempList), (err) => {
if (err) {
console.log(chalk.redBright("新增失败"), err);
return;
}
console.log(chalk.greenBright("新增成功"));
});
});
自定义模版查询
// 查看所有模版树形列表
program
.command("ls")
.description("查看所有模版树形列表")
.action(async () => {
console.log(figlet.textSync("PAN CLI"));
printTree(await getTemplateList());
});
// 查看所有模版树形列表值和repo 地址
program
.command("ll")
.description("查看所有模版对象结构")
.action(async () => {
printTree(await getTemplateList(), 0, true);
});
自定义模版删除
program
.command("delete <name>")
.description("删除自定义模版")
.action(async (_name: string) => {
// 获取模板列表
const tempList = await getTemplateList();
// 删除输入名称的模版
const afterDelTempList = tempList[tempList.length - 1]?.children?.filter(
(item) => item.name !== _name
);
tempList[tempList.length - 1].children = afterDelTempList;
writeFile("repoList.txt", JSON.stringify(tempList), (err) => {
if (err) {
console.log(chalk.redBright("删除失败"), err);
return;
}
console.log(chalk.greenBright("删除成功"));
});
});
自定义模版更新
program
.command("update <name> <gitUrl>")
.description("更新自定义模版")
.action(async (_name: string, _gitUrl: string) => {
// 获取模板列表
const tempList = await getTemplateList();
// 获取自定义组
const customGroup = tempList.pop();
// 查找自定义组中是否已经存在同名模板
const customTemplate = customGroup?.children?.find(
(item) => item.name === _name
);
if (customTemplate) {
customTemplate.git = _gitUrl;
}else{
console.log(chalk.redBright("模板不存在"));
return
}
if (customGroup) {
tempList.push(customGroup);
}
writeFile("repoList.txt", JSON.stringify(tempList), (err) => {
if (err) {
console.log(chalk.redBright("更新失败"), err);
return;
}
console.log(chalk.greenBright("更新成功"));
});
});
发布
使用standard-version
发布版本以及管理CHANGELOG
- 安装依赖:
pnpm add standard-version -D
- 添加命令
"scripts": {
"dev": "unbuild --stub",
"build": "unbuild",
"release": "standard-version && npm publish"
},
使用
全局安装使用,npm install -g @x-fe/cli
具体功能查看-h
功能即可