【nodejs】脚手架从零开始搭建JBD

news2024/10/6 16:04:16

🛎️脚手架编写


脚手架框架

  • bin

    • www.js
  • src

    • contant.js

    • create.js

    • main.js

  • package-lock.json

  • package.json

在这里插入图片描述


🛠️插件安装

devDependencies & dependencies

脚本名称脚本作用
commander读取版本,设定选项(option),开发命令行工具
consolidateexpress中的模板引擎可以覆盖其他模板
download-git-repo可以通过git的方式下载模板到本地
ejs模板库,json生成html和consolidate配合使用
inquirer命令行交互
metalsmith批量处理模板
ora优化加载等待的交互
chalk美化终端
ncp判断文件是否存在
axioshttp库发送请求

在这里插入图片描述

npm i

编写bin文件

bin文件下创建 www.js 文件

输入

#! /usr/bin/env node

require("../src/main.js")
  • 回到 package.json 添加如下启动项
  "bin": {
    "xiu": "./bin/www.js"
  },

⭐编写src文件


🚀main.js

版本命令行生成以及文件路径选择都在这里编写

const {version} = require('../package.json');
const path = require('path')
const program  = require('commander');

编写文件创建和帮助指令

const mapActions = {
    create: { 
        alias: "c",
        description:"create a project",
        examples:["xiu create <project-name>"]
    },
    config: { 
        alias:"conf",
        description:"config project variable",
        examples:["xiu config set<k><v>","xiu config get <k>"]
    },
    "*": { 
        alias:"",
        description:"command not found",
        examples:[]
    }
};
  • 完成上一步操作后,我们需要逐步进行命令选择

  • 需要使用 Reflect 中的 ownkeys进行每条命令的遍历

  • 最后使用 forEach进行循环操作

Reflect.ownKeys(mapActions).forEach((action) => {
    program
        .command(action)
        .alias(mapActions[action].alias)
        .description(mapActions[action].description)
        .action(() => {
        if(action === "*") 
            console.log(mapActions[action].description) 
        else{
            require(path.resolve(__dirname,action))(...process.argv.slice(3));
        }
    });
});

command :命令行(对应mapActions中的每一个动作)

alias :别称,也就是mapActions中的alias

description:同样对应mapActions中的description

因为这边是对mapActions进行循环遍历,所以每一个在mapActions中的属性都需要遍历到

  • 最后一行的判断

help事件

program.on("--help",() => {
    console.log("\nExamples:");
    Reflect.ownKeys(mapActions).forEach((action)=> {
        mapActions[action].examples.forEach((example)=> {
            console.log(example)
        });
    });
});
  • 执行help的时候输出mapActions和命令行自带的option

  • 在这里插入图片描述

版本显示

program.version(version).parse(process.argv);

main代码

console.log("welcome xiu");

const {version} = require('../package.json');
const path = require('path')
const program  = require('commander');
const mapActions = {
    create: { 
        alias: "c",
        description:"create a project",
        examples:["xiu create <project-name>"]
    },
    config: { 
        alias:"conf",
        description:"config project variable",
        examples:["xiu config set<k><v>","xiu config get <k>"]
    },
    "*": { 
        alias:"",
        description:"command not found",
        examples:[]
    }
};
Reflect.ownKeys(mapActions).forEach((action) => {
    program
        .command(action)
        .alias(mapActions[action].alias)
        .description(mapActions[action].description)
        .action(() => {
        if(action === "*") 
            console.log(mapActions[action].description) 
        else{
            require(path.resolve(__dirname,action))(...process.argv.slice(3));
        }
    });
});
// help event
program.on("--help",() => {
    console.log("\nExamples:");
    Reflect.ownKeys(mapActions).forEach((action)=> {
        mapActions[action].examples.forEach((example)=> {
            console.log(example)
        });
    });
});
program.version(version).parse(process.argv);

💡create.js

如果纯复刻,建议先去下面把 constant.js写了再回来

模板的选择模板的复制终端的选择终端的样式都在这里实现

const axios = require('axios')
const ora = require('ora')
const Inquirer = require('inquirer')
const path = require('path')
// 包装 
const {promisify} = require('util')
let downLoadGitRepo = require('download-git-repo')
downLoadGitRepo = promisify(downLoadGitRepo) // 装成ES6
// 复制
let ncp = require('ncp')
ncp = promisify(ncp)
// 复杂选择
const fs = require('fs')
const metalSmith = require('metalsmith')
let {render} = require('consolidate').ejs
render = promisify(render)
const {downloadDirectory} = require('./constant')
const download = require('download-git-repo')
// 美化终端
const chalk = require('chalk')

获取仓库信息

// 获取仓库信息
const fetchRepoList = async() => {
    const {data} = await axios.get("/*请求地址*/ https://api/orgs/repos")
    return data
}

抓取版本列表

// 抓取版本(tag)列表
const fetchTagList = async(repo) => {
    const {data} = await axios.get("/*请求地址*/" `https://api/orgs/${repo}/tags`)
    return data
}

下载项目

const downLoad = async(repo, tag) => {
    let api = `xiu/${repo}`
    if(tag) {
        api += `#${tag}`
    }
    const tempdest = `${downloadDirectory}/${repo}`
    await downLoadGitRepo(api,tempdest)
    return tempdest
}

repo 就是仓库下的模板名称

tag 就是每个模板的版本号

编写加载项

再完成上面几步之前会有一个加载的过程,重复的加载我们可以封装来完成

const waitFnLoading = (fn,message) => async(...args) => {
    // loading 加载
    const spinner = ora(message)
    spinner.start()
    let repos = await fn(...args)
    spinner.succeed();
    return repos
}

这里有两个函数体变量

(fn,message) , async(…args)

前者用来接收执行的函数和发出的提示

后者用来对执行函数自带参数进行调用

  • 接下来要做的就是导出下载模块

导出下载模块

交互选择

    let repos = await waitFnLoading(fetchRepoList,'fetch template...')()
    // 交互选择
    repos = repos.map(item => item.name)
    const {repo} = await Inquirer.prompt({
        name: 'repo',
        type: 'list',
        message: 'please choise a template',
        choices : repos, // 选择列表
    });

获取对应的版本号

let tags = await waitFnLoading(fetchTagList,'fetch template tag...')(repo)
    tags = tags.map((item) => item.name)
    const {tag} = await Inquirer.prompt({
        name: 'repo',
        type: 'list',
        message: 'please choise a tags for template',
        choices : tags, // 选择列表
    });

交互选择 和 获取对应的版本号 本质上没有差别

都是通过 inquirer 交互页面查看每个版本进行选择

不同的地方是:

异步调用的 加载项不同:

let repos = await waitFnLoading(fetchRepoList,'fetch template...')<mark>()</mark>

let tags = await waitFnLoading(fetchTagList,'fetch template tag...')<mark>(repo)</mark>
  • 下载的项目首先是临时存放到本地,之后再对存放的内容的文件名称和当前路径下的文件匹配有无重复最后实现模板复制到当前路径下。

  • 在一些简单模板下没有ask.js 但是在绝大部分的复杂模板下有 ask.js文件,这就需要我们对ask.js文件进行访问和重编写

这里需要使用到 metalSmith 对模板的内容进行批量处理

 // 下载项目 返回临时的存放目录
    const result = await waitFnLoading(downLoad,'downloading...')(repo,tag)
    if(!fs.existsSync(path.join(result,'ask.js'))) {
        await ncp(result,path.resolve(proname))  
    } else {
        // 复杂模板需要选择
        await new Promise((resolve,reject) => {
            metalSmith(__dirname)
                .source(result)
                .destination(path.resolve(proname))
                .use(async(files,metal,done) => {
                    // files 现在就是所有的文件
                    const args = require(path.join(result,'ask.js'))
                    // 选择
                    const obj = await Inquirer.prompt(args)
                    const meta = metal.metadata()
                    Object.assign(meta,obj)
                    delete files["ask.js"]
                    done()
                })
                .use((files,metal,done)=>{
                    const obj = metal.metadata()
                    Reflect.ownKeys(files).forEach(async(file)=>{
                        if(file.includes("js")|| file.includes("json")) {
                            let content = files[file].contents.toString()
                            if(content.includes("<%")) {
                                content = await render(content, obj)
                                files[file].contents = Buffer.from(content) // 渲染
                            }
                        }
                    })
                    done()
                }).build(err => {
                    if(err) {
                        reject()
                    }else {
                        resolve()
                    }
                })
        })
    }
  • done() 相当于 node.js的中间件 next()
const result = await waitFnLoading(downLoad,'downloading...')(repo,tag)
  • 这一行代码获取到的result 就是对应模板和版本号之后的结果
if(!fs.existsSync(path.join(result,'ask.js'))) {
        await ncp(result,path.resolve(proname))  
    } else {}
  • 这里的 if…else… 是对文件中是否存在 ask.js 进行判断

  • 有就是复杂模板 需要重编写

            metalSmith(__dirname)
                .source(result)
                .destination(path.resolve(proname))
                .use(async(files,metal,done) => {
                    // files 现在就是所有的文件
                    const args = require(path.join(result,'ask.js'))
                    // 选择
                    const obj = await Inquirer.prompt(args)
                    const meta = metal.metadata()
                    Object.assign(meta,obj)
                    delete files["ask.js"]
                    done()
                })
  • 这一处代码就是对模板下的所有文件匹配找到ask.js然后遍历执行里面所有的问题最后删除 delete files[“ask.js”]
.use((files,metal,done)=>{
                    const obj = metal.metadata()
                    Reflect.ownKeys(files).forEach(async(file)=>{
                        if(file.includes("js")|| file.includes("json")) {
                            let content = files[file].contents.toString()
                            if(content.includes("<%")) {
                                content = await render(content, obj)
                                files[file].contents = Buffer.from(content) // 渲染
                            }
                        }
                    })
                    done()
                })
  • 使用 metalSmith下的use 对刚刚遍历执行的问题中找到以‘<%’开头的选项就是要用户选择或是填写的

  • 这里说明一下,前面删除的 ask.js 为什么这里还可以访问?

  • 原因在于同样都是中间件,中间件之间是可以互相访问变量内容的,所以删除的ask.js在use里同样算是变量未删除。

成功退出

没什么好说的,直接复制改改就好了

    console.log(`
${chalk.green('thanks to use my CLI')}
${chalk.white.bold.bgBlue('success download')}
----------------------------------
⭕ ${chalk.red('version of this')}${chalk.white(repo,tag)}${chalk.white('buy me a coofee')}
design by ${chalk.bgBlue.yellow('wuchanghua')}™️
----------------------------------
${chalk.bgWhite.blue('如果你足够充满智慧,加入我,和我一起创造 QQ:1453346832 Email: 1453346832@qq.com')}
${chalk.green('Finish to 100%')}
${chalk.green('Welcome')}
`)

在这里插入图片描述

create代码

const axios = require('axios')
const ora = require('ora')
const Inquirer = require('inquirer')
const path = require('path')
// 包装 
const {promisify} = require('util')
let downLoadGitRepo = require('download-git-repo')
downLoadGitRepo = promisify(downLoadGitRepo) // 装成ES6
// 复制
let ncp = require('ncp')
ncp = promisify(ncp)
// 复杂选择
const fs = require('fs')
const metalSmith = require('metalsmith')
let {render} = require('consolidate').ejs
render = promisify(render)
const {downloadDirectory} = require('./constant')
const download = require('download-git-repo')
// 美化终端
const chalk = require('chalk')

// 获取仓库信息
const fetchRepoList = async() => {
    const {data} = await axios.get("/*请求地址*/ https://api/orgs/repos")
    return data
}
// 抓取版本(tag)列表
const fetchTagList = async(repo) => {
    const {data} = await axios.get("/*请求地址*/" `https://api/orgs/${repo}/tags`)
    return data
}
// 下载项目
const downLoad = async(repo, tag) => {
    let api = `xiu/${repo}`
    if(tag) {
        api += `#${tag}`
    }
    const tempdest = `${downloadDirectory}/${repo}`
    await downLoadGitRepo(api,tempdest)
    return tempdest
}
const waitFnLoading = (fn,message) => async(...args) => {
    // loading 加载
    const spinner = ora(message)
    spinner.start()
    let repos = await fn(...args)
    spinner.succeed();
    return repos
}
module.exports = async(proname) => {
    let repos = await waitFnLoading(fetchRepoList,'fetch template...')()
    // 交互选择
    repos = repos.map(item => item.name)
    const {repo} = await Inquirer.prompt({
        name: 'repo',
        type: 'list',
        message: 'please choise a template',
        choices : repos, // 选择列表
    });
    // 获取对应的版本号
    let tags = await waitFnLoading(fetchTagList,'fetch template tag...')(repo)
    tags = tags.map((item) => item.name)
    const {tag} = await Inquirer.prompt({
        name: 'repo',
        type: 'list',
        message: 'please choise a tags for template',
        choices : tags, // 选择列表
    });
    // 下载项目 返回临时的存放目录
    const result = await waitFnLoading(downLoad,'downloading...')(repo,tag)
    if(!fs.existsSync(path.join(result,'ask.js'))) {
        await ncp(result,path.resolve(proname))  
    } else {
        // 复杂模板需要选择
        await new Promise((resolve,reject) => {
            metalSmith(__dirname)
                .source(result)
                .destination(path.resolve(proname))
                .use(async(files,metal,done) => {
                    // files 现在就是所有的文件
                    const args = require(path.join(result,'ask.js'))
                    // 选择
                    const obj = await Inquirer.prompt(args)
                    const meta = metal.metadata()
                    Object.assign(meta,obj)
                    delete files["ask.js"]
                    done()
                })
                .use((files,metal,done)=>{
                    const obj = metal.metadata()
                    Reflect.ownKeys(files).forEach(async(file)=>{
                        if(file.includes("js")|| file.includes("json")) {
                            let content = files[file].contents.toString()
                            if(content.includes("<%")) {
                                content = await render(content, obj)
                                files[file].contents = Buffer.from(content) // 渲染
                            }
                        }
                    })
                    done()
                }).build(err => {
                    if(err) {
                        reject()
                    }else {
                        resolve()
                    }
                })
        })
    }
    console.log(`
${chalk.green('thanks to use my CLI')}
${chalk.white.bold.bgBlue('success download')}
----------------------------------
⭕ ${chalk.red('version of this')}${chalk.white(repo,tag)}${chalk.white('buy me a coofee')}
design by ${chalk.bgBlue.yellow('wuchanghua')}™️
----------------------------------
${chalk.bgWhite.blue('如果你足够充满智慧,加入我,和我一起创造 QQ:1453346832 Email: 1453346832@qq.com')}
${chalk.green('Finish to 100%')}
${chalk.green('Welcome')}
`)
}

💡constant.js

  • 在create中用到的地址下载,你会发现无法复刻到本地原因在于 电脑系统版本不同,临时文件存放路径不同

  • 所以这里可以单独对版本进行判断

const downloadDirectory = `${process.env[process.platform === 'darwin' ? 'Home' : 'USERPROFILE']}/.template`
module.exports = {
    downloadDirectory
}
  • 如果是 ‘darwin’ 那就是 mac 如果不是 其他的都是Windows

🎈完结

祝福你也可以完成自己的脚手架

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/195696.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

混合人机协同制造系统设计与控制中的运营管理问题:一项调查

S. Ehsan Hashemi-Petroodi , Simon Thevenin , Sergey Kovalev , Alexandre Dolgui 小于翻译摘要&#xff1a;能够执行多种任务的制造系统需要不同类型的资源。使用机器人的全自动系统具有高速、准确、不知疲倦和力量&#xff0c;但它们很昂贵。另一方面&#xff0c;人类工作者…

【Redis】快速入门使用

文章目录Redis初识NosqlRedis安装依赖库上传安装包并解压启动Redis桌面客户端Redis常见命令Redis通用命令String类型String的常见命令Key结构Hash类型List类型Set类型SortedSet类型Redis的Java客户端Jedis客户端快速入门连接池SpringDataRedis客户端快速入门自定义序列化String…

无法通过SSH远程登录Linux实例时的排查指引-阿里云国际

本文介绍在使用阿里云国际版云服务器实例时&#xff0c;通过SSH远程登录Linux系统的ECS实例时&#xff0c;连接失败&#xff0c;无法正常登录Linux实例的排查指引&#xff1a; SSH登录失败时没有明确的报错信息 处理流程 如果没有收到系统返回的报错信息&#xff0c;请根据以下…

多级缓存架构 | 黑马Redis高级篇

目录 一、多级缓存介绍 1、传统缓存的问题 2、多级缓存方案 二、JVM进程缓存 1、初始Caffeine 缓存分类 Caffeine入门 2、实现进程缓存 三、Lua脚本 1、初始Lua 2、Lua语法 数据类型 变量 循环 ​编辑 函数 条件控制 四、OpenResty 1、初始OpenResty 2、Open…

Java 定时任务详解

文章目录单机定时任务技术选型TimerScheduledExecutorServiceSpring Task时间轮分布式定时任务技术选型QuartzElastic-JobXXL-JOB单机定时任务技术选型 Timer java.util.Timer是 JDK 1.3 开始就已经支持的一种定时任务的实现方式。 Timer 内部使用一个叫做 TaskQueue 的类存…

在GCP的Kubernetes上安装dapr

1 简介 我们之前使用了dapr的本地托管模式&#xff0c;但在生产中我们一般使用Kubernetes托管&#xff0c;本文介绍如何在GKE(GCP Kubernetes)安装dapr。 相关文章&#xff1a; dapr本地托管的服务调用体验与Java SDK的Spring Boot整合 dapr入门与本地托管模式尝试 2 安装…

STM32开发(2)----CubeMX的安装和使用

CubeMX的安装和使用前言一、CubeMX简介二、软件安装二、软件使用HSE 和 LSE 时钟源设置时钟树配置功能引脚配置配置 Debug 选项生成工程源码总结前言 本章对STM32CubeMX的安装和使用做简单介绍 一、CubeMX简介 STM32CubeMX是一种图形化工具&#xff0c;它允许非常简单地配置…

C++:指针

目录 1.指针 1.1指针三要素&#xff1a; 1.2修饰结构体struct 1.3 Pointers of Pointers 1.4constant修饰 pointer 2.指针和数组 2.1.数组的地址是连续的 2.2pointer arithmetic:指针的代数运算 2.3指针和数组的不同 3.内存分配&#xff1a; 1.指针 1.1指针三要素…

内卷潮不断袭来,智己汽车主打高端市场有何胜算?

当前&#xff0c;新能源汽车赛道已进入白热化&#xff0c;2022年全年产销迈入700万辆规模&#xff0c;分别达到705.8万辆和688.7万辆&#xff0c;同比分别增长96.9%和93.4%&#xff0c;市占率为25.6%。在政策和市场的双轮驱动下&#xff0c;新能源汽车市场的竞争愈发激烈。为掌…

SpringCloudAlibaba和nacos整合sentinel的简单案例

文章目录1.简单代码1.1依赖1.2配置文件配置1.2.1项目yml配置文件1.2.2nacos配置文件1.2controller1.3 service2.sentinel下载运行3.运行验证nacos的安装部署SpringCloudAlibaba整合nacos1.简单代码 1.1依赖 SpringBoot 2.3.12.RELEASE SpringCloudAlibaba 2.2.8.RELEASE <…

自动识别查找特定的串口号 比如设备管理器中Modem属性里的串口 按这个方法可以获取设备管理器任意信息。C++

1.目标&#xff1a; 自动识别查找特定的串口号 2.注册表里搜串口号 设备管理器中所有的信息都在注册表中有&#xff0c;那么我直接在注册表里搜COM143。 搜到了这个&#xff0c;但这里有2个名称key相同的。后面193,192还是可能会变的&#xff0c;不方便精确识别。继续搜。 这…

从0到1一步一步玩转openEuler--01 openEuler操作系统介绍

1 openEuler操作系统介绍 openEuler是一款开源操作系统。当前openEuler内核源于Linux&#xff0c;支持鲲鹏及其它多种处理器&#xff0c;能够充分释放计算芯片的潜能&#xff0c;是由全球开源贡献者构建的高效、稳定、安全的开源操作系统&#xff0c;适用于数据库、大数据、云…

JVM 基础 - Java 垃圾回收机制

Java 垃圾回收机制一&#xff1a;哪些内存需要回收二&#xff1a;怎么定义垃圾1、引用计数算法2、可达性分析算法3、方法区的回收三&#xff1a;引用类型1、强引用2、软引用3、弱引用4、虚引用四&#xff1a;怎么回收垃圾1、垃圾回收算法标记 - 清除算法标记 - 整理算法标记 - …

STM32MP157开发板Linux+Qt项目实战:智慧家庭

stm32mp157开发板FS-MP1A是华清远见自主研发的一款高品质、高性价比的Linux单片机二合一的嵌入式教学级开发板。开发板搭载ST的STM32MP157高性能微处理器&#xff0c;集成2个Cortex-A7核和1个Cortex-M4 核&#xff0c;A7核上可以跑Linux操作系统&#xff0c;M4核上可以跑FreeRT…

第八章 idea集成github

第八章 idea集成github 第一节 给 IDEA 安装github插件 在IDEA中连接github需要在IDEA中github插件(如果有的话就不用安装了) 我这个是已经安装的状态 &#xff08;安装过程中按照提示安装即可&#xff09; 第二节 在 IDEA 中设置github账号 方式一 用户名密码登录 这种方…

HTTPS、SSH共享端口的--工具SSLH

目录 使用的环境 一、安装SSLH 二、配置nginx服务器 三、配置SSLH 三、启用并启动 sslh 服务以更新更改 四、测试 使用的环境 Ubuntu作为靶机&#xff0c;centos7做测试 一、安装SSLH 在Ubuntu上安装命令如下 $ sudo apt-get install sslh 安装 SSLH 时&#xff0c;将…

【LeetCode】最长同值路径 [M](二叉树)

687. 最长同值路径 - 力扣&#xff08;LeetCode&#xff09; 一、题目 给定一个二叉树的 root &#xff0c;返回 最长的路径的长度 &#xff0c;这个路径中的 每个节点具有相同值 。 这条路径可以经过也可以不经过根节点。 两个节点之间的路径长度 由它们之间的边数表示。 示…

阿里二面: BigKey、HotKey 问题严重,该如何 预防和解决

BigKey、HotKey是 日常生产中经常会碰到由于redis集群的不当访问&#xff0c;造成的线上问题。 而且&#xff0c;这也是常见的面试题。 在咱们社群的面试交流中&#xff0c;有很多小伙伴在面试网易、滴滴、京东等大厂的二面、三面中遇到了这个问题。 前段时间&#xff0c;有…

Hadoop基础之《(9)—整合HBase+Phoenix+Hive》

一、HBase简介 1、HBase定义 Apache HBase是以HDFS为数据存储的&#xff0c;一种分布式、可扩展的NoSQL数据库&#xff08;非关系型&#xff0c;以k,v的形式存储数据&#xff09;。 HBase可以认为是以HDFS为存储的数据库。 2、HBase数据模型 &#xff08;1&#xff09;HBase的…

Python---方法(普通方法,类方法,静态方法)

专栏&#xff1a;python 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;Python基础已经更新完&#xff0c;接下来是Python面向对象的知识点。 方法前言普通方法类方法静态方法总结前言 方法是什么&#xff1f; 一个类&#xff0c;它有特征&#xff0c;也有动作&#xff0…