【Cli大法】从0开始玩一个创建react-vite模板的cli工具

2024 年 11 月 17 日 星期日(已编辑)
14

【Cli大法】从0开始玩一个创建react-vite模板的cli工具

前排告示:这里只是记录一个demo,使用cli通过git clone 开源的react-vite模板一键导入到本地文件的方法

首先项目目录如下

目录

目录

初始化

npm init

packjson初始化

{
  "name": "fe-cli",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module",// 这里一定要加module 表明当前是支持ES6模块化ES Modules 的node项目
  "bin": {
    "fecli": "./dist/bin/cli.js"//这里使用fecli进行npm link的绑定
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "tsc",
    "dev": "tsc -w"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "chalk": "^5.3.0",
    "commander": "^12.1.0",
    "download-git-repo": "^3.0.2",
    "fs-extra": "^11.2.0",
    "inquirer": "^12.0.1",
    "ora": "^8.1.1"
  },
  "devDependencies": {
    "@types/fs-extra": "^11.0.4",
    "@types/node": "^22.9.0",
    "typescript": "^5.6.3"
  }
}

同时配置tsconfig.json

{
  "compilerOptions": {
    "target": "ES2018",
    "module": "ESNext",//当前是Esmodule
    "outDir": "./dist",
    "rootDir": "./",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node"
  },
  "include": ["bin/**/*", "lib/**/*","src/**/*","src/types/*.d.ts"],// 对这些文件夹下的文件进行ts类型处理
  "exclude": ["node_modules"]
}

创建用于创建cli的文件(bin/cli.ts)

#!/usr/bin/env node  这一行命令必须要加,要不然fecli执行的时候会出错
/*
 * @Date: 2024-11-11 20:23:12
 * @Description: 
 */
import { Command } from "commander";
import create from "../lib/create.js";

console.log('CLI 启动');
console.log('命令行参数:', process.argv);

const program = new Command();

program
  .version("1.0.0")
  .command("create <project-name>")
  .description("create a new project")
  .action(async (name) => {
    try {
      console.error('fdfdfd:');
      await create(name);
    } catch (error) {
      console.error('执行出错:', error);
    }
  });

console.log('已注册的命令:', program.commands.map(cmd => cmd.name()));

program.parse(process.argv);

创建用户交互cli界面(lib/create.ts)

#!/usr/bin/env node
/*
 * @Date: 2024-11-11 20:26:43
 * @Description:
 */
import fs from "fs-extra";
import * as path from "path";
import chalk from "chalk";
import inquirer from "inquirer";
import TemplateDownloader from '../download/download.js';

/**
 * @description: cli创建项目
 * @param {string} projectName
 * @return {*}
 */
const createCli = async (projectName: string) => {
  try {
    const answers = await inquirer.prompt([
      {
        type: "input",
        name: "projectName",
        message: "please input project name",
      },
      {
        type: "list",
        name: "framework",
        message: "please choose framework:",
        choices: ["React", "Vue"],
      },
      {
        type: "list",
        name: "buildTool",
        message: "please choose build tool:",
        choices: ["Vite", "Webpack"],
      },
      {
        type: "confirm",
        name: "useEslint",
        message: "use eslint?",
        default: true,
      },
    ]);

    // 创建项目目录
    const targetDir = path.join(process.cwd(), projectName);
    console.log(chalk.blue(`Creating project in ${targetDir}`));
    if (fs.existsSync(targetDir)) {
      const { action } = await inquirer.prompt([
        {
          type: "list",
          name: "action",
          message: "the directory already exists, please choose action:",
          choices: ["overwrite", "cancel"],
        },
      ]);
      console.log(chalk.blue(`${action} project...`));
    }
    // 根据用户选择生成项目
    console.log(chalk.blue("Creating project..."));
    const templatePath = await TemplateDownloader.downloadTemplate(answers.framework,
      answers.buildTool);
      console.log('create template path is: ',templatePath);
      
    return true;
  } catch (error) {
    console.error("发生错误:", error);
    throw error;
  }
};

export default createCli;

创建模板下载的界面(download/download.ts)

#!/usr/bin/env node

import ora from "ora";
import path from "path";
import chalk from "chalk";
import fs from "fs-extra";
import download from "download-git-repo";
/**
 * @description: 模板下载器
 * @return {*}
 */
class TemplateDownloader {
  private templateRegistry: Record<string, string>;
  private cacheDir: string;
  constructor() {
      // 后续会支持更多模板
    this.templateRegistry = {
      "react-vite": "knakamura13/react-vite-template#main",
    };
    // 下载后,模板暂存的位置
    this.cacheDir = path.resolve(process.cwd(), "cli-templates");
  }
  // 获取缓存路径
  getCachePath(templateKey: string) {
    return path.join(this.cacheDir, templateKey);
  }
  // 获取所选模板对应的key值
  getTemplateKey(framework: string, buildTool: string) {
    return `${framework.toLowerCase()}-${buildTool.toLowerCase()}`;
  }
  async downloadTemplate(framework: string, buildTool: string) {
    const templateKey = this.getTemplateKey(framework, buildTool);
    const templateUrl = this.templateRegistry[templateKey];
    const cachePath = this.getCachePath(templateKey);
    console.log("this template url is ", templateUrl);

    if (!templateUrl) {
      throw new Error(`Template ${templateKey} not found`);
    }
    const spinner = ora(`Downloading ${templateKey} template...`).start();
    try {
      await download(templateUrl, cachePath, (err?: Error) => {
        if (err) {
          spinner.fail();
          throw err;
        }
      });
      spinner.succeed("Template downloaded successfully");
      return cachePath;
    } catch (error) {
      spinner.fail("Template download failed");
      throw error;
    }
  }
}

export default new TemplateDownloader();

之后执行

tsc
npm link
fecli create <项目名>

之后模板代码就会放到cli-template这个文件下面

未完待续...

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...