丰富命令系统
1.修改commands/base目录下文件,同级添加index.ts,registerCommand.ts, src目录下修改cli.ts。index.ts
// packages\cli\src\commands\base\build.ts 其他同理
import { Command } from 'commander'
import { logger } from '../../utils/logger'
export const build = (program: Command) => {
return program
.createCommand('build')
.description('构建项目').action(()=>{
logger.info('构建项目');
});
};
// 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\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'
2.commands/base/create.ts
解析参数 匹配技术栈 匹配模板,区分是本地还是远程 对模板进行加工(例如修改package.json name等) 完成创建
安装依赖fs-extra(文件处理)
pnpm i fs-extra -D
packages\cli\src\utils\loadTemplate.ts
// packages\cli\src\utils\loadTemplate.ts
// 本地模板,fs操作
// import {readFile} from 'fs-extra'
import {copy} from 'fs-extra'
import { join } from 'node:path'
// readFile(join(__dirname,'../../cli/package.json')).then(data=>{
// console.log('加载本地脚手架',data.toString())
// })
export const loadTemplate = async (templateName:string) => {
// 1. 获取模板路径
const templatePath = join(__dirname,`../templates/${templateName}`)
console.log('templatePath',templatePath)
// 2. 拷贝模板到目标路径
await copy(templatePath,`${process.cwd()}/wzm-demo`)
console.log('模板加载完成')
}
// packages\cli\src\commands\base\create.ts
import { Command } from 'commander'
import { logger } from '../../utils/logger'
import { loadTemplate } from '../../utils/loadTemplate'
import pc from 'picocolors'
export const create = (program:Command) => {
return program
.createCommand('create')
.description('初始化创建项目').action(()=>{
logger.info(pc.bgCyan('初始化创建项目'));
loadTemplate('template-ssr-vue')
});
};
增加packages\cli\templates文件夹,目录下放对应的模板文件
在packages\cli\src\cli.ts引入loadTemplate.ts
执行命令wzm-cli-demo create
3.根据传参选择对应的脚手架
// \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())
// })
export const loadTemplate = async (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()}/wzm-demo`)
console.log('模板加载完成')
}
// packages\cli\src\commands\base\create.ts
import { Command } from 'commander'
import { logger } from '../../utils/logger'
import { loadTemplate } from '../../utils/loadTemplate'
import pc from 'picocolors'
export const create = (program:Command) => {
return program
.createCommand('create')
.arguments('<project-name>')
.option('-f, --template <template>', 'template name')
.description('初始化创建项目').action((projectName, options)=>{
logger.info(pc.bgCyan('初始化创建项目'));
logger.info(pc.bgCyan(`name: ${projectName}, options: ${JSON.stringify(options)}`));
loadTemplate('template-ssr-vue')
});
};
执行命令 wzm-cli-demo2 create wzm-demo --template vue3-ts
wzm-demo 是定义的文件夹名称
vue3-ts 是自定义指定的templates文件夹下的某一个脚手架项目
name: wzm-demo, options: {"template":"vue3-ts"}
// packages\cli\src\commands\base\create.ts
import { Command } from 'commander'
import { logger } from '../../utils/logger'
import { loadTemplate } from '../../utils/loadTemplate'
import pc from 'picocolors'
import prompts from 'prompts'
// 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)=>{
console.log('已经进到执行环境中',projectName)
let { template } = options
// 如果未指定模板,则提示用户选择
if(!template){
const response = await prompts({
type: 'select',
choices: [
{ title: 'template-ssr-vue-模板', value: 'template-ssr-vue' },
{ title: 'template-ssr-vue-ts-模板', value: 'template-ssr-vue-ts' }
],
name: 'selectTemplate',
message: 'What is your framework?'
})
template = response.selectTemplate
logger.info(pc.bgCyan(`手动选择了模板: ${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\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('模板加载完成')
}
4. 加载本地脚手架和远程脚手架模板
1. package.json
packages\cli\package.json
{
"name": "@wzm/cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "tsup --watch",
"build": "tsup"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.14.0",
"files": [
"bin",
"index.js"
],
"bin": {
"wzm-cli-demo2": "bin/wzmtest"
},
"dependencies": {
"@wzm/core": "workspace:*",
"@wzm/shared": "workspace:*",
"giget": "^2.0.0"
},
"devDependencies": {
"@types/node": "^24.3.0",
"commander": "^14.0.0",
"consola": "^3.4.2",
"fs-extra": "^11.3.1",
"picocolors": "^1.1.1",
"prompts": "^2.4.2",
"tsup": "^8.5.0",
"typescript": "^5.9.2"
}
}
2.packages\cli\src\utils\loadTemplate.ts
// 本地模板,fs操作
// import {readFile} from 'fs-extra'
import {copy, emptyDir } from 'fs-extra'
import { downloadTemplate } from 'giget'
import { join } from 'node:path'
interface loadTemplate {
projectName: string
templateName: string
local?: boolean
}
// 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 loadLoacalTemplate = async (params:Omit<loadTemplate,'local'>) => {
const { projectName, templateName } = params;
// 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('模板加载完成')
}
export const loadRemoteTemplate = async (params:Omit<loadTemplate,'local'>) => {
const { projectName } = params
const { dir } = await downloadTemplate("https://codeload.github.com/design-sparx/antd-multipurpose-dashboard/tar.gz/refs/heads/main", {
dir: join(process.cwd(), ".temp"),
force: true
})
await emptyDir(`${process.cwd()}/projectName`)
await copy(dir, join(process.cwd(), projectName))
console.log('远程模板加载完成')
}
export const loadTemplate = async (params:loadTemplate) => {
const { local, ...resetParams } = params;
console.log('恭喜发财', resetParams)
if(local){
console.log('加载本地模板')
loadLoacalTemplate(resetParams)
} else {
console.log('加载远程模板')
loadRemoteTemplate(resetParams)
}
}
3. packages\cli\src\commands\base\create.ts
import { Command } from 'commander'
import { logger } from '../../utils/logger'
import { loadTemplate } from '../../utils/loadTemplate'
import pc from 'picocolors'
import prompts from 'prompts'
// 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)=>{
console.log('已经进到执行环境中',projectName)
let { template } = options
// 如果未指定模板,则提示用户选择
if(!template){
const response = await prompts({
type: 'select',
choices: [
{ title: 'template-ssr-vue-模板', value: 'template-ssr-vue' },
{ title: 'template-ssr-vue-ts-模板', value: 'template-ssr-vue-ts' }
],
name: 'selectTemplate',
message: 'What is your framework?'
})
template = response.selectTemplate
logger.info(pc.bgCyan(`手动选择了模板: ${template}`));
}
logger.info(pc.bgCyan('初始化创建项目'));
logger.info(pc.bgCyan(`name: ${projectName}, options: ${JSON.stringify(options)}`));
// loadTemplate('template-ssr-vue')
// loadTemplate(projectName, template)
loadTemplate({projectName, templateName:template, local:false})
});
};
4. packages\cli\src\index.ts
// \packages\cli\tsup.config.ts
import { defineConfig } from "tsup"
export default defineConfig ({
"dts": true, // 在dist目录下生成dts文件
"entry": ["src/index.ts"],
"format": ["cjs"],
"outDir": "dist",
"platform": "node",
"target": "node18",
// 不要将 ESM-only 依赖打包进来,避免 import.meta 丢失
"external": ["giget", "nypm", "tinyexec"]
})