企业脚手架-雏形
1. 基础修改
packages\cli\bin\wzmserve
import { runCLI } from '../dist/index.js'
runCLI()
console.log('bin执行目录文件')
packages\cli\src\cli.ts
/*
*/
const create = (...args) => {
console.log("初始化项目create", args);
};
const build = (...args) => {
console.log("构建项目build", args);
};
const serve = (...args) => {
console.log("启动项目serve", args);
};
export const run = (args) => {
const [, , ...runargs] = args;
const [command, ...commandArgs] = runargs;
console.log("command参数:", command);
console.log("cli-获取参数:", runargs);
switch (command) {
case "create":
console.log("create参数:", commandArgs);
create(...commandArgs);
break;
case "build":
build(...commandArgs);
break;
case "serve":
serve(...commandArgs);
break;
default:
console.log("请输入正确的命令", ...commandArgs);
break;
}
};
packages\cli\src\index.ts
import { run } from "./cli";
// 入口文件保持足够的整洁,方便后续扩展,给外部暴露内部的api
export const defineConfig = () => {};
export const runCLI = () => {
// 获取配置信息
run(process.argv);
};
执行命令 wzm-cli-demo create test -r "wzm"
command 参数: create
cli-获取参数: [ 'create', 'test', '-r', 'wzm' ]
create 参数: [ 'test', '-r', 'wzm' ]
初始化项目 create [ 'test', '-r', 'wzm' ]
bin 执行目录文件
2.增加commander
pnpm i commander -D
tsup.config.t
import { defineConfig } from "tsup";
export default defineConfig({
dts: true, // 在dist目录下生成dts文件
clean: true,
entry: ["src/index.ts"],
format: ["cjs"], // 属性有esm, cjs, iife 默认是esm
outDir: "dist",
platform: "node",
target: "node22",
// 不要将 ESM-only 依赖打包进来,避免 import.meta 丢失
external: [
"commander",
"events",
"path",
"fs",
"util",
"os",
"stream",
"child_process",
"http",
"https",
"url",
"net",
"dns",
"zlib",
"crypto",
"tls"
]
});
2.wzmserve
import { runCLI } from "../dist/index.cjs";
runCLI();
console.log("bin执行目录文件");
3.cli.ts
import { program } from "commander";
import { create } from "./commands/base/create";
import { build } from "./commands/base/build";
import { serve } from "./commands/base/serve";
// const create = () => {
// console.log("初始化项目create");
// };
// const build = (...args) => {
// console.log("构建项目build", args);
// };
// const serve = (...args) => {
// console.log("启动项目serve", args);
// };
/**
export const run = (args) => {
const [, , ...runargs] = args;
const [command, commandArgs] = runargs;
console.log("command参数:",command);
console.log("cli-获取参数:",runargs);
switch (command) {
case "create":
create(commandArgs);
break;
case "build":
build(commandArgs);
break;
case "serve":
serve(commandArgs);
break;
default:
console.log("请输入正确的命令", commandArgs);
break;
}
}
**/
program.version("0.0.1").name("wzm-cli");
program.command("create").description("创建项目").action(create);
program.command("build").description("打包项目").action(build);
program.command("serve").description("构建项目").action(serve);
export const run = (args) => {
const [, , ...runargs] = args;
const [command, commandArgs] = runargs;
console.log("cli-command参数:", command);
console.log("cli-command-获取参数:", commandArgs);
switch (command) {
case "create":
create(commandArgs);
break;
case "build":
build(commandArgs);
break;
case "serve":
serve(commandArgs);
break;
default:
console.log(`${command}命令有误,请输入正确的命令, `);
break;
}
};
build.ts,serve.ts,build.ts文件内容如下:
import { Command } from "commander";
// import { logger } from '../../utils/logger'
export const build = (program: Command) => {
console.info("-----build-构建项目------", program);
// return program
// .createCommand("build")
// .description("构建项目")
// .action(() => {
// console.info("构建项目");
// });
};
3.创建packages\cli\src\commands\base\greet.ts,
promptsboxenchalkgradient-stringorafigletcli-table
import { Command } from "commander";
import { logger } from "../../utils/logger";
import prompts from "prompts";
import boxen from "boxen";
import chalk from "chalk";
import gradient, { rainbow, pastel } from "gradient-string";
import ora from "ora";
import figlet from "figlet";
import Table from "cli-table";
import standard from "figlet/importable-fonts/Standard.js";
figlet.parseFont("Standard", standard);
export const greet = async (program: Command) => {
logger.info("初始化项目greet", program);
const nameRes = await prompts({
type: "text",
name: "name",
message: "请输入你的名字"
});
const hobbyRes = await prompts({
type: "select",
name: "hobby",
choices: [
{ title: "篮球", value: "football" },
{ title: "足球", value: "basketball" },
{ title: "游泳", value: "swimming" }
],
message: "请输入你的爱好"
});
logger.info(`Hello ${nameRes.name},你爱好的是${hobbyRes.hobby}`);
logger.info(
boxen("boxen 盒子模型 恭喜发财,红包拿来", {
borderColor: "blue", // 'black' 'red' 'green' 'yellow' 'blue' 'magenta' 'cyan' 'white' 'gray' or a hex value like '#ff0000'
borderStyle: "double", // string| object // signal 'round' 'double' 'bold' 'singleDouble', 'doubleSingle', 'classic','arrow','none'
/* {
topLeft: '+',
topRight: '+',
bottomLeft: '+',
bottomRight: '+',
top: '-',
bottom: '-',
left: '|',
right: '|'
}*/
dimBorder: false, // 边框是否为 dim
title: "文本描述展示", // 居中展示
titleAlignment: "center", // 'left' 'center' 'right'
width: 50,
height: 10,
// fullscreen: (width, height) => [width, height - 100], // 宽高
padding: 1,
margin: 1,
float: "left", // 'left' 'center' 'right',
backgroundColor: "yellow", // 'black' 'red' 'green' 'yellow' 'blue' 'magenta' 'cyan' 'white' 'gray' or a hex value like '#ff0000'
textAlignment: "left" // Values: 'left' 'center' 'right'
})
);
console.log(
boxen("boxen 盒子模型", { title: "magical", titleAlignment: "center" })
);
console.log(
boxen(chalk.blue("boxen 盒子模型+chalk"), {
padding: 1,
margin: 1,
borderStyle: "double",
borderColor: "red"
})
);
// console.log(chalk.blue('Hello world!'))
console.log(pastel("I love gradient-string!"));
console.log(rainbow("It is so pretty! 🌈"));
const duck = gradient(["green", "yellow"]).multiline(`
__
<(o )___
( ._> /
---
`);
console.log(duck);
console.log(rainbow.multiline("多个颜色渐变\n变化不同"));
console.log(
gradient(["cyan", "pink"], { interpolation: "hsv" }).multiline(
"Multi line\nstring"
)
);
const spinner = ora("加载中").start();
console.log("========");
setTimeout(() => {
spinner.color = "yellow";
// spinner.text = 'Loading rainbows'
spinner.prefixText = "前缀";
spinner.suffixText = "后缀";
spinner.text = chalk.red("Loading rainbows");
spinner.succeed("加载成功完成");
}, 1000);
//
// ASCII艺术字
const asciiArt = figlet.textSync("My App", { font: "Standard" });
// 渐变标题
const title = gradient.rainbow("欢迎使用我的应用程序");
// 信息表格
const table = new Table({
head: ["TH 1 label", "TH 2 label"],
colWidths: [40, 50]
});
table.push({ 版本: "1.0.0" }, { 作者: "开发者" }, { 许可证: "MIT" });
// 使用boxen包装
const message = `${chalk.bold(asciiArt)}\n\n${title}\n\n${table.toString()}`;
console.log(
boxen(message, {
titleAlignment: "center",
padding: 1,
borderStyle: "double",
borderColor: "cyan",
backgroundColor: "#222"
})
);
};
4. 丰富命令
cli文件夹下执行命令pnpm i fs-extra picocolors -Dpackages\cli\src\cli.ts
// packages\cli\src\cli.ts
import { program } from "commander";
import "./commands";
export const defineConfig = () => {
//
};
export const runCLI = () => {
program.parse(process.argv);
};
packages\cli\src\index.ts
export * from "./cli";
packages\cli\src\commands\registerCommand.ts
// packages\cli\src\commands\registerCommand.ts
import type { Command } from "commander";
import { program } from "commander";
type Fn = (p: Command) => Command;
/**
* register new command
* @param program
* @param command
*/
// 负责插件的注册逻辑
export const registerCommand = (fn: Fn) => {
program.addCommand(fn(program));
};
packages\cli\src\utils\loadTemplate.ts
// packages\cli\src\utils\loadTemplate.ts
// 本地模板,fs操作
// import {readFile} from 'fs-extra'
import { copy, emptyDir } from "fs-extra";
import { join } from "node:path";
// readFile(join(__dirname,'../../cli/package.json')).then(data=>{
// console.log('加载本地脚手架',data.toString())
// })
// wzm-cli-new>wzm-cli-demo2 create wzm-demo --template template-ssr-vue
export const loadTemplate = async (
projectName: string,
templateName: string
) => {
// 1. 获取模板路径
const templatePath = join(__dirname, `../templates/${templateName}`);
// console.log('templatePath',templatePath)
const targetPath = `${process.cwd()}/wzm-demo`;
// 2. 清空目标路径
await emptyDir(targetPath);
// 3. 拷贝模板到目标路径
await copy(templatePath, `${process.cwd()}/${projectName}`);
console.log("模板加载完成");
};
-
packages\cli\templates在此文件下增加模板文件,如 template-ssr-vue -
执行命令
wzm-cli-demo create ssr-vue3在同级目录下会创建一个ssr-vue3文件夹,完成了脚手架的创建 -
增加好看的颜色设置
- 修改
packages\cli\src\commands\base\create.ts文件
- 修改
// packages\cli\src\commands\base\create.ts
import { Command } from "commander";
import { logger, figlet, pastel, gradient, rainbow } from "../../utils/logger";
import { loadTemplate } from "../../utils/loadTemplate";
import prompts from "prompts";
// ASCII艺术字
const asciiArt = figlet.textSync("Welcome", { font: "Standard" });
const welcomeMsg = `${asciiArt}`;
// wzm-cli-new>wzm-cli-demo2 create wzm-demo --template template-ssr-vue
export const create = (program: Command) => {
return program
.createCommand("create")
.arguments("<project-name>")
.option("-f, --template <template>", "template name")
.description("初始化创建项目")
.action(async (projectName, options) => {
logger.log(welcomeMsg);
logger.log(pastel(welcomeMsg));
let { template } = options;
// 如果未指定模板,则提示用户选择
if (!template) {
const response = await prompts({
type: "select",
choices: [
{
title: `${pastel("template-ssr-vue-模板")}?`,
value: "template-ssr-vue"
},
{
title: `${pastel("template-ssr-vue-ts-模板")}?`,
value: "template-ssr-vue-ts"
}
],
name: "selectTemplate",
message: `${pastel("选择你需要的脚手架")}?`
});
template = response.selectTemplate;
const duck = gradient(["green", "yellow"]).multiline(`
__
<(o )___
( ._> /
---
`);
logger.log(duck);
console.log(rainbow.multiline(`手动选择了模板:${template}`));
}
// logger.info(pc.bgCyan("初始化创建项目"));
// logger.info(
// pc.bgCyan(`name: ${projectName}, options: ${JSON.stringify(options)}`)
// );
// loadTemplate('template-ssr-vue')
loadTemplate(projectName, template);
});
};
* `packages\cli\src\utils\logger.ts`
import { createConsola } from "consola";
import boxen from "boxen";
import chalk from "chalk";
import gradient, { rainbow, pastel } from "gradient-string";
import ora from "ora";
import figlet from "figlet";
import Table from "cli-table";
import standard from "figlet/importable-fonts/Standard.js";
figlet.parseFont("Standard", standard);
export const logger = createConsola({});
export {
boxen,
chalk,
gradient,
rainbow,
pastel,
ora,
figlet,
Table,
standard
};
- 优化
packages\cli\src\commands\base\create.ts
// packages\cli\src\commands\base\create.ts
import { Command } from "commander";
import {
logger,
figlet,
pastel,
gradient,
rainbow,
ora,
loggerConsts,
chalk
} from "../../utils/logger";
import { loadTemplate } from "../../utils/loadTemplate";
import prompts from "prompts";
// ASCII艺术字
const asciiArt = figlet.textSync("Welcome", { font: "Standard" });
const welcomeMsg = `${asciiArt}`;
// wzm-cli-new>wzm-cli-demo2 create wzm-demo --template template-ssr-vue
/**
* 创建一个新项目
*
* @param program Commander实例
* @returns 返回配置好的program实例
*/
export const create = (program: Command) => {
return program
.createCommand("create")
.arguments("[project-name]")
.option("-f, --template <template>", "template name")
.description("初始化创建项目")
.action(async (projectName, options) => {
if (!projectName) {
const response = await prompts({
type: "text",
name: "projectName",
message: `${pastel("请输入创建的项目名称:")}?`,
validate: (value) => {
if (!value) return `${pastel("项目名称不能为空")}?`;
return true;
}
});
if (!response.projectName) {
logger.error("项目名称是必需的");
process.exit(1);
}
projectName = response.projectName;
}
logger.log(welcomeMsg);
logger.log(pastel(welcomeMsg));
let { template } = options;
// 如果未指定模板,则提示用户选择
if (!template) {
const response = await prompts({
type: "select",
choices: [
{
title: `${pastel("template-ssr-vue-模板")}?`,
value: "template-ssr-vue"
},
{
title: `${pastel("template-ssr-vue-ts-模板")}?`,
value: "template-ssr-vue-ts"
}
],
name: "selectTemplate",
message: `${pastel("选择你需要的脚手架")}?`
});
template = response.selectTemplate;
const spinner = ora("加载中").start();
spinner.color = "yellow";
// spinner.text = 'Loading rainbows'
spinner.prefixText = "前缀";
spinner.suffixText = "后缀";
spinner.text = chalk.red("Loading rainbows");
const duck = gradient(["green", "yellow"]).multiline(
loggerConsts.l_duck
);
logger.log(duck);
console.log(rainbow.multiline(`手动选择了模板:${template}`));
setTimeout(() => {
spinner.succeed("加载成功完成");
}, 2000);
}
// logger.info(pc.bgCyan("初始化创建项目"));
// logger.info(
// pc.bgCyan(`name: ${projectName}, options: ${JSON.stringify(options)}`)
// );
// loadTemplate('template-ssr-vue')
loadTemplate(projectName, template);
});
};