初始化
- 新建文件夹
- 初始化命令
npm init -y
tsc --init
npm i @types/node
npm i typescript
# 处理别名
npm i -D tsc-alias
-y 表示选项都为yes
安装ts相关依赖
新建相关文件
-
bin 文件夹
-
src文件夹
-
commands 文件夹 (命令
-
utils 文件夹 (封装方法)
- index.ts文件
export * from "./chalk"
- index.ts文件
-
index.ts 文件
#! /usr/bin/env node console.log('hello gaogao')
#! /usr/bin/env node
前后不可有空格
#!用于指定脚本的解释程序,开发npm包这个指令一定要加
-
-
.gitignore 文件
#basic
node_module
package-lock.json
#build
bin
- .npmrc 文件
registry=https://mirrors.huaweicloud.com/repository/npm
TS配置
建【tsconfig.json】
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"outDir": "./bin", // 输出地址 相对路径
"baseUrl": "./",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"paths":{
"@":["src"],
"@utils":["utils"],
}
},
"include": [
"./src",
"src/**/*.ts",
"src/**/*.d.ts"
]
}
修改【package.json】
bin:执行的文件或命令
scripts-build 处理ts文件
{
"name": "gaogao-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc && tsc-alias"
},
"bin": {
"gaogao": "/bin/src/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@types/figlet": "^1.7.0",
"@types/fs-extra": "^11.0.4",
"@types/inquirer": "^9.0.7",
"@types/log-symbols": "^3.0.0",
"@types/node": "^22.13.2",
"@types/shelljs": "^0.8.15",
"chalk": "^4.0.0",
"commander": "^9.0.0",
"download-git-repo": "^3.0.2",
"figlet": "^1.8.0",
"fs-extra": "^10.0.1",
"inquirer": "^8.2.1",
"loading-cli": "^1.1.2",
"log-symbols": "^4.1.0",
"ora": "^5.4.1",
"shelljs": "^0.8.5",
"table": "^6.9.0",
"typescript": "^5.7.3"
},
"devDependencies": {
"tsc-alias": "^1.8.10"
}
}
测试
ts语言需要先build
npm run build
build后bin文件夹下自动新增index.js文件
验证修改是否生效都需要build
cnpm link
gaogao
安装相关工具
安装固定版本,有些版本会有bug
commander
https://www.npmjs.com/package/commander
- 处理控制台命令工具
- 解析用户输入时一些参数
- 例如 create 就是利用此工具做解析辅助
cnpm i commander@9.0.0
import {program} from 'commander'
import Pack from "../package.json"
program.version(Pack.version, "-v, --version");
program.parse(process.argv)//nodejs提供的属性
封装常用命令
- commands文件夹下
新建create文件夹
文件
import commandCreate from'./create'
// create见 create命令目录
const commands:any = {
'create <project-name>':{
description:'create a project',
option:[
{
cmd:'-f,--force',
msg:'overwrite target directory if it exists'
}
],
action:commandCreate
}
}
export default commands
import { Command } from "commander";
import Commands from "@commands";
//index.ts
Object.keys(Commands).forEach((command:any) => {
const current:any = program.command(command);
if (Commands[command].option && Commands[command].option.length) {
let options = current.option
Commands[command].option.forEach((item: { cmd: string; msg: any }) => {
current.option(item.cmd, item.msg || "");
});
}
current.action(Commands[command].action);
});
chalk 美化工具
- 该模块用于添加颜色和样式到控制台输出
效果见【figlet】
import chalk from 'chalk'
console.log("\r\n" + chalk.greenBright.bold('hello gaogao-cli'))
封装字体处理
import chalk from 'chalk';
export const Cblue= (text:string) =>chalk.blue(text)
export const Cred= (text:string) =>chalk.red(text)
export const Cgreen= (text:string) =>chalk.green(text)
figlet
https://www.npmjs.com/package/figlet
- 该模块用于生成ASCII艺术字
具体字体可以去figlet官网查看
cnpm i figlet@1.5.2 @types/figlet
import chalk from 'chalk'
import figlet from 'figlet'
program
.name("gaogao")
.description("gaogao-cli")
.usage("<command> [options]")
// 用在内置的帮助信息之后输出自定义的额外信息
.on("--help", () => {
console.log("\r\n" + chalk.greenBright.bold(figlet.textSync("gaogao-cli", {
font: "Standard",
horizontalLayout: "default",
verticalLayout: "default",
width: 100,
whitespaceBreak: true,
})))
console.log(`\r\n Run ${chalk.cyanBright(`gaogao-cli <command> --help`)} for detailed usage of given command.`)
});
inquirer -命令行交互工具
https://www.npmjs.com/package/inquirer
- 该模块用于实现交互式命令行界面
- 例如vue询问是否单元测试
cnpm i inquirer@8.2.1 @types/inquirer
封装inquirer
- lib文件夹下`新建interactive.ts `文件
import inquirer from 'inquirer';
/**
* @param {string} message 询问提示语句
* @returns {Object} 根据name属性获取用户输入的值{confirm: y/n}
*/
export const inquirerConfirm = async (message:string): Promise<object> => {
const answer = await inquirer.prompt({
type: "confirm",
name: "confirm",
message,
});
return answer;
}
/**
*
* @param {string} name 询问事项
* @param {string} message 询问提示语句
* @param {Array} choices 选择模板列表,默认读取对象的name属性
* @returns {Object} 根据name属性获取用户输入的值{请选择项目模板: xxxxxx}
*/
export const inquirerChoose = async (name:string,message:string, choices:Array<any>): Promise<any> => {
const answer = await inquirer.prompt({
type: 'list',
name,
message,
choices,
});
return answer;
}
/**
* @param {Array} messages 询问提示语句数组
* @returns {Object} 结果对象
*/
export const inquirerInputs = async (messages: Array<any>): Promise<object> => {
const questions = messages.map(msg => {
return {
name: msg.name,
type: "input",
message: msg.message,
}
})
const answers = await inquirer.prompt(questions);
return answers
}
loading-cli
https://www.npmjs.com/package/loading-cli
- utils 下
新建loading
文件 在这里插入代码片
//loading.ts
import loading, { Options, Loading } from "loading-cli";
class Load {
load: null | Loading;
constructor() {
this.load = null;
}
/**
* @Descripttion: 开始loading状态
* @msg:
* @param {Options} options
* @return {*}
*/
start (options: Options | string) {
if(!this.load){
typeof options==='object'
&&!options.frames&&(options.frames=['<','<','^','>','>','_','_'])
this.load = loading(options).start()
}else{
this.load.start(options as string)
}
};
stop () {
this.load && this.load.stop();
};
succeed(text='success') {
this.load && this.load.succeed(text);
};
warn(text: string) {
this.load && this.load.warn(text);
};
info (text: string){
this.load && this.load.info(text);
};
}
export default new Load();
// index.ts
program
.command("loading")
.description("View all available templates")
.action(() => {
loading.start({
color: "red",
text: "begin",
});
setTimeout(() => {
loading.warn("警告");
setTimeout(() => {
loading.info("提示");
setTimeout(() => {
loading.stop();
}, 2000);
}, 2000);
}, 2000);
})
fs
https://url.nodejs.cn/api/fs.html
- 该模块用于对文件系统进行更强大的操作。
cnpm i fs-extra.0.1 /fs-extra
封装文件处理方法
import fs from "fs";
import { Cred } from "./chalk";
export const readDir = (path: string): Promise<any> =>
new Promise((res, rej) => {
fs.readdir(path, (err) => {
if (!err) res(true);
res(false)
});
});
export const mkdir = (path: string): Promise<any> =>
new Promise((res, rej) => {
fs.mkdir(path, (err) => {
if (!err) res("");
rej(
`${Cred("Can not mak dir")} ${
typeof err === "string" ? err : JSON.stringify(err)
}`
);
});
});
export const rm = (path: string): Promise<any> =>
new Promise((res, rej) => {
fs.rm(path,{ recursive: true}, (err) => {
if (!err) res("");
rej(
`${Cred("Can not remove dir:"+ path)} ${
typeof err === "string" ? err : JSON.stringify(err)
}`
);
});
});
其他常用工具
# 安装ora模块,该模块用于显示动画加载效果。
cnpm i ora@5.4.1
# 安装download-git-repo模块,该模块用于下载并提取Github/Git(template本地)仓库中的文件。
cnpm i download-git-repo@3.0.2
# 安装handlebars模块,该模块用于处理模板文件。
cnpm i handlebars@4.7.6
# 安装log-symbols模块,该模块用于在控制台输出不同类型的日志符号(√或×)。
cnpm i log-symbols@4.1.0
# 安装axios模块,该模块用于发起HTTP请求。
cnpm i axios@0.26.1
# 安装gitee-repo模块,该模块用于从Gitee仓库中下载模板文件。
cnpm i gitee-repo@0.0.2
# 命令行界面表格内容显示
cnpm i table
# 基于nodejs的shell命令工具
cnpm i shelljs @types/shelljs
# 在控制台输出不同类型的日志符号(√或×)
cnpm i log-symbols@4.1.0 @types/log-symbols
配置模版文件
- lib文件夹下
新建constants.ts
文件
//constants.ts
/**
* 项目模板列表
*/
export const templates = [
{
name: "vue-template",
value: "direct:https://gitee.com/账号/vue-template.git",
desc: "基于vite的自定义vue项目模板",
},
];
/**
* 项目信息
*/
export const messages = [
{
name: "name",
message: "请输入项目名称:",
},
{
name: "description",
message: "请输入项目描述:",
},
];
//index.ts
import { table } from 'table';
import { templates } from '../lib/constants'
// 查看模板列表
program
.command("ls")
.description("View all available templates")
.action(() => {
const data = templates.map(item => [chalk.greenBright(item.name), chalk.white(item.value), chalk.white(item.desc)]);
data.unshift([chalk.white("Template name"), chalk.white("Template address"), chalk.white("Template description")]);
console.log(table(data));
})
gaogao ls
create命令
create
- commands文件夹下
新建create文件夹
文件
import fs from "fs-extra";
import ora from "ora";
import inquirer from "inquirer";
import path from "path";
import { exec } from "child_process";
import {
readDir,
mkdir,
rm,
Cred,
readFile,
copyFolder,
loading,
inquirerChoose
} from "@utils";
import { TempLatesRepo, TempLatesName, templates } from "../../constant/repo";
export default async function (projectName: any, options: any) {
const cwd = process.cwd();
const targetDirectory = path.join(cwd, projectName);
if (fs.existsSync(targetDirectory)) {
if (options.force) {
// 存在force配置项,直接覆盖
await fs.remove(targetDirectory);
} else {
// 不存在force配置 项,询问是否覆盖
let { isOverwrite } = await inquirerChoose(
"isOverwrite",
"Target directory exists,Please choose an action.",
[
{
name: "Overwrite",
value: true,
},
{
name: "Cancel",
value: false,
},
]
);
if (!isOverwrite) {
console.log("Cancel");
return;
} else {
loading.start(`Removing ${projectName},please wait a minute`);
await fs.remove(targetDirectory);
loading.stop();
}
}
}
const spinner = ora("Creating a project......").start();
try {
await mkdir(targetDirectory);
const TemplatePath = `${process.cwd()}/${TempLatesName}`;
if (await readDir(TemplatePath)) {
spinner.fail(Cred(`${TempLatesName} is existed!Please remove it`));
process.abort();
}
spinner.stop()
//下载模版git
//TempLatesRepo 模版git地址
exec(`git clone ${TempLatesRepo}`, async (err) => {
if(err){
spinner.fail(Cred("can not clone this repo,please try again later!"));
spinner.fail(Cred(typeof err === "string" ? err : JSON.stringify(err)));
process.abort();
}
const project =await readFile( `${TemplatePath}/project.json`)
//读取模版工程中project.json文件。存放模版list
const question = [
{
type: "list",
message: "Please select a project template:",
name: "template",
choices: JSON.parse(project),// string[]
},
];
const { template } = await inquirer.prompt(question);
const newPath = targetDirectory;
const oldPath = TemplatePath + "/" + template;
loading.start("Template generation...");
await copyFolder(oldPath, newPath);
// 删除克隆模板
await rm(TemplatePath);
loading.stop()
});
} catch (err) {
console.log(Cred("Project creation failure"));
spinner.fail(Cred(typeof err === "string" ? err : JSON.stringify(err)));
process.abort();
}
}
- 创建项目
- 选择模版