创建发布一个cli工具

1.初始化项目

npm init -y

2.创建目录bin,里面创建inde.js

#!/usr/bin/env node
// 上面的不是注释,非常重要,告诉命令使用node环境运行本文件
console.log("hellow cli...")

3.package.json中配置bin命令

...
"main": "index.js",
"bin": {
  "kun": "./bin/index.js"
},
...

4.执行命令,创建软链接,把bin目录的路径暴露出来

npm link

成功后,在命令行尝试执行

kun

控制台打印 hellow cli...

  1. 安装辅助的依赖 ,commander node.js命令行界面的完整解决方案
npm install commander -S

修改bin/index.js

#!/usr/bin/env node
// 上面的不是注释,非常重要,告诉命令使用node环境运行本文件
const program=require('commander');
program.version(require('./../package.json').version);
program.command("create <name>")

    .description("create a koa project")
    .action((name)=>{
        console.log("name: "+name)        
    })
// 这行代码是解析参数
program.parse(process.argv)

执行kun create hellow,打印name: hellow
现在支持 kun create <name>,kun -V,而且action中可以执行一下操作,并且能得到用户传入的参数。
6.安装 chalk.js 控制台粉笔工具,方便改变打印的颜色

npm install chalk -S

修改/bin/index.js,action中的function换成init导出的function

...
   .description("create a koa project")
   .action(require('./init'))
program.parse(process.argv)

根目录创建init.js ,主要功能就是创建一个文件夹,里面创建一个index.js和一个package.json。

const fs = require("fs");
const chalk = require("chalk");
const path = require("path");
const createLog = (type) => (content) => console.log(chalk[type](content));
const success = createLog("green");
const errorLog = createLog("red");
function getFilePath(name) {
  return `./${name}`;
}
// 删除目录
function delDir(path) {
  let files = [];

  if (fs.existsSync(path)) {
    files = fs.readdirSync(path);

    files.forEach((file) => {
      let curPath = path + "/" + file;

      if (fs.statSync(curPath).isDirectory()) {
        delDir(curPath); //递归删除文件夹
      } else {
        fs.unlinkSync(curPath); //删除文件
      }
    });

    fs.rmdirSync(path);
  }
}
// 这个name值是index.js中,`program.parse(argv)`解析的
module.exports = async (name) => {
  const basePath = getFilePath(name);
  if (fs.existsSync(basePath)) {
    errorLog("同名文件已经存在");
    return false;
  }
  //   创建目录
  fs.mkdirSync(basePath);
  try {
    fs.writeFileSync(`${basePath}/index.js`, "index");
    fs.writeFileSync(`${basePath}/package.json`,'package');
    success("创建成功");
  } catch (error) {
    //   删除目录及文件
    delDir(basePath);
    errorLog("创建失败!");
  }
};

7.有内味了。就是文件生成的确实low b点。安装ejs高效的嵌入式 JavaScript 模板引擎
首先要创建模板目录template。里面创建index.ejs和package.ejs

//index.ejs
const Koa = require('koa');

<% if (middleware.router) { %>    
const Router = require('koa-router');
<% } %>
<% if (middleware.static) { %>    
const serve = require("koa-static");
<% } %>
<% if (middleware.views) { %>    
const views = require("koa-views");
<% } %>
<% if (middleware.body) { %>    
const body = require('koa-body');
<% } %>

const app = new Koa();

<% if (middleware.static) { %>
app.use(serve(__dirname, '/static'));

<% } %>
<% if (middleware.views) { %>    
app.use(views(__dirname, '/views'), {
    extension: 'pug'
})
<% } %>

<% if (middleware.body) { %>    
app.use(body({
    multipart: true
}))
<% } %>

<% if (middleware.router) { %>    
const router = new Router();
router.get('/', ctx => {
    ctx.body = "hellow world"
})
app.use(router.routes());
<% } %>


app.listen(<%= port %>, () => {
    console.log("服务启动在localhost:<%= port %>")
})
//package.ejs
{
"name": "template",
"version": "1.0.0",
"description": "",
"main": "<%= packageName %>.js",
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": {
    "koa": "^2.13.1"
    <% if (middleware.body) { %>
        ,"koa-body": "^4.2.0"
        <% } %>
            <% if (middleware.router) { %>
                , "koa-router": "^10.0.0"
                <% } %>
                    <% if (middleware.static) { %>
                        ,"koa-static": "^5.0.0"
                        <% } %>
                            <% if (middleware.views) { %>
                                ,"koa-views": "^7.0.1"
                                <% } %>
                                    }
                                    }

创建目录createCode ,里面创建文件 inde.jscreateIndexPage.jscreatePackagePage.js

//index.js
const createIndexPage =require('./createIndexPage')
const createPackagePage =require('./createPackagePage')
module.exports.createIndexPage=createIndexPage
module.exports.createPackagePage=createPackagePage
//createIndexPage.js
const ejs = require("ejs");
const fs = require("fs");
const path = require("path");
module.exports = () => {
 const content = fs
   .readFileSync(path.join(__dirname, "../template/index.ejs"))
   .toString();
 const code = ejs.render(content, { middleware: {}, port: 3000 });
 return code;
};

// createPackagePage.js
const ejs = require("ejs");
const fs = require("fs");
const path=require('path');
module.exports = () => {
  const content = fs
    .readFileSync(path.join(__dirname, "./../template/package.ejs"))
    .toString();
  const code = ejs.render(content, { middleware: {}, packageName: 3000 });
  return code;
};

init.js稍微修改一下,改变文件写入的字符

const { createIndexPage, createPackagePage } = require("./createCode");

...
  try {
    fs.writeFileSync(`${basePath}/index.js`, createIndexPage());
    fs.writeFileSync(`${basePath}/package.json`,createPackagePage());
    success("创建成功");
  } catch (error) {
...

8.来点交互更加炫酷
安装 inquirer 一个用户与命令行交互的工具

npm install inquirer -S

创建目录question,里面创建文件index.jspackageName.jsport.jsmiddleware.js

//index.js 
const inquirer = require("inquirer");
function getPromptList(name) {
  return [
    // 具体交互内容
    require("./packageName.js")(name),
    require("./port.js")(),
    require("./middleware.js")(),
  ];
}
module.exports = (name) => {
  return inquirer.prompt(getPromptList(name));
};

// packageName.js
module.exports = (name) => ({
  type: "input",
  name: "packageName",
  message: "设置项目名称",
  validate(val) {
    if (!val) {
      return "请输入项目名称";
    } else {
      return true;
    }
  },
  default() {
    return name;
  },
});

//middleware.js
module.exports = (name) => ({
  type: "checkbox",
  name: "middleware",
  message: "请选择使用的中间件",
  choices: [
    {
      name: "KoaBody",
    },
    {
      name: "Koaviews",
    },
    {
      name: "KoaStatic",
    },
    {
      name: "KoaRouter",
    },
  ],
});

// port.js
module.exports = () => ({
  type: "input",
  name: "port",
  message: "设置服务端口号",
  default() {
    return 8000;
  },
  validate: function (val) {
    if (val > 3000 && val < 65535) {
      // 校验位数
      return true;
    }
    return "端口号范围应为:3000-65535";
  },
});

init.js中执行交互,并且将用户的选择传入到生成代码的函数



const question = require("./question");


...
  const data = await question();
  console.log(data);
  var config = {
    packageName: data.packageName,
    port: data.port,
    middleware: {
      body: data.middleware.indexOf("KoaBody") !== -1,
      views: data.middleware.indexOf("KoaViews") !== -1,
      static: data.middleware.indexOf("KoaStatic") !== -1,
      router: data.middleware.indexOf("KoaRouter") !== -1,
    },
  };
   //   创建目录
  fs.mkdirSync(basePath);
  try {
    fs.writeFileSync(`${basePath}/index.js`, createIndexPage(config));
    fs.writeFileSync(`${basePath}/package.json`, createPackagePage(config));
....

  1. 项目创建完成,贴心的帮用户安装依赖。就是用代码执行npm install。安装 execa将process_child pipe到主进程
npm install execa -S

init.js

const fs = require("fs");
const chalk = require("chalk");
const execa = require("execa");
const { createIndexPage, createPackagePage } = require("./createCode");
const question = require("./question");
const createLog = (type) => (content) => console.log(chalk[type](content));
const success = createLog("green");
const errorLog = createLog("red");
function getFilePath(name) {
  return `./${name}`;
}
// 删除目录
function delDir(path) {
  let files = [];
  if (fs.existsSync(path)) {
    files = fs.readdirSync(path);
    files.forEach((file) => {
      let curPath = path + "/" + file;
      if (fs.statSync(curPath).isDirectory()) {
        delDir(curPath); //递归删除文件夹
      } else {
        fs.unlinkSync(curPath); //删除文件
      }
    });
    fs.rmdirSync(path);
  }
}

module.exports = async (name) => {
  const basePath = getFilePath(name);
  if (fs.existsSync(basePath)) {
    errorLog("同名文件已经存在");
    return false;
  }
  const data = await question(name);
  var config = {
    packageName: data.packageName,
    port: data.port,
    middleware: {
      body: data.middleware.indexOf("KoaBody") !== -1,
      views: data.middleware.indexOf("KoaViews") !== -1,
      static: data.middleware.indexOf("KoaStatic") !== -1,
      router: data.middleware.indexOf("KoaRouter") !== -1,
    },
  };
  //   创建目录
  fs.mkdirSync(basePath);
  try {
    fs.writeFileSync(`${basePath}/index.js`, createIndexPage(config));
    fs.writeFileSync(`${basePath}/package.json`, createPackagePage(config));
    await execa("npm", ["install"], { cwd: `./${name}`, stdio: [2, 2, 2] });
    success(`
恭喜!!!小项目创建完毕
-----------------------------------------------
***********************************************


cd ./${name}

npm run serve


===============================================
`);
  } catch (error) {
    //   删除目录及文件
    delDir(basePath);
    errorLog("创建失败!");
  }
};

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容