前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手教学拥有自己的CLI

手把手教学拥有自己的CLI

作者头像
星宇大前端
发布2023-11-28 11:01:47
2690
发布2023-11-28 11:01:47
举报
文章被收录于专栏:大宇笔记大宇笔记

随着开发时间的增长,你积累的模版需要管理,不能老是复制粘贴。那么一个小小的cli 可以帮助你这个问题。它是你进行管理分类的管家,替你管理仓库和翻东西。

技术选型

  • NodeJS
  • TS
  • pnpm
  • unbuild : unbuild 是基于rollup 配置更加单的打包工具
  • chalk : 输出颜色
  • commander:命令管理
  • inquirer:询问
  • figlet:输出数字化字体
  • standard-version: 版本管理

因为我是前端 node对于我来说比较友好,node 环境电脑一般都有,写这种cli js其实是比较好的选择,灵活高效。但是我还是想用TS 🐶。

思路

开发cli 的目的主要是为了管理模版 ,所以我们需要自定义输入模版,这就用到了本地存储,存储选用文件存储。

  1. 命名行输入创建命令
  2. 查询自己的模版
  3. 选择模版
  4. 利用git 命令clone
  5. node更改模版需要更改的名称和内容
  6. 完成

开发前准备工作

步骤如下

  1. 创建项目
  2. 设置入口,并已验证
  3. 添加依赖
创建项目
初始化项目package.json
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
设置入口
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
link 到全局验证测试
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
添加依赖

添加生产依赖

pnpm add chalk commander figlet inquirer

在这里插入图片描述
在这里插入图片描述
TS 配置

安装ts 和 类型等开发依赖:

代码语言:javascript
复制
  "devDependencies": {
    "@types/figlet": "^1.5.8",
    "@types/inquirer": "^9.0.7",
    "@types/node": "^20.9.5",
    "typescript": "^5.3.2"
  },
在这里插入图片描述
在这里插入图片描述

tsconfig 配置:

代码语言:javascript
复制
{
  "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 简单高效。相信尤大大没错。

安装依赖:

代码语言:javascript
复制
pnpm add unbuild -D

添加配置:

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
import { defineBuildConfig } from "unbuild";

export default defineBuildConfig({
  entries: ["./src/index"],
  clean: true,
  rollup: {
    inlineDependencies: true,
    esbuild: {
      target: 'node18',
      minify: true,
    },
  }
});

package.json 添加脚本:

代码语言:javascript
复制
  "scripts": {
    "dev": "unbuild --stub",
    "build": "unbuild"
  },

功能开发

目的是快捷的创建自己的模版库,主要是增删改查,理想情况是有用户系统,远程维护。这么依赖比较麻烦,电脑换的还是比较少的,所以文件存储了。

创建新应用
代码语言:javascript
复制
// 创建命令
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);
      });
    });
  });
自定义模版新增
代码语言:javascript
复制
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("新增成功"));
    });
  });
自定义模版查询
代码语言:javascript
复制
// 查看所有模版树形列表
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);
  });
自定义模版删除
代码语言:javascript
复制
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("删除成功"));
    });
  });
自定义模版更新
代码语言:javascript
复制
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
  • 添加命令
代码语言:javascript
复制
  "scripts": {
    "dev": "unbuild --stub",
    "build": "unbuild",
    "release": "standard-version && npm publish"
  },

使用

全局安装使用,npm install -g @x-fe/cli

具体功能查看-h功能即可

在这里插入图片描述
在这里插入图片描述
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-11-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 技术选型
  • 思路
  • 开发前准备工作
    • 创建项目
      • 初始化项目package.json
        • 设置入口
        • link 到全局验证测试
      • 添加依赖
        • TS 配置
          • 打包工具
          • 功能开发
            • 创建新应用
              • 自定义模版新增
                • 自定义模版查询
                  • 自定义模版删除
                    • 自定义模版更新
                    • 发布
                    • 使用
                    相关产品与服务
                    文件存储
                    文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档