具体完整代码可以去查看我的github项目,mw-cli - 一个Nodejs脚手架工具

战斗准备

建立一个项目文件 打开命令窗口 我们所使用的一切为pnpm

npm install pnpm -g

# 初始化项目指令
pnpm init

# 安装我们所需要会用到的依赖 作用去查去查阅下吧!
pnpm add typescript  // Typescript 支持 💕 个人习惯
pnpm add commander  
pnpm add copy-dir
pnpm add cross-spawn
pnpm add fs
pnpm add kolorist
pnpm add minimist
pnpm add ora
pnpm add ts-node

# 生成ts配置文件 tsconfig.json
tsc --init

启动文件

创建bin目录作为启动路径,创建index.ts文件,注意 #!/usr/bin/env 是一个常见的约定,用于告诉操作系统在运行脚本时使用指定的解释器。在这种情况下,/usr/bin/env 是一个可执行文件,它会在环境变量中查找指定的解释器(在这里是 node),并使用它来执行脚本。

# /bin/index.ts

#!/usr/bin/env node

// 引用cross-spawn
const spawn = require("cross-spawn");

// 调用pkgjson
const pkg = require("../../package.json");

// 引用commander
const program = require("commander");

// 引用创建项目逻辑
const createProject = require("../src/core/create");

//版本号 -v --version 选项
program.version(pkg.version, '-v,--version')

// 创建项目
program.command("create <projectName>").description("创建项目").action((projectName: string) => {
    createProject(projectName);
});

program.parse(process.argv);

创建项目配置文件

建立src目录,创建config目录和core目录,config 文件夹我们用于存放配置文件,core 文件夹用于存放核心逻辑文件。

config 文件夹内建立 repo.config.ts , 用于存放我们项目模板地址,原本有考虑是用github的项目地址的,但是个人认为并不适合。

# /src/config/repo.config.ts

const path = require("path");

// 模板地址
const repoUrl =  path.resolve(__dirname, "../../template");

module.exports = repoUrl;

并且创建 template 目录,存放我们项目模板

切图1

创建模板逻辑

# /src/core/create.ts 

// 读取vue模版
const vueCommand = require("./vueCreate");
// 读取node模版
const nodeCommand = require("./nodeCreate");

const createCommand = async (projectName = "") => {
  const {
    blue,
    cyan,
    green,
    lightBlue,
    lightGreen,
    lightRed,
  } = require("kolorist");

  const prompts = require("prompts");

  const questions = [
    {
      type: "text",
      name: "projectName",
      message: "请输入你需要创建的项目名称",
      initial: projectName,
    },
    {
      type: "text",
      name: "projectVersion",
      message: "请输入你需要创建的项目版本号",
      initial: "1.0.0",
    },
    {
      type: "select",
      name: "projectTemplate",
      message: "请选择你需要创建的项目模板",
      choices: [
        {
          title: "Vue",
          value: "vue",
          description: blue("vue类型的一些项目模版"),
        },
        {
          title: "React",
          value: "react",
          description: blue("react类型的一些项目模版"),
        },
        {
          title: "Node",
          value: "node",
          description: blue("node类型的一些项目模,例如配置好mysql或者orm框架"),
        },
      ],
    },
  ];

  (async () => {
    const response = await prompts(questions);
    const { projectName, projectVersion, projectTemplate } = response;
    if (projectTemplate === "vue") {
      vueCommand(response);
    } else if (projectTemplate === "node") {
      nodeCommand(response);
    } else {
      console.log(lightRed("暂时只支持vue模版"));
    }
  })();
};

module.exports = createCommand;

创建vue模板逻辑

# /src/core/vueCreate.ts 

interface Create {
  projectName: string; // 项目名称
  projectVersion: string; // 项目版本号
  projectTemplate: string; // 项目模版
}

// 读取vue模版
const vueCreate = async (create: Create) => {
  const prompts = require("prompts");
  const config = require("../config/repo.config");
  const {
    blue,
    cyan,
    green,
    lightBlue,
    lightGreen,
    lightRed,
  } = require("kolorist");

  const questions = [
    {
      type: "select",
      name: "vueTemplate",
      message: "请选择你需要创建的项目模板",
      choices: [
        {
          title: "template-vue3-ts-vite",
          value: "template-vue3-ts-vite",
          description: green("vue3 + ts + vite 项目模版"),
        },
        {
          title: "template-vue3-webpack-ts",
          value: "template-vue3-webpack-ts",
          description: green("vue3 + ts + webpack 项目模版"),
        },
      ],
    },
  ];

  (async () => {
    const response = await prompts(questions);
    const { vueTemplate } = response;
    // 走copy-dir 不走github了 没意义
    const copydir = require("copy-dir");
    // 进度
    const ora = require("ora");
    const spinner = ora(blue("下载模版中..."));

    copydir.sync(
      `${config}/${vueTemplate}`,
      `./${create.projectName}`,
      {
        utimes: true, // keep add time and modify time
        mode: true, // keep file mode
        cover: true, // cover file when exists, default is true
        filter: function (stat: string, filepath: any, filename: string) {
          return true; // remind to return a true value when file check passed.
        },
      },
      function (err: Error) {
        if (err) throw err;
        spinner.fail(lightRed(`项目模版创建失败`));
      }
    );

    spinner.succeed(lightGreen("项目模版创建成功"));
  })();
};

module.exports = vueCreate;

从这里整体逻辑就已经编写完了 但是我们还需要在package.json中配置一下,并且修改下tsconfig.json

# tsconfig.json
# ...
 "outDir": "lib"
# ...

修改输出目录为lib , 这样我们使用tsc命令编译后的文件就会输出到lib目录下

{
  "name": "mw-create",
  "version": "1.0.14",
  "description": "",
  "main": "./lib/bin/index.js",
  "bin": {
    "mwcli": "./lib/bin/index.js"
  },
  "scripts": {
    "test": "ts-node ./bin/index",
    "build": "node ./bin/copyDir.ts && tsc",
    "minor": "npm version minor",
    "major": "npm version major",
    "patch": "npm version patch"
  },
  "publishConfig": {
    "access": "public"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/itmowang/mw-cli.git"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "@types/node": "^20.4.9"
  },
  "dependencies": {
    "commander": "^11.0.0",
    "copy-dir": "^1.3.0",
    "cross-spawn": "^7.0.3",
    "download-git-repo": "^3.0.2",
    "fs": "0.0.1-security",
    "kolorist": "^1.8.0",
    "minimist": "^1.2.8",
    "ora": "5.4.1",
    "prompts": "^2.4.2",
    "ts-node": "^10.9.1"
  }
}

这里我们需要注意的是 “main”: “./lib/bin/index.js”, main字段指定了程序的主入口文件,bin字段指定了程序的命令名,npm会在全局环境下建立一个软链接,指向我们的主入口文件,这样我们就可以在命令行中使用mwcli命令了。

测试一下


npm link

mwcli create test

切图1

一切大功告成

具体代码参考github仓库 https://github.com/itmowang/mw-cli